想省力不看分析,可以直接去最下面看总结
JDK版本说明:1.8
1.存储方式:
采用数组的方式存储,并且使用了transient关键字(反序列化),目的是节省空间。因为定义数组是有长度的,比如ArrayList初始大小是10,若第一次只存了5个元素,且没有transient关键字修饰的话,会序列化整个数组,而不仅只是有效的5个元素,所以ArrayList使用transient关键字,实现了Serializable接口,并且定义了writeObject()和readObject()方法,实现自定义序列化。
transient Object[] elementData;
2.构造方法:
三种构造方法,实现如下
1.带int的构造方法:使用用户传入的值作为初始数组的大小
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
//使用用户传入的值作为初始数组的大小
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {//如果参数为0则返回一个空数组
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
2.无参构造:定义初始数组大小为0;
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
3.带集合的构造方法:这里面会有一个jdk的bug,这里不做讨论,正常情况也是定义数组的初始大小为0
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}
3.常见方法(主要讲ArrayList扩容机制)
add,addAll,remove,removeAll还有一个奇葩的retainAll,其他比较简单的就不列出来了。
3.1.add方法:
add方法有两种重载方式:
1.只有值,没有索引,这个方法首先会重新判断容量的大小,然后决定是否扩容,如果需要扩容则先扩容,然后将值存入,同时size索引++;如果不需要扩容则直接将值存入,同时size索引++。
具体判断是否扩容以及如何扩容,请阅读下面代码及代码中文注释,英文注释是源码,仍有问题请评论留言。
/*1.如果原来创建对象时的构造方法使用的时无参构造,且是第一次调用add方法,
则相当于elementData[0] = e;size++;
2.举例:第2次调用add方法,则相当于elementData[1] = e;size++;
3.举例:第11次调用add方法,elementData长度为10,先扩容,elementData=15,
然后elementData[10] = e;size++;
*/
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
//涉及的方法如下,主要是计算容器的大小是否能满足需要以及扩容
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
/*1.如果原来创建对象时的构造方法使用的时无参构造,且是第一次调用add方法,
则会返回Math.max(DEFAULT_CAPACITY, minCapacity),其中DEFAULT_CAPACITY为10.
2.举例:第2次调用add方法,elementData数组长度为10,返回minCapacity,值为2.
3.举例:第11次调用add方法,elementData数组长度为10,返回minCapacity,值为11.
*/
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
/*1.如果原来创建对象时的构造方法使用的时无参构造,且是第一次调用add方法,
则此时minCapacity =10,elementData.length=0,进入grow方法(ArrayList的第一次扩容)
2.举例:第2次调用add方法,elementData数组长度为10.此时minCapacity = size+1 = 2,结束
3.举例:第11次调用add方法,elementData数组长度为10,minCapacity=11
所以minCapacity - elementData.length > 0,进入grow方法(ArrayList的第二次扩容)
*/
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
/*1.如果原来创建对象时的构造方法使用的时无参构造,且是第一次调用add方法,
oldCapacity =0,newCapacity =0,经过判断执行newCapacity = minCapacity,
此时newCapacity=10 ,然后进入 copyOf方法后,得到的返回值应该长度为10的空数组,
最后赋值给elementData ,完成第一次扩容.
2.举例:第2次调用add方法时,未进入grow方法
3.举例:第11次调用add方法,minCapacity =11,oldCapacity =10,newCapacity =15,
因为oldCapacity + (oldCapacity >> 1) = 10 + 5(转换为二进制后右移一位,相当于java中,整数/2),
newCapacity - minCapacity = 15-11 > 0,执行copyOf方法,作用如下:
先创建一个长度为15的copy数组,然后把elementData中的内容全部赋值进copy数组中,
再返回copy数组,最后重新赋值给elementData数组,完成第二次扩容.
*/
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);
}
//加上数组的类型参数后进入重载的copyOf方法中
public static <T> T[] copyOf(T[] original, int newLength) {
return (T[]) copyOf(original, newLength, original.getClass());
}
/*首先判断数组类型是否为Object类型,如果是true,
则T[] copy =(T[]) new Object[newLength],即创建一个长度为10的数组,
然后调用 System.arraycopy方法,这是jdk的本地方法,具体作用接着往下看,
最后返回copy数组
*/
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
@SuppressWarnings("unchecked")
T[] copy = ((Object)newType == (Object)Object[].class)
? (T[]) new Object[newLength]
: (T[]) Array.newInstance(newType.getComponentType(), newLength);
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}
/* 方法含义:将src数组从srcPos位置开始复制length长度的内容,
* 粘贴到dest数组中,从destPos位置开始粘贴
* @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);
2.有值,有索引
public void add(int index, E element) {
//判断索引是否合法,注意:index=size的情况是被允许的,因为刚好可以衔接上原数组
rangeCheckForAdd(index);
//进行容量判断
ensureCapacityInternal(size + 1); // Increments modCount!!
/*已经经过容量判断,如需扩容会先扩容,因此不会产生越界问题,
将从index开始长度为size-index的内容复制到起始位置为index+1的后面。
*/
System.arraycopy(elementData, index, elementData, index + 1, size - index);
//将要插入的元素插入进index位置
elementData[index] = element;
size++;
}
//涉及方法如下,已经展示过的方法将不再展示:
//查看索引是否在正常范围
private void rangeCheckForAdd(int index) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
3.2addAll方法
作用:将某个集合中的全部元素加入当前ArrayList中
public boolean addAll(Collection<? extends E> c) {
Object[] a = c.toArray();
int numNew = a.length;
//判断和执行是否需要扩容
ensureCapacityInternal(size + numNew); // Increments modCount
//复制元素
System.arraycopy(a, 0, elementData, size, numNew);
size += numNew;
return numNew != 0;
}
public boolean addAll(int index, Collection<? extends E> c) {
//判断索引是否合法
rangeCheckForAdd(index);
Object[] a = c.toArray();
int numNew = a.length;
//判断和执行是否需要扩容
ensureCapacityInternal(size + numNew); // Increments modCount
int numMoved = size - index;
//复制元素,判断是验证index是否刚好等于size
if (numMoved > 0)
System.arraycopy(elementData, index, elementData, index + numNew,
numMoved);
System.arraycopy(a, 0, elementData, index, numNew);
size += numNew;
return numNew != 0;
}
3.3.remove方法:
remove方法同样有两种,源码比较简单,不再过多叙述:
1.带索引的remove
public E remove(int index) {
//判断索引是否合法
rangeCheck(index);
modCount++;
//取出要删除的元素
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved > 0)
//作用相当于把index后面的所有元素前移一位
System.arraycopy(elementData, index+1, elementData, index, numMoved);
//将最后一个元素置为null让GC来处理
elementData[--size] = null; // clear to let GC do its work
//返回要删除的元素
return oldValue;
}
2.带元素内容的remove
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;
}
//fastRemove比remove(int index)方法仅少了验证index和取出并返回元素内容的部分
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
}
3.4 removeAll方法:
作用:删掉当前集合与集合参数中所含元素的交集。有一个就返回true。
public boolean removeAll(Collection<?> c) {
//判断是否为null
Objects.requireNonNull(c);
return batchRemove(c, false);
}
private boolean batchRemove(Collection<?> c, boolean complement) {
final Object[] elementData = this.elementData;
int r = 0, w = 0;
boolean modified = false;
try {
for (; r < size; r++)
//将不符合删除条件的放入elementData中
if (c.contains(elementData[r]) == complement)
elementData[w++] = elementData[r];
} finally {
// Preserve behavioral compatibility with AbstractCollection,
// even if c.contains() throws.
if (r != size) {
System.arraycopy(elementData, r,
elementData, w,
size - r);
w += size - r;
}
if (w != size) {
//将符合删除条件的元素置为null,等待GC删除
// clear to let GC do its work
for (int i = w; i < size; i++)
elementData[i] = null;
modCount += size - w;
//将size设为不符合删除条件元素的总和
size = w;
modified = true;
}
}
return modified;
}
3.4 retainAll方法:
作用:原集合与参数集合取交集,如果有,原集合为交集,如果没有,则原集合设为空。(这方法真猛)
public boolean retainAll(Collection<?> c) {
Objects.requireNonNull(c);
return batchRemove(c, true);
}
private boolean batchRemove(Collection<?> c, boolean complement) {
final Object[] elementData = this.elementData;
int r = 0, w = 0;
boolean modified = false;
try {
for (; r < size; r++)
if (c.contains(elementData[r]) == complement)
elementData[w++] = elementData[r];
} finally {
// Preserve behavioral compatibility with AbstractCollection,
// even if c.contains() throws.
if (r != size) {
System.arraycopy(elementData, r,
elementData, w,
size - r);
w += size - r;
}
if (w != size) {
// clear to let GC do its work
for (int i = w; i < size; i++)
elementData[i] = null;
modCount += size - w;
size = w;
modified = true;
}
}
return modified;
}
总结
最后总结一下最核心的AraryList扩容机制吧,用无参构造方法举例
1.调用构造方法创建时,采用懒加载的方式,即AraryList的实例的初始size是0,等进行第一次add时,才会把size变成10,这是第一次扩容,从0到10。
2.第一次扩容之后,从第二次add到第十次,都不会有扩容操作,直到第11次add,即上一次插入元素后,数组已经存满了,那么下一次就会触发扩容机制。新数组的容量是newCapacity = oldCapacity + (oldCapacity >> 1),可能正好是原数组大小的1.5倍,或1.5倍左右,关键看原数组大小是不是偶数,这就是第二次扩容。
3.之后的每次扩容条件以及扩容机制就都和第二次一样了,相对HashMap还是很简单的。
最后,有些地方实在是分支太多,文字解释起来十分复杂,如果朋友们对哪个地方有所疑问,还请在评论区写下,我查阅后会尽快详细回复。