ArrayList 做为最经常使用的容器之一,一般用来存储一系列的数据对象,O(1)级别的数据读写java
查看源码,其内部定义的成员变量数组
// 默认数组容量 private static final int DEFAULT_CAPACITY = 10; // 静态成员,建立一个空的ArrayList时,内部数组实际使用这个 // 避免每次建立一个ArrayList对象,都要新建立一个对象数组 private static final Object[] EMPTY_ELEMENTDATA = {}; private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; // 实际保存数据的数组 transient Object[] elementData; // non-private to simplify nested class access private int size;
所以ArrayList的底层数据模型比较清晰,就是一个数组,默认初始容量为10安全
由于底层的数据结构为数组,因此根据
index
查询元素是常量级别开销,等同于获取数组中所索引为index处的元素微信所以须要关注的就是新增一个元素,若数组容量不够,如何进行扩容数据结构
删除一个元素,数组的连续性又是如何保障并发
获取List中某索引处的值,实现逻辑比较简单,以下ide
public E get(int index) { // 判断是否数组越界 rangeCheck(index); // 获取数组中的元素 return elementData(index); } private void rangeCheck(int index) { if (index >= size) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); } E elementData(int index) { return (E) elementData[index]; }
另外一个比较常见的读取接口就是contain
和indexOf
两个接口,用于判断列表中是否包含某个元素or某个元素在数组中的索引this
若让咱们本身来设计上面两个接口,多半是遍历数组,依次判断每一个元素,是否知足要求线程
JDK实际实现代码以下设计
public boolean contains(Object o) { return indexOf(o) >= 0; } public int indexOf(Object o) { if (o == null) { for (int i = 0; i < size; i++) if (elementData[i]==null) return i; } else { for (int i = 0; i < size; i++) if (o.equals(elementData[i])) return i; } return -1; }
从具体实现,能够注意一下几点
indexOf
返回的是第一个匹配的位置indexOf
返回-1表示不存在在添加元素以前,先看删除元素的接口实现,由于不涉及到动态扩容问题, 在分析中考虑下面几点
- 删除中间的元素,是否会形成后续的数组迁移
- 删除最后一个元素,是否会形成重排(仍是直接size-1便可)
首先看删除指定索引处的值
public E remove(int index) { // 数组越界判断 rangeCheck(index); modCount++; E oldValue = elementData(index); int numMoved = size - index - 1; if (numMoved > 0) { // 若是移动不是最后一个则须要数组拷贝 // native 方法实现数组拷贝 System.arraycopy(elementData, index+1, elementData, index, numMoved); } // 消灭最后一个元素 elementData[--size] = null; // clear to let GC do its work return oldValue; }
从源码解决上面两个问题
结合删除的逻辑,新增元素逻辑应该比较清晰,将添加索引处及以后的元素,总体后移一位,而后赋值新的值; 须要注意扩容的机制
添加一个元素的实现
public boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; } public void add(int index, E element) { // 判断索引是否越界 rangeCheckForAdd(index); // 扩容 ensureCapacityInternal(size + 1); // Increments modCount!! // 数组拷贝 System.arraycopy(elementData, index, elementData, index + 1, size - index); // 设置值 elementData[index] = element; size++; }
扩容的逻辑以下
private void ensureCapacityInternal(int minCapacity) { if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } ensureExplicitCapacity(minCapacity); } private void ensureExplicitCapacity(int minCapacity) { modCount++; if (minCapacity - elementData.length > 0) { // 当前的数组容量,已经超过数组长度 grow(minCapacity); } } private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; private void grow(int minCapacity) { // overflow-conscious code int oldCapacity = elementData.length; // 扩容原则: // 新增原来容量的一半,即变为以前容量的 1.5倍 // 若是上面容量依然不够,则选择扩容到刚好容下全部元素的容量 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); }
针对上面的逻辑进行小结:
add(index,object)
方法,会形成不必的数组迁移调用插入删除示意图
容器基本上都是实现了
Iterable
接口,因此遍历则主要是依据迭代器的next()
方法来实现
List的遍历,说白了就是数组的遍历,实现逻辑比较简单,惟一有意思的就是并发修改抛异常的问题
先看下迭代器类
private class Itr implements Iterator<E> { int cursor; // index of next element to return int lastRet = -1; // index of last element returned; -1 if no such int expectedModCount = modCount; public boolean hasNext() { return cursor != size; } @SuppressWarnings("unchecked") public E next() { // 下面方法确保在遍历过程当中,如有其余线程修改了列表的内容,则抛异常 checkForComodification(); int i = cursor; if (i >= size) throw new NoSuchElementException(); Object[] elementData = ArrayList.this.elementData; if (i >= elementData.length) throw new ConcurrentModificationException(); // 遍历的实际逻辑,就是索引的递增 cursor = i + 1; return (E) elementData[lastRet = i]; } public void remove() { // ... } @Override @SuppressWarnings("unchecked") public void forEachRemaining(Consumer<? super E> consumer) { // } final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); } }