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(..) ,如下:
1 public boolean addAll(int index, Collection<? extends E> c) { 2 checkPositionIndex(index); //判断下表越界 3 4 Object[] a = c.toArray(); //把c转为数组 5 int numNew = a.length; //numNew为数组长度 6 if (numNew == 0) 7 return false; 8 9 Node<E> pred, succ; 10 11 if (index == size) { //在构造的调用过程中,肯定是相等的 12 succ = null; 13 pred = last; //pred指向尾节点 14 } 15 16 /* 注释的是构造时不执行的 17 else { 18 succ = node(index); 19 pred = succ.prev; 20 } 21 */ 22 23 for (Object o : a) { 24 E e = (E) o; //从数组中取出元素 25 Node<E> newNode = new Node<>(pred, e, null); //创建新节点 26 27 /* 28 if (pred == null) 29 first = newNode; 30 */ 31 else 32 pred.next = newNode; //原尾节点的后一个节点指向新节点 33 pred = newNode; //pred成了新节点 34 } 35 36 if (succ == null) { 37 last = pred; //last是添加c后的尾节点 38 } 39 40 /* 41 else { 42 pred.next = succ; 43 succ.prev = pred; 44 } 45 */ 46 47 size += numNew; //修改容量 48 modCount++; //修改次数+1 49 return true; 50 }
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的操作实际上是对指向前节点和后节点的引用操作,所以其插入和删除效率较高,但是随机访问效率较差,因为要遍历