序言:ArrayList是Java集合中的基础中的基础,也是面试中常问常考的点,今天咱们来点简单的,盘盘ArrayListjava
下面我凭本身的经验大概说一下如何看源码。面试
注:本篇文章全部分析,针对 jdk1.8版本设计模式
ArrayList 的本质是能够动态扩容的数组集合,是基于数组去实现的List类数组
ArrayList 的底层的数据结构是 Object[] 类型的数组,默认建立为空数组,采用懒加载的方式节省空间,每次采起1.5倍扩容的方式安全
ArrayList 是线程不安全的,线程安全的 List 参考 Collections.synchronizedList()
或者 CopyOnWriteArrayList数据结构
大体结构如上图,先考虑三个问题:dom
1.为何要继承 AbstractList,而让 AbstractList 去实现 List , 并非直接实现呢 ?源码分析
这里主要运用了模板方法设计模式,设想一下,jdk 开发做者 还有一个类要实现叫 LinkedList ,它也有何 ArrayList 相同功能的基础方法,可是因为是直接实现接口的方式,这个 类 LinkedList 就无法复用以前的方法了ui
2.ArrayList 实现了哪些其余接口,有什么用 ?this
RandomAccess 接口: 是一个标志接口, 代表实现这个这个接口的 List 集合是支持快速随机访问的。也就是说,实现了这个接口的集合是支持 快速随机访问 策略的,实现了此接口 for循环的迭代效率,会高于 Iterator
Cloneable 接口: 实现了该接口,就可使用 Object.Clone()
方法
Serializable 接口:实现序列号接口,代表该类能够被序列化
3. 为何AbstractList 已经实现了 List接口了,做为父类ArrayList 再去实现一遍呢 ?
做者说这是一个错误,一开始拍脑壳以为可能有用,后来没用到,但没什么影响就留到了如今
public class ArrayList extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable{ private static final long serialVersionUID = 8683452581122892189L; //默认初始容量为10 private static final int DEFAULT_CAPACITY = 10; //空的对象数组 private static final Object[] EMPTY_ELEMENTDATA = {}; //缺省空对象数组 private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; //集合容器,transient标识不被序列化 transient Object[] elementData; //容器实际元素长度 private int size; }
1. 使用无参构造器
/** * 默认无参构造器建立的时候其实只是个空数组,使用懒加载节省内存开销 * Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {} */ public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; }
2. 建立时,指定集合大小,如何一开始就能预估存储元素的size,推荐用此种方式
public ArrayList(int initialCapacity) { /** * 若是initialCapacity > 0,就申明一个这么大的数组 * 若是initialCapacity = 0,就用属性 EMPTY_ELEMENTDATA * Object[] EMPTY_ELEMENTDATA = {} * 若是initialCapacity < 0,不合法,抛异常 */ if (initialCapacity > 0) { this.elementData = new Object[initialCapacity]; } else if (initialCapacity == 0) { this.elementData = EMPTY_ELEMENTDATA; } else { throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); } }
能够看出有四个添加的方式,咱们挑选前两个经常使用方法讲解源码,其余基本差很少
1. boolean add(E) 默认在末尾插入元素
/** * 添加一个元素到list的末端 */ public boolean add(E e) { //确保内置的容量是否足够 ensureCapacityInternal(size + 1); //若是足够添加加元素放进数组中,size + 1 elementData[size++] = e; //返回成功 return true; } //minCapacity = size + 1 private void ensureCapacityInternal(int minCapacity) { //确认容器容量是否足够 ensureExplicitCapacity(calculateCapacity(elementData, minCapacity)); } //先计算容量大小,elementData是初始建立的集合容器,minCapacity= size + 1 private static int calculateCapacity(Object[] elementData, int minCapacity) { //判断初始容器是否等于空 if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { //为空,就拿size + 1和默认容量10比,谁大返回谁 return Math.max(DEFAULT_CAPACITY, minCapacity); } //不然返回size + 1 return minCapacity; } private void ensureExplicitCapacity(int minCapacity) { //列表结构被修改的次数,用户迭代时修改列表抛异常(ConcurrentModifyException) modCount++; /** * minCapacity 若是超过容器的长度,则进行扩容,两种场景 * 1.第一次添加元素,minCapacity = size + 1,size = 0,在上个方法中会 * 与默认的容器容量10进行大小比较最后为10,那么10 > 0,初始化容器 * 2.添加到第10个元素,上个方法则返回 size + 1 = 11,11 > 10,须要扩容 * 否则数组装不下了 */ if (minCapacity - elementData.length > 0) grow(minCapacity); } //容器的扩容方法 private void grow(int minCapacity) { //将扩充前的容器长度赋值给oldCapacity int oldCapacity = elementData.length; //扩容后的newCapacity = 1 + 1/2 = 1.5,因此为1.5倍扩容 int newCapacity = oldCapacity + (oldCapacity >> 1); /** * 适用于初始建立,elementData为空数组,length = 0 * oldCapacity = 0,但minCapacity = 10,此处则进行了初始化 */ if (newCapacity - minCapacity < 0) newCapacity = minCapacity; //若是新容量超过容量上限Integer.MAX_VALUE - 8,则会再尝试扩容 if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); //新的容量肯定后,只须要将旧元素所有拷贝进新数组就能够了 elementData = Arrays.copyOf(elementData, newCapacity); } //此方法主要是给集合赋最大容量用的 private static int hugeCapacity(int minCapacity) { if (minCapacity < 0) // overflow throw new OutOfMemoryError(); /** * 这一步无非再判断一次 * size + 1 > Integ er.MAX_VALUE - 8 ? * 若是大的话赋值 Integer.MAX_VALUE * 说明集合最大也就 Integer.MAX_VALUE的长度了 */ return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; }
2. void add(int,E);在指定位置添加元素
public void add(int index, E element) { //边界校验 插入的位置必须在0-size之间 rangeCheckForAdd(index); //不分析了上面有 ensureCapacityInternal(size + 1); //就是将你插入位置的后面的数组日后移一位 System.arraycopy(elementData, index, elementData, index + 1, size - index); //放入该位置 elementData[index] = element; //元素个数+1 size++; } private void rangeCheckForAdd(int index) { //插入位置过于离谱,反手给你一个越界异常 if (index > size || index < 0) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); } /** * src:源对象 * srcPos:源对象对象的起始位置 * dest:目标对象 * destPost:目标对象的起始位置 * length:从起始位置日后复制的长度 */ public static native void arraycopy(Object src, int srcPos, Object dest, int destPos, int length)
总结:
初始容量为10,1.5倍扩容,扩容最大值为 Integer.MAX_VALUE,
当咱们调用add方法时,实际方法的调用以下:
由于这几个都基本相似,咱们就选两个进行剖析
1. remove(int):经过删除指定位置上的元素
public E remove(int index) { //检测index,是否在索引范围内 rangeCheck(index); //列表结构被修改的次数,用户迭代时修改列表抛异常 modCount++; //取出要被删除的元素 E oldValue = elementData(index); //和添加的逻辑类似,算数组移动的位置 int numMoved = size - index - 1; //大于0说明不是尾部 if (numMoved > 0) //将删除位置后面的数组往删除位前移一格 System.arraycopy(elementData, index+1, elementData, index, numMoved); /* * 因为只是将数组前移盖住了一位,可是长度没变 * 若是删除位置不是末尾,此时末尾会有两个相同元素 * 须要删除末尾最后一个元素,size - 1 */ elementData[--size] = null; // clear to let GC do its work //返回要删除的元素值 return oldValue; } /* * 这个方法不过多解释了,可是笔者认为这小小的检查边界的方法细节写的不是很完美 * 这个地方没有主动考虑,index < 0的状况,异常是后续直接抛的,并非在这 * 后续版本彷佛对这个细节作了纠正 */ private void rangeCheck(int index) { if (index >= size) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); }
2. removeAll(collection c):批量删除元素
public boolean removeAll(Collection<?> c) { //判断非空 Objects.requireNonNull(c); //批量删除 return batchRemove(c, false); } private boolean batchRemove(Collection<?> c, boolean complement) { //用一个引用变量接收容器 final Object[] elementData = this.elementData; //r表明elementData循环次数,w表明不须要删除的元素个数 int r = 0, w = 0; //修改变更默认为false boolean modified = false; try { //遍历集合,遍历几回r就是几,直至r=size for (; r < size; r++) //查看当前集合元素是否不被包含,complement = false if (c.contains(elementData[r]) == complement) //不被包含,那我在当前容器从头开始放,w是下标 elementData[w++] = elementData[r]; } finally { /** * 若是r没有成功遍历完,抛异常了(这是个finally) * 理所固然将后面没有遍历完的元素归为不被包含 */ if (r != size) { System.arraycopy(elementData, r, elementData, w, size - r); w += size - r; } //存在不须要被删除的元素 if (w != size) { //遍历删除 for (int i = w; i < size; i++) elementData[i] = null; modCount += size - w; size = w; //有作过修改变更 modified = true; } } return modified; }
public E set(int index, E element) { //边界范围校验 rangeCheck(index); //取出旧值 E oldValue = elementData(index); //赋上新值 elementData[index] = element; //返回旧值 return oldValue; }
public E get(int index) { //边界范围校验 rangeCheck(index); //经过下标获取查找的元素 return elementData(index); }