少年阿涛 2020-04-11
关注【星辰学院】 http://xingchenxueyuan.com 更多知识和内容,一起打怪升级!
ArrayList 是基于数组实现的,支持快速随机访问。
数组的默认大小为 10。
存储结构如图:
添加元素时使用 ensureCapacityInternal() 方法来保证容量足够,如果不够时,需要使用 grow() 方法进行扩容,新容量的大小为 oldCapacity + (oldCapacity >> 1)
,也就是旧容量的 1.5 倍。
扩容操作需要调用 Arrays.copyOf()
把原数组整个复制到新数组中,这个操作代价很高,因此最好在创建 ArrayList 对象时就指定大概的容量大小,减少扩容操作的次数。
public boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; } private void ensureCapacityInternal(int minCapacity) { if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } ensureExplicitCapacity(minCapacity); } private void ensureExplicitCapacity(int minCapacity) { modCount++; // overflow-conscious code if (minCapacity - elementData.length > 0) grow(minCapacity); } private void grow(int minCapacity) { // overflow-conscious code int oldCapacity = elementData.length; int newCapacity = oldCapacity + (oldCapacity >> 1); if (newCapacity - minCapacity < 0) newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); // minCapacity is usually close to size, so this is a win: elementData = Arrays.copyOf(elementData, newCapacity); }
public E remove(int index) { rangeCheck(index); modCount++; E oldValue = elementData(index); int numMoved = size - index - 1; if (numMoved > 0) System.arraycopy(elementData, index+1, elementData, index, numMoved); elementData[--size] = null; // clear to let GC do its work return oldValue; }
* @param src the source array. 源数组 * @param srcPos starting position in the source array. 源数组的起始位置 * @param dest the destination array. 目标数组 * @param destPos starting position in the destination data. 目标数组的起始位置 * @param length the number of array elements to be copied. 复制的长度 public static native void arraycopy(Object src,int srcPos,Object dest, int destPos,int length)
public synchronized boolean add(E e) { modCount++; ensureCapacityHelper(elementCount + 1); elementData[elementCount++] = e; return true; } public synchronized E get(int index) { if (index >= elementCount) throw new ArrayIndexOutOfBoundsException(index); return elementData(index); }
写操作在一个复制的数组上进行,读操作还是在原始数组中进行,读写分离,互不影响。
写操作需要加锁,防止并发写入时导致写入数据丢失。
写操作结束之后需要把原始数组指向新的复制数组。
public boolean add(E e) { final ReentrantLock lock = this.lock; lock.lock(); try { Object[] elements = getArray(); int len = elements.length; Object[] newElements = Arrays.copyOf(elements, len + 1); newElements[len] = e; setArray(newElements); return true; } finally { lock.unlock(); } } final void setArray(Object[] a) { array = a; } @SuppressWarnings("unchecked") private E get(Object[] a, int index) { return (E) a[index]; }
CopyOnWriteArrayList 在写操作的同时允许读操作,大大提高了读操作的性能,因此很适合读多写少的应用场景。
但是 CopyOnWriteArrayList 有其缺陷:
所以 CopyOnWriteArrayList 不适合内存敏感以及对实时性要求很高的场景。
基于双向链表实现,使用 Node 存储链表节点信息。
private static class Node<E> { E item; Node<E> next; Node<E> prev; }
每个链表存储了 first 和 last 指针:
transient Node<E> first; transient Node<E> last;
结构图
ArrayList 基于动态数组实现,LinkedList 基于双向链表实现。ArrayList 和 LinkedList 的区别可以归结为数组和链表的区别:
关注【星辰学院】 http://xingchenxueyuan.com 更多知识和内容,一起打怪升级!