菜鸟带你看源码——看不懂你打我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删除元素原理图
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_SIZElist被扩容到10,list.size() = 1

然后添加第2、3、4…直到第11个元素的时候,发现容量不够,需要再次扩容。由于每次扩容的新容量都为原来容量的1.5倍,所以进入grow()后,newCapacity = 15list.size() = 11。以此类推下去…

结束

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

刘水镜 CSDN认证博客专家 Spring Boot 架构 Java
【欢迎阅读我的最新专栏——《SpringBoot趣味私房课》https://www.imooc.com/read/75】【欢迎加入「开发者」技术交流群:168965372】【群链接:https://jq.qq.com/?_wv=1027&k=Inxd5Aa9】
已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 猿与汪的秘密 设计师:上身试试 返回首页