文章目录
Collection 接口
List 接口
-
ArrayList
底层是通过Arrays.copyof()和System.arraycopy()操作的。
非线程安全,如果需要保证线程安全,后面会介绍其它相关的容器
底层是通过数组实现,内部通过一个Object对象数组来存放元素
默认容量大小 10 ,不指定初始容量的时,第一次add操作的时候,会初始化容量为10.
扩容后的大小是原来的1.5倍数:newCapacity = oldCapacity + (oldCapacity >> 1)
扩容会有一个将旧数组数据拷贝到新数组数据的过程,所以如果知道具体数据量的情况下,指定好容量
在指定位置存放元素的时候,会右移数组index右边位置的所有元素
删除index位置元素的时候,如果index后面有元素,则全部左移动,保证连续
注:在ArrayList中维护了一个int类型的属性modCount,标识操作的次数
当在进行遍历的过程中,如果list发生了修改操作,会抛ConcurrentModificationException的异常public void forEach(Consumer<? super E> action) { final int expectedModCount = modCount final E[] elementData = (E[]) this.elementData; final int size = this.size; for (int i=0; modCount == expectedModCount && i < size; i++) { action.accept(elementData[i]); } if (modCount != expectedModCount) { throw new ConcurrentModificationException(); } }
-
LinkedList
底层是通过双向链表实现的,内部通过Node next 和 Node prev 来维护链表结构
节点的新增和删除等都是通过 Node前后节点地址指向的变化完成
所以LinkedList插入、删除的效率较高。但是在读取效率方面是不及ArrayList,关于get(index)的部分源码// 将链表长度一分为二,判断传入的index在那个区间,如果在前面部分,从第一个开始依次遍历 // 如果在后面部分,则从最后一个节点向前遍历查找 // 所以说越中间的元素,查找次数会越多 查找的效率越低 Node<E> node(int index) { if (index < (size >> 1)) { 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; } }
-
Vector
Vector 和 ArrayList 高度相似,代码逻辑和ArrayList的差不多
不同于ArrayList的主要有如下几点:
1、Vector 线程安全
相较于ArrayList,Vector在很多影响线程安全的方法(如add、remove、get、size等)上面加了synchronized关键字保证线程安全,以add方法为例
2、扩容容量public synchronized boolean add(E e) { modCount++; ensureCapacityHelper(elementCount + 1); elementData[elementCount++] = e; return true; }
ArrayList中,扩容后的数量是原来的1.5倍(newCapacity = oldCapacity + (oldCapacity >> 1))
Vector 中提供了一个可以多传一个影响扩容数量参数的构造方法,如下:
通过传入的这个参数影响着扩容后的具体数量,相关逻辑如下:public Vector(int initialCapacity, int capacityIncrement) { super(); if (initialCapacity < 0) throw new IllegalArgumentException("Illegal Capacity: "+initialCapacity); this.elementData = new Object[initialCapacity]; this.capacityIncrement = capacityIncrement; }
private void grow(int minCapacity) { int oldCapacity = elementData.length; // 如果 capacityIncrement 是大于0的情况下,扩容后的大小是(原来容量数+capacityIncrement)的值 int newCapacity = oldCapacity + ((capacityIncrement > 0) ? capacityIncrement : oldCapacity); if (newCapacity - minCapacity < 0) newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); elementData = Arrays.copyOf(elementData, newCapacity); }
-
Stack
通过源码可以看到Stack是继承与Vector的:
由于Stack继承了Vector,所以它包含了Vector中全部的API,然后再内部又额外的封装了用于栈操作的一系列方法:public class Stack<E> extends Vector<E> { // 省略 }
push(E object)、pop()、peek()、empty()、search(Object o)
由于Stack是继承于Vector,并且自己内部封装的影响线程安全的方法都是有synchronized关键字修饰的,所以Stack也是线程安全的一个基于数组实现的容器
empty():实现原理就是判断数组的size是否为空
push(E object):
peek():// 入栈 并返回入栈元素 public E push(E item) { // addElement 逻辑即Vector 中的添加元素逻辑 addElement(item); return item; }
pop():// 返回最后一个元素 public synchronized E peek() { int len = size(); if (len == 0) throw new EmptyStackException(); return elementAt(len - 1); }
empty():// 返回最后一个元素并移除 public synchronized E pop() { E obj; int len = size(); obj = peek(); // 调用peek removeElementAt(len - 1); // 父类 Vector 中逻辑 return obj; }
search(Object o)// 判空 public boolean empty() { return size() == 0; }
// 查找元素o在栈的位置 public synchronized int search(Object o) { int i = lastIndexOf(o); // 父类 Vector 中的逻辑 if (i >= 0) { return size() - i; } return -1; }
-
CopyOnWriteArrayList
前面的Vector 是读写的时候都加锁的,其实在有些情况下,读的时候是不需要加锁操作的。CopyOnWrite可以很好的避免这个问题。
CopyOnWrite 读的时候不加锁,只有写操作的时候加锁
应用场景:高并发情况下 写操作明显少于都操作的场景下可以使用CopyOnWrite提高效率
原理:
在进行写操作的时候,会将原来的数据Copy一份,然后写操作在这个新的List上操作,读操作还是原来的list,写操作完成之后改变原来List的引用到当前新的List。
写操作部分代码如下:// CopyOnWriteArrayList 源码: public boolean add(E e) { // 18 之前和1.8之后的区别是 锁机制不同 // 1.8 之前是 synchronized 1.8之后是 ReentrantLock final ReentrantLock lock = this.lock; lock.lock(); try { Object[] elements = getArray(); int len = elements.length; // copy 原来的数组(len+1),将添加的元素追加到copy的这个list后面 Object[] newElements = Arrays.copyOf(elements, len + 1); newElements[len] = e; setArray(newElements); // 将原来数组的引用指向新的引用 return true; } finally { lock.unlock(); } } //读的时候不需要枷锁 //因为复制了一份,对原来的没影响
Set 接口
-
CopyOnWriteArraySet
CopyOnWriteArraySet 和 CopyOnWriteArrayList 的实现原理基本一致,无非就是set不允许重复的区别,在CopyOnWriteArraySet 多了一个校验元素是否存在的逻辑
部分原理参考上面CopyOnWriteArrayList
简单分析add方法和CopyOnWriteArrayList的不同之处:// CopyOnWriteArraySet 构造方法实际是创建了一个CopyOnWriteArrayList private final CopyOnWriteArrayList<E> al; public CopyOnWriteArraySet() { al = new CopyOnWriteArrayList<E>(); } // add 方法 public boolean add(E e) { return al.addIfAbsent(e); } // addIfAbsent 方法中添加数据之前进行了一次判断(indexOf) return indexOf(e, snapshot, 0, snapshot.length) >= 0 ? false :addIfAbsent(e, snapshot); /** * o:add 的元素 * elements:原数组 * index 传 0 * fence 数组长度 */ private static int indexOf(Object o, Object[] elements,int index, int fence) { if (o == null) { for (int i = index; i < fence; i++) if (elements[i] == null) return i; } else { for (int i = index; i < fence; i++) // 判断是否存在 if (o.equals(elements[i])) return i; } return -