0
点赞
收藏
分享

微信扫一扫

菜鸟带你看源码——看不懂你打我ArrayList源码分析(基于java 8)


文章目录

  • ​​看源码并不难​​
  • ​​软件环境​​
  • ​​成员变量:​​
  • ​​构造方法​​
  • ​​核心方法​​
  • ​​get方法​​
  • ​​remove方法​​
  • ​​add方法​​
  • ​​结束​​

看源码并不难

如何学好编程?如何写出优质的代码?如何快速提高自己的编程能力?等等一系列的问题下面,我们通常都会看到一个相同的答案——看源码。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源码分析(基于java 8)_jdk1.8


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的核心内容就已经分析完了,怎么样?是不是觉得很简单,理解起来很轻松?如果本文有帮助到你,就点个赞以鼓励我继续跟大家一起分享更多更好的内容!


举报

相关推荐

0 条评论