Java集合源码 -- List列表
List概述
List是一个有序,可重复的集合,可以在List的中间插入和移除元素,根据整数索引访问元素
下图是List集合的框架图
下面是对上图的简单介绍
AbstractCollection: 提供 Collection 接口的骨干实现 Iterator: 迭代器
ListIterator:列表迭代器 Queue:队列
Deque:一个线性 collection,支持在两端插入和移除元素 AbstractSequentialList:提供了 List 接口的骨干实现
LinkedList:链表的实现 ArrayList:大小可变数组的实现 Vector:实现可增长的对象数组
Stack:后进先出(LIFO)的对象堆栈
ArrayList
底层存储
属性 elementData 存储集合中的内容
/** 缓存区:存储元素 */
private transient Object[] elementData;
将元素添加到指定位置
将元素添加到指定位置后, 把原数组中 该位置和之后的元素 向后移动一位
/** 在指定位置添加元素 */
public void add(int index, E element) {
rangeCheckForAdd(index); //判断索引位置是否正确 ensureCapacityInternal(size + 1); // 扩容 //对原数组进行复制处理(位移),从index + 1到size-index
//即向右移动当前位于该位置的元素以及所有后续元素。
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element; //插入
size++;
}
介绍下 System.arraycopy(......)
/**
即从指定源数组中复制一个数组,
复制从指定的位置开始,到目标数组的指定位置结束。
将原数组src从srcPos位置开始复制到dest数组中,到dest的destPos位置开始,复制的长度为length
*/
public static native void arraycopy(Object src, int srcPos, Object dest, int destPos, int length);
删除指定位置的元素
删除元素后, 把原数组中 该位置之后的元素 向前移动一位
public E remove(int index) {
rangeCheck(index); //判断移动位置 modCount++; //记录改变次数
E oldValue = elementData(index); //要删除的元素 int numMoved = size - index - 1; //移动的长度 //向左移动numMoved个长度
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; //把最后一个元素设为null return oldValue;
}
扩容
首先扩容为原来的1.5倍, 在检查新容量, 最大容量为Integer.MAX_VALUE
private void grow(int minCapacity) {
...
int newCapacity = oldCapacity + (oldCapacity >> 1); //将新的容量变为原来容量的1.5倍。
...
hugeCapacity(int minCapacity);
} //对扩容后的容量进行检查
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) //得到的容量<0,为什么会有这种情况,内存溢出了。
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ? //如果需要的容量比规定的最大容量大,那么最大容量只能是 Integer.MAX_VALUE。
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
动态扩容,其实是一个新数组; 在清楚业务,插入的数据量大于扩容后的1.5倍, 应该自己设置一个合适容量
Vector 和 Stack
概述
Vector可以看做ArrayList的同步实现, 具体可参考ArrayList的分析
Stack类提供了栈的结构, 表示先进后出的操作方法
Stack 继承 Vector , 它定义了5个方法并且调用的都是父类中的方法, 下面是方法定义
E push(E item) //把对象压入栈顶部, 内部调用是父类的 addElement(item)
E pop() //移除栈顶对象,内部调用是父类的 removeElementAt(int index)
E peek() //查看栈顶的对象,内部调用是父类的 elementAt(int index)
int search(Object o) // 返回对象在堆栈中的位置,内部调用父类lastIndexOf(Object o)
empty() // 测试堆栈是否为空
自定义Stack
/**
* 自定义Stack
*/
public class MyStack {
private static final int CAPACITY = 3;
private static Object[] array = new Object[CAPACITY];
private static int top = -1; /** 把对象压入栈顶 */
void push(Object o) throws Exception {
if(getSize() == CAPACITY) {
throw new ExceptionStack("stack is full");
} array[++top] = o;
} /** 移除栈顶元素 */
Object pop() throws Exception {
if(isEmpty()) {
throw new ExceptionStack("stack is empty");
}
return array[top--]; } /** 查看栈顶元素 */
Object peek() throws Exception {
if(isEmpty()){
throw new ExceptionStack("Stack is empty");
}
return array[top];
} /** 判断尺寸已满 */
int getSize() {
if(isEmpty()){
return 0;
}else{
return top + 1;
}
} /** 判断为空 */
boolean isEmpty() {
return (top < 0);
} public static void main(String[] args) throws Exception {
MyStack s = new MyStack();
System.out.println(s.isEmpty()); s.push("1");
s.push("2");
s.push("3"); System.out.println(s.isEmpty());
System.out.println(s.getSize());
System.out.println(s.pop());
System.out.println(s.peek());
}
}
自定义异常类:
public class ExceptionStack extends Exception{ //Define myself exception construct with parameters
public ExceptionStack(String string){
super(string);
}
}
LinkedList
LinkedList 的内部实现是双向链表,它允许null的存在,它的方法是不同步的
与ArrayList相比,在插入和删除时优于ArrayLis, 而随机访问则比ArrayList逊色些
主要属性
transient int size = 0; //集合长度
transient Node<E> first; //头节点
transient Node<E> last; //尾节点
介绍下静态内部类Node
private static class Node<E> {
E item; //原节点
Node<E> next; //指向后一个节点
Node<E> prev; //指向前一个节点 //构造方法
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
构造方法
分析下: LinkedList(Collection<? extends E> c) 构造一个包含指定 collection 中的元素的列表
最终调用的是 addAll(..) ,如下:
public boolean addAll(int index, Collection<? extends E> c) {
checkPositionIndex(index); //判断下表越界 Object[] a = c.toArray(); //把c转为数组
int numNew = a.length; //numNew为数组长度
if (numNew == 0)
return false; Node<E> pred, succ; if (index == size) { //在构造的调用过程中,肯定是相等的
succ = null;
pred = last; //pred指向尾节点
} /* 注释的是构造时不执行的
else {
succ = node(index);
pred = succ.prev;
}
*/ for (Object o : a) {
E e = (E) o; //从数组中取出元素
Node<E> newNode = new Node<>(pred, e, null); //创建新节点 /*
if (pred == null)
first = newNode;
*/
else
pred.next = newNode; //原尾节点的后一个节点指向新节点
pred = newNode; //pred成了新节点
} if (succ == null) {
last = pred; //last是添加c后的尾节点
} /*
else {
pred.next = succ;
succ.prev = pred;
}
*/ size += numNew; //修改容量
modCount++; //修改次数+1
return true;
}
node(int index) 返回指定位置的节点
node(int index) 很重要, 添加,删除等操作都会用到, 下面就介绍它
/**
返回指定位置的节点
*/
Node<E> node(int index) { //判断遍历的方向
if (index < (size >> 1)) { // size >> 1 = siz2 /2
Node<E> x = first; //从指定位置的节点开始往前遍历
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
Node<E> x = last; //从尾节点开始向指定位置遍历
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
添加
add(E e ): 把节点加到链表的最后 ,实际上调用的是linkLast(E e)方法
void linkLast(E e) {
final Node<E> l = last; //把末尾节点保存
final Node<E> newNode = new Node<>(l, e, null); //创建一个新节点,并指定前一个节点是原链表的末尾节点
last = newNode; //最后一个节点的引用指向新节点
if (l == null)
first = newNode;
else
l.next = newNode; //原链表的末尾节点的下一个节点指向新节点
size++; //链表总数+1
modCount++;
}
add(int index, E element): 把数据插入指定的位置,实际上调用的是 linkBefore(element, node(index))
/**
参数1: 代表新元素
参数2: succ 代表指定位置的节点
*/
void linkBefore(E e, Node<E> succ) { final Node<E> pred = succ.prev; //把指定位置的前一个节点保存在pred
final Node<E> newNode = new Node<>(pred, e, succ); //创建新节点
succ.prev = newNode; //指定新节点的后一个节点
if (pred == null)
first = newNode;
else
pred.next = newNode; //指定新节点的前一个节点
size++;
modCount++;
}
删除
remove(Object o): 从此列表中移除首次出现的指定元素, 实际上调用的是 unlink(Node<E> x)
/**
参数: 要移除的节点
*/
E unlink(Node<E> x) { final E element = x.item;
final Node<E> next = x.next; // 保存 移除节点 的后一个节点 在变量next中
final Node<E> prev = x.prev; //保存 移除节点 的前一个节点 在变量prev中 if (prev == null) { //true代表 移除节点 是头节点
first = next;
} else {
prev.next = next; // 将 prev后一个节点 指向next
x.prev = null; // 将 移除节点的prev设为空
} if (next == null) { //true代表移除节点是尾节点
last = prev;
} else {
next.prev = prev; //将 next前一个节点 指向prev
x.next = null; //将 移除节点的next 设为空
} x.item = null; //将移除节点的元素 设为空
size--;
modCount++;
return element; //返回移除的元素
}
总结
对LinkedList的操作实际上是对指向前节点和后节点的引用操作,所以其插入和删除效率较高,但是随机访问效率较差,因为要遍历
Java集合源码 -- List列表的更多相关文章
- Java集合源码分析(三)LinkedList
LinkedList简介 LinkedList是基于双向循环链表(从源码中可以很容易看出)实现的,除了可以当做链表来操作外,它还可以当做栈.队列和双端队列来使用. LinkedList同样是非线程安全 ...
- Java集合源码学习(一)集合框架概览
>>集合框架 Java集合框架包含了大部分Java开发中用到的数据结构,主要包括List列表.Set集合.Map映射.迭代器(Iterator.Enumeration).工具类(Array ...
- Java集合源码分析(四)Vector<E>
Vector<E>简介 Vector也是基于数组实现的,是一个动态数组,其容量能自动增长. Vector是JDK1.0引入了,它的很多实现方法都加入了同步语句,因此是线程安全的(其实也只是 ...
- Java集合源码分析(二)ArrayList
ArrayList简介 ArrayList是基于数组实现的,是一个动态数组,其容量能自动增长,类似于C语言中的动态申请内存,动态增长内存. ArrayList不是线程安全的,只能用在单线程环境下,多线 ...
- Java 集合源码分析(一)HashMap
目录 Java 集合源码分析(一)HashMap 1. 概要 2. JDK 7 的 HashMap 3. JDK 1.8 的 HashMap 4. Hashtable 5. JDK 1.7 的 Con ...
- java集合源码分析几篇文章
java集合源码解析https://blog.csdn.net/ns_code/article/category/2362915
- java集合源码分析(三):ArrayList
概述 在前文:java集合源码分析(二):List与AbstractList 和 java集合源码分析(一):Collection 与 AbstractCollection 中,我们大致了解了从 Co ...
- java集合源码分析(六):HashMap
概述 HashMap 是 Map 接口下一个线程不安全的,基于哈希表的实现类.由于他解决哈希冲突的方式是分离链表法,也就是拉链法,因此他的数据结构是数组+链表,在 JDK8 以后,当哈希冲突严重时,H ...
- Java集合源码学习(三)LinkedList分析
前面学习了ArrayList的源码,数组是顺序存储结构,存储区间是连续的,占用内存严重,故空间复杂度很大.但数组的二分查找时间复杂度小,为O(1),数组的特点是寻址容易,插入和删除困难.今天学习另外的 ...
随机推荐
- 计算细胞数【BFS】
问题描述 一矩形阵列由数字0到9组成,数字1到9代表细胞,细胞的定义为沿细胞数字上下左右还是细胞数字则为同一细胞,求给定矩形阵列的细胞个数. 输入格式 2行:第1行为两个整数 mm, nn, 代表矩阵 ...
- [LeetCode]Generate Parentheses题解
Generate Parentheses: Given n pairs of parentheses, write a function to generate all combinations of ...
- Linux 更改时区
原文:https://www.cnblogs.com/st-jun/p/7737188.html Linux修改时区的正确方法 CentOS和Ubuntu的时区文件是/etc/localtime,但是 ...
- CentOS6.5下连网以及输入法下载
宽带拨号连网: 1.系统--首选项--网络连接(或点击桌面右上角连网图标--VPN连接--VPN配置) 2.添加--选择DSL--勾自动连接(也可不勾)--DSL下填写用户名.密码--应用 ...
- css3怎么分清伪类和伪元素
伪类用于向某些选择器添加特殊的效果. 伪元素用于将特殊的效果添加到某些选择器. 伪类有::first-child ,:link:,vistited,:hover,:active,:focus,:lan ...
- PHP在foreach中对$value赋值无效,应该用 ‘键’ 或者 &$value的形式
首先我们看下这段代码: foreach ($data as$value) { $value['name'] = 'Hehe'; } $data中原始的数据为: array(1) { [0] => ...
- 将本地项目放到GitHub上托管并展示
忽然知道自己写的项目效果可以放到网上让别人看到之后,就已经迫不及待了.不墨迹,先去了解GitHub得知,它很强(牛逼),我理解的是这是一个托管平台,可以把自己本地的项目通过git放到上面,你需要新建一 ...
- React 入门实例教程[阮一峰的网络日志] (分享)
作者: 阮一峰 https://github.com/ruanyf/react-demos 转自:http://www.ruanyifeng.com/blog/2015/03/react.html 对 ...
- ArcGIS最权威、最专业的技术分享网站:积思园(www.iarcgis.com)
你对iArcGIS.com说点什么 为什么会有该网站的产生 在这个所谓的“大数据”的时代,每个人都深陷于海量信息无法自拔,因为过多碎片化的数据只会让自己的思维更加迷离,快餐式的阅读只会让自己变得虚胖. ...
- VS2008和VC6.0下使用pthread.h头文件
原文:http://www.cppblog.com/liquidx/archive/2009/06/16/87811.html 要在windows环境下使用 #include <pthread. ...