ArrayList源码的总结
ArrayList 是一个数组队列,相当于 动态数组。与Java中的数组相比,它的容量能动态增长。它继承于AbstractList,实现了List, RandomAccess, Cloneable, java.io.Serializable这些接口。
ArrayList属性
private static final long serialVersionUID = 8683452581122892189L;
这个叫做序列化id,若有兴趣理解可参考这位大佬的见解:java 序列化ID的作用_ostracod_lvj的博客-CSDN博客_序列化id有什么用
// 默认初始的容量
private static final int DEFAULT_CAPACITY = 10;
// 定义一个空的数组实例以供其他需要用到空数组的地方调用
private static final Object[] EMPTY_ELEMENTDATA = {};
// 定义一个空数组,跟前面的区别就是这个空数组是用来判断ArrayList第一添加数据的时候要扩容多少。默认的构造器情况下返回这个空数组,而EMPTY_ELEMENTDATA是在用户指定容量为0时返回。
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
// 当前数据对象存放地方,被标记为transient,在对象被序列化的时候不会被序列化,它的容量就是这个数组的长度,同时只要是使用默认构造器(DEFAULTCAPACITY_EMPTY_ELEMENTDATA )第一次添加数据的时候容量扩容为DEFAULT_CAPACITY = 10
transient Object[] elementData; // non-private to simplify nested class access 非私有以简化嵌套类访问
// ArrayList的实际大小(数组包含的元素个数/实际数据的数量)默认为0
private int size;
// 要分配的数组的最大大小
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
// Integer.MAX_VALUE:一个常量,容纳int类型的最大值2^31-1
// 为什么减8:因为某些VM会在数组中保留一些头字,尝试分配这个最大存储容量,可能会导致array容量大于VM的limit,最终导致OutOfMemoryError
ArrayList构造方法
-
无参构造
// 构造一个初始容量为10的空列表 public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; }
-
int类型的有参构造
// 构造一个具有指定初始容量的空列表。 // 参数:initialCapacity—列表的初始容量 // 抛出:IllegalArgumentException -如果指定的初始容量为负值 public ArrayList(int initialCapacity) { if (initialCapacity > 0) { this.elementData = new Object[initialCapacity]; } else if (initialCapacity == 0) { this.elementData = EMPTY_ELEMENTDATA; } else { throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); } }
-
Collection对象的有参构造
// 构造一个包含指定集合元素的列表,按照集合的迭代器返回元素的顺序排列。 // 参数:c -将其元素放入列表中的集合 // 抛出: // NullPointerException -如果指定的集合为空 // NullPointerException -如果指定的集合为空 public ArrayList(Collection<? extends E> c) { Object[] a = c.toArray(); if ((size = a.length) != 0) { if (c.getClass() == ArrayList.class) { elementData = a; } else { elementData = Arrays.copyOf(a, size, Object[].class); } } else { // replace with empty array. elementData = EMPTY_ELEMENTDATA; } }
ArrayList其他方法
因为在所有添加数据的操作上面都要需要判断当前数组容量是否足以容纳新的数据
所以在总结这个前必须先了解一个机制:ArrayList扩容机制(相关扩容方法)
能力有限在此参考文章:
-
扩容方法
// 判断当前数组是否是默认构造方法生成的空数组,如果是的话minCapacity=10反之则根据原来的值传入下一个方法去完成下一步的扩容判断 public void ensureCapacity(int minCapacity) { int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) // any size if not default element table ? 0 // larger than default for default empty table. It's already // supposed to be at default size. : DEFAULT_CAPACITY; if (minCapacity > minExpand) { ensureExplicitCapacity(minCapacity); } } // minCapacity表示修改后的数组容量,minCapacity = size + 1 private static int calculateCapacity(Object[] elementData, int minCapacity) { if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { return Math.max(DEFAULT_CAPACITY, minCapacity); } return minCapacity; //可以看到如果是使用了空数组EMPTY_ELEMENTDATA话,那么不会返回默认的初始容量 } //判断看看是否需要扩容 private void ensureCapacityInternal(int minCapacity) { ensureExplicitCapacity(calculateCapacity(elementData, minCapacity)); } //判断当前ArrayList是否需要进行扩容 private void ensureExplicitCapacity(int minCapacity) { //快速报错机制(详细内容参考第一篇引用文章) modCount++; // overflow-conscious code if (minCapacity - elementData.length > 0) grow(minCapacity); } // 如果elementData元素是空的,就是第一次添加元素,minCapacity=size+1,其实就是等于1,空的数组没有长度就存放不了,所以就将minCapacity变成10,也就是默认容量的大小。也就是elementData数组的此时需要的最小容量为变10了,此时ensureExplicitCapacity()方法中的minCapacity就是10。到此只是说elementData数组需要的容量至少是10,还没有改变elementData的大小。 // 如果elementData数组中的元素不是空的,那么它此时需要的最小容量就是原先的数组长度加1,minCapacity代表着elementData中元素增加之后的实际数据个数。 // 要始终记住minCapacity = size+1,在ensureExplicitCapacity()方法中,首先操作数自增1,再把需要的最小空间容量与数组当前实际长度进行比较: // 如果elementData中的元素是空的,它现在需要的容量是10,但是elementData.length为0,所以要扩容。 // 如果elementData数组中的元素不是空的,若它添加一个元素后需要的容量比原数组长度大,就需要扩容,否则就不需要扩容 // ArrayList扩容的核心方法,此方法用来决定扩容量 // 增加容量以确保它至少可以容纳最小容量参数指定的元素数量 // 参数: minCapacity - 所需的最小容量 private void grow(int minCapacity) { // overflow-conscious code // 获取当前数组的容量 int oldCapacity = elementData.length; // 扩容。新的容量=当前容量+当前容量/2.即将当前容量增加一半(当前容量增加1.5倍) int newCapacity = oldCapacity + (oldCapacity >> 1); if (newCapacity - minCapacity < 0) newCapacity = minCapacity;//将扩容后的容量再次扩容为想要的最小容量 // elementData就空数组的时候,length=0,那么oldCapacity=0,newCapacity=0,在这里就是真正的初始化elementData的大小了,就是为10. if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); // minCapacity is usually close to size, so this is a win: // 新的容量大小已经确定好了,就copy数组,改变容量大小。 // copyof(原数组,新的数组长度) elementData = Arrays.copyOf(elementData, newCapacity); } // 扩容量大于最大数组长度时进入此方法 private static int hugeCapacity(int minCapacity) { //如果minCapacity<0,抛出异常 if (minCapacity < 0) // overflow throw new OutOfMemoryError(); //如果想要的容量大于MAX_ARRAY_SIZE,则分配Integer.MAX_VALUE,否则分配MAX_ARRAY_SIZE return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; }
-
size()方法等其他实用方法
// 返回该列表元素个数 public int size() { return size; } // 判断列表是否为空 public boolean isEmpty() { return size == 0; } // 返回指定元素的首个索引,如果找不到则返回-1 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; } // 如果列表中包含指定的元素,则返回true public boolean contains(Object o) { return indexOf(o) >= 0; } // 返回列表中指定元素最后一次出现的索引,如果找不到则返回-1 public int lastIndexOf(Object o) { if (o == null) { for (int i = size-1; i >= 0; i--) if (elementData[i]==null) return i; } else { for (int i = size-1; i >= 0; i--) if (o.equals(elementData[i])) return i; } return -1; } // 克隆目标集合,但是这里需要注意的是,克隆是浅拷贝的,如果目标集合的内存存储元素变量,这里也会改变 public Object clone() { try { ArrayList<?> v = (ArrayList<?>) super.clone(); v.elementData = Arrays.copyOf(elementData, size); v.modCount = 0; return v; } catch (CloneNotSupportedException e) { // this shouldn't happen, since we are Cloneable throw new InternalError(e); } } // 返回一个数组,该数组包含列表中的所有元素 public Object[] toArray() { return Arrays.copyOf(elementData, size); } // 这里需要注意的是,前者的返回类型是Object,后者返回类型是泛类 public <T> T[] toArray(T[] a) { if (a.length < size) // Make a new array of a's runtime type, but my contents: return (T[]) Arrays.copyOf(elementData, size, a.getClass()); System.arraycopy(elementData, 0, a, 0, size); if (a.length > size) a[size] = null; return a; } // ...
ArrayList主要方法
-
get()方法
// 返回此列表中指定位置的元素。 // 参数:index - 要返回的元素的索引 // 返回:此列表中指定位置的元素 // 抛出:IndexOutOfBoundsException - public E get(int index) { rangeCheck(index);//越界检查 return elementData(index);//返回索引为index的元素 }
-
set()方法
// 将此列表中指定位置的元素替换为指定元素。 // 参数: index - 要替换的元素的索引 element - 要存储在指定位置的元素 // 返回: 之前在指定位置的元素 抛出:IndexOutOfBoundsException - public E set(int index, E element) { rangeCheck(index);//检查索引是否越界。如果参数指定索引index>=size,抛出一个越界异常 //记录被替换的元素(旧值) E oldValue = elementData(index); elementData[index] = element;//替换元素(新值) return oldValue;//返回被替换的元素 }
-
add()方法
// 将指定元素附加到此列表的末尾。 // 参数:e - 要附加到此列表的元素 // 返回:true(由 Collection.add 指定) public boolean add(E e) { //确认list容量,如果不够,容量加1。注意:只加1,保证资源不被浪费 ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e;//将元素e放在size的位置上,并且size++ return true; } // 在此列表中的指定位置插入指定元素。将当前位于该位置的元素(如果有)和任何后续元素向右移动(将其索引加一)。 // 参数: index - 要插入指定元素的索引 element - 要插入的元素 // 抛出:IndexOutOfBoundsException - public void add(int index, E element) { rangeCheckForAdd(index);//越界检查 //确认list容量,如果不够,容量加1。注意:只加1,保证资源不被浪费 ensureCapacityInternal(size + 1); // Increments modCount!! // 对数组进行复制处理,目的就是空出index的位置插入element,并将index后的元素位移一个位置 // 在插入元素之前,要先将index之后的元素都往后移一位 // arraycopy(原数组,源数组中的起始位置,目标数组,目标数据中的起始位置,要复制的数组元素的数量) System.arraycopy(elementData, index, elementData, index + 1, size - index); elementData[index] = element;// 将指定的index位置赋值为element size++;// 实际容量+1 }
-
remove()方法
// 移除此列表中指定位置的元素。将任何后续元素向左移动(从它们的索引中减去 1)。 // 参数:index - 要删除的元素的索引 // 返回:从列表中删除的元素 // 抛出:IndexOutOfBoundsException - public E remove(int index) { rangeCheck(index);//检查索引是否越界。如果参数指定索引index>=size,抛出一个越界异常 modCount++;// 结构性修改次数+1 E oldValue = elementData(index);// 记录索引处的元素 int numMoved = size - index - 1;// 删除指定元素后,需要左移的元素个数 // 如果有需要左移的元素,就移动(移动后,该删除的元素就已经被覆盖了) if (numMoved > 0) System.arraycopy(elementData, index+1, elementData, index, numMoved); // size减一,然后将索引为size-1处的元素置为null。为了让GC起作用,必须显式的为最后一个位置赋null值 elementData[--size] = null; // clear to let GC do its work // 返回被删除的元素 return oldValue; } // 从此列表中删除第一次出现的指定元素(如果存在)。如果列表不包含该元素,则它不变。 // 参数:o - 要从此列表中删除的元素,如果存在 // 返回:如果此列表包含指定的元素,则为 true public boolean remove(Object o) { if (o == null) { for (int index = 0; index < size; index++) if (elementData[index] == null) { fastRemove(index); return true; } } else { for (int index = 0; index < size; index++) if (o.equals(elementData[index])) { fastRemove(index); return true; } } return false; } // 私有的remove方法,该方法跳过边界检查,并且不返回已删除的值。 private void fastRemove(int index) { modCount++; 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 }
-
clear()方法
// 从此列表中删除所有元素。此调用返回后,列表将为空。 public void clear() { modCount++; // clear to let GC do its work for (int i = 0; i < size; i++) elementData[i] = null; size = 0; }
小结:
- ArrayList可以存放null。
- ArrayList本质上就是一个 elementData数组。
- ArrayList区别于数组的地方在于能够自动扩展大小,其中关键的方法就是 gorw()方法。
- ArrayList中clear是删除集合中的所有元素。
- ArrayList由于本质是数组,所以它在数据的查询方面会很快,而在插入删除这些方面,性能下降很多,有移动很多数据才能达到应有的效果。