菜鸟带你看源码——看不懂你打我ArrayList源码分析(基于java 8)
看源码并不难
如何学好编程?如何写出优质的代码?如何快速提高自己的编程能力?等等一系列的问题下面,我们通常都会看到一个相同的答案——看源码。But,每次点开JDK的源代码,不到五分钟就又关上了。为啥呢?因为看不懂,因为太枯燥等等。总结成一句话就是——看源码是一件很痛苦的事情。
其实,之所以觉得看源码很痛苦,看不下去,不是因为源码太难。其实写JDK的那些人,都是非常优秀的程序员,而他们写的代码都是逻辑清晰、命名规范,基本上不写注释也能非常清楚的表达一段代码是干嘛的,何况源码里的注释也非常的丰富。所以根本原因并不是因为大神们写的代码晦涩难懂。
学习源码远没有大家以为的那么难,以ArrayList为例,首先看清楚它的结构:
- 成员变量
- 构造方法
- 核心方法
- 内部类
我们在研究一个类源码的时候,先关注这几块,成员变量没什么难度,根据命名和注释可以很轻松的知道它们的用途。
软件环境
- Windows 10
- JDK 1.8.0_192
- IDEA 2018.2
成员变量:
private static final long serialVersionUID = 8683452581122892189L;
//默认容量
private static final int DEFAULT_CAPACITY = 10;
//空对象数组,用于构造空对象数组
private static final Object[] EMPTY_ELEMENTDATA = {};
//空对象数组,用于默认构造方法初始化对象数组
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//存放数据的对象数组
transient Object[] elementData;
//List中元素的个数(跟容量不是一回事)
private int size;
构造方法
首先从构造方法入手,看看在创建一个ArrayList对象的时候都做了什么,这没什么难度。
//默认构造方法,创建一个空list对象
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
//带初始容量参数的构造方法,创建一个指定容量的list对象
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);
}
}
//带指定元素的构造方法,创建一个包含指定元素的list对象
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
this.elementData = EMPTY_ELEMENTDATA;
}
}
ArrayList的三个构造方法都非常简单明了,基本上有过编程经验的人,理解起来都不会有太大的困难。
核心方法
接下来就是看它的核心方法,跟我们关系最紧密的就三类方法:add、get和remove。ArrayList的get和remove方法的基本上跟它的构造方法是一个难度级别的,所以我们简单看一下:
get方法
//根据索引,获取对应的元素
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
//判断一下索引有没有下标越界
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
OK,正如我们上面所说,get方法是如此的简单和直接。
remove方法
remove方法也就比get方法复杂了一个构造方法的级别吧。
public E remove(int index) {
//检查下标是否越界
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved > 0)
//elementData源数组,index+1起始位置,elementData目标数组,index目标数组起始位置,numMoved需要复制的元素个数
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
//将末尾元素置空,等待被垃圾回收
elementData[--size] = null;
return oldValue;
}
一张图胜过千言万语,不废话看图:
ArrayList有多个remove方法,不过都大同小异,这里就不赘述了,看懂一个,剩下的也就都没问题了。
add方法
接下来我们来一起看看add方法:
public boolean add(E e) {
//添加元素前,确保容量够用
ensureCapacityInternal(size + 1);
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
//计算扩容量
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return 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) {
int oldCapacity = elementData.length;
//Java中移位操作是基于二进制进行运算的,所以向右移一位相当于除以二
//新的容量为原来的1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
//如果新容量小于最小容量,那么把最小容量作为新容量
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
//如果新容量大于 MAX_ARRAY_SIZE,执行hugeCapacity()比较 minCapacity 和 MAX_ARRAY_SIZE
//如果minCapacity大于MAX_ARRAY_SIZE,则新容量则为Integer.MAX_VALUE,否则,新容量大小则为MAX_ARRAY_SIZE
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();
//若minCapacity大,将Integer.MAX_VALUE作为新数组的大小
//若MAX_ARRAY_SIZE大,将MAX_ARRAY_SIZE作为新数组的大小
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8
下面我们分析一个完整的添加扩容的过程,当一个ArrayList对象list
添加第一个元素的时候,calculateCapacity()
方法得到minCapacity
的值为10。然后进入grow()
方法,此时newCapacity
小于MAX_ARRAY_SIZE
,list
被扩容到10,list.size() = 1
。
然后添加第2、3、4…直到第11个元素的时候,发现容量不够,需要再次扩容。由于每次扩容的新容量都为原来容量的1.5倍,所以进入grow()
后,newCapacity = 15
,list.size() = 11
。以此类推下去…
结束
OK,ArrayList的核心内容就已经分析完了,怎么样?是不是觉得很简单,理解起来很轻松?如果本文有帮助到你,就点个赞以鼓励我继续跟大家一起分享更多更好的内容!