源码学习day01(ArrayList)
根据学习计划,我们现阶段已将开始学习源码,因为我们数据专业已经学过了List的数据结构相关的一些知识,所以我就从ArrayList开始入手,下面是我在看Array源码时遇到的一些问题。
首先为啥要实现那个么多接口呢
我们可以看到,ArrayList除了实现了基本的List接口,还额外的实现了RandomAccess,Cloneable,Serializable, 以及继承了AbstractList抽象类
所以,我就在想为什么要多实现以及继承这几个类或者接口呢?
我们追进去List接口可以看到,里面都是些集合的基本操作方法,
那么AbstractList类呢
里面也是对与集合的基本操作,这就有导致我出现了一个新的问题,为什么要同时继承和实现AbstractList抽象类和List接口呢?经过不断地百度,我觉得我找到了一个我认为合理的解释,ArrayList实现List接口,是因为他要有List必有得一些特征属性,而继承AbstractList抽象类,因为有些方法没有必要去重写那就像size(),所以像这类实现方法是通用的,就可以在一个抽象类中统一实现,然后将add()等自由化程度较高地方法交给每个实现类自己去实现
那么randomAccess得作用又是什么呢?
追进去后发现竟然什么都没有,离谱啊,官方解释是只要List实现这个接口,就能支持快速随机访问,那么什么时快速随机访问呢,这就要进行一下比较了,
我们可以看一下LinkedList的源码
可以看到LinkedList并没有去实现randomAccess接口,然后我们经过一些测试就会发现LinkedList的迭代相比于for循环要快,而ArrayList的for循环则要比迭代快,实现RandomAccess接口的List可以通过for循环来遍历数据比使用iterator遍历数据更高效,未实现RandomAccess接口的List可以通过iterator遍历数据比使用for循环来遍历数据更高效。
那么cloneable的作用呢?
只有实现了这个接口才能调用Object.clone()方法,来对ArrayList进行克隆,不然就会抛出CloneNotSupportedException。
至于Serializable接口,只要想将对象进行序列化就必须要实现这个接口
接下来就要开始源码了
首先就是相关的属性
//序列化的序号,用来判断序列化与反序列化方式是否正确
private static final long serialVersionUID = 8683452581122892189L;
//默认数组的大小设置
private static final int DEFAULT_CAPACITY = 10;
/**
* 大小为0的数组,当调用有参构造创建初始化容量为0的ArrayList时赋值给elementData
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
/**
* 大小为0的数组,当调用无参构造创建ArrayList时赋值给elementData
* 该数组区别于EMPTY_ELEMENTDATA是为了在第一次添加元素时判断如何扩容
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//数组
transient Object[] elementData;
//元素的多少
private int size;
我们可以看到这里面有两个空数组,EMPTY_ELEMENTDATA 的空数组,是在调用有参构造器,当输入参数为零的时候调用的,默认参数大小就是0,而DEFAULTCAPACITY_EMPTY_ELEMENTDATA是在无参构造器中调用的,并且在添加元素是的默认大小为10
//初始化数组对象
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);
}
}
//实例化对象, 默认空数组
//默认初始化容量为10,但是在添加元素之前容量一直为0
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
//实例化对象,可添加集合参数
public ArrayList(Collection<? extends E> c) {
//首先将集合转化为数组
Object[] a = c.toArray();
//判断数组的大小 如果数组的大小为0的话,直接赋值空数组
if ((size = a.length) != 0) {
//c.toArray()返回的类型可能不是Object类型
if (c.getClass() == ArrayList.class) {
elementData = a;
} else {
elementData = Arrays.copyOf(a, size, Object[].class);
}
} else {
// 传入的集合为空集合
elementData = EMPTY_ELEMENTDATA;
}
}
这是一个无参构造器和两个有参的构造器
如何扩容以及看这段代码是遇到的问题
这是添加元素的方法(感觉可以更直观的理解扩容)
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
这是扩容相关的代码
//对数组进行扩容
public void ensureCapacity(int minCapacity) {
//最小的扩展策略
//如果元素数组不为默认的空,则 minExpand 的值为0,反之值为10
int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
? 0
: DEFAULT_CAPACITY;
// 如果最小容量大于已有的最大容量
if (minCapacity > minExpand) {
ensureExplicitCapacity(minCapacity);
}
}
/**
* 计算最小扩容量(被调用)
*/
private static int calculateCapacity(Object[] elementData, int minCapacity) {
// 如果元素数组为默认的空
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
// 获取“默认的容量”和“传入参数 minCapacity ”两者之间的最大值
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
/**
* 得到最小扩容量
*/
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
/**
* 判断是否需要扩容
*/
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// 如果最小容量比数组的长度还大
if (minCapacity - elementData.length > 0)
// 就调用grow方法进行扩容
grow(minCapacity);
}
//要分配的最大数组大小
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
// 扩容的核心方法
private void grow(int minCapacity) {
//将当前元素的数组长度定义为 oldCapacity
int oldCapacity = elementData.length;
//定义扩容后的数组长度 为老数组长度的1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
//然后继续检查新容量是否大于最小容量, 若还小于就把最小需要容量当作数组的新容量
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
// 再检查新容量是否超出了ArrayList 所定义的最大容量
// 若超出,则调用hugeCapacity()
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
}
/**
*比较minCapacity 和 MAX_ARRAY_SIZE的大小
*/
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
首先如果使用的是无参构造器,调用添加元素的方法,会进入到ensureCapacityInternal()方法中去,size默认初始值为0,传进去的参数是1,
接着进calculateCapacity()方法,
注意这时候我们用的是无参构造器创建的是DEFAULTCAPACITY_EMPTY_ELEMENTDATA空数组,会进入if中,返回值就是10,然后进入ensureExplicitCapacity()方法
这里我们看到一个modCount,然后我有多想了,这玩意儿干啥用的,一会解释,先说正事。
minCapacity 的值是10,elementData数组的长度也是10,所以不用扩容,那要是再添加元素呢?size值会自增,也就是minCapacity 的值,直到size值大于
elementData的长度,就会到扩容的核心
private void grow(int minCapacity) {
//将当前元素的数组长度定义为 oldCapacity
int oldCapacity = elementData.length;
//定义扩容后的数组长度 为老数组长度的1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
//然后继续检查新容量是否大于最小容量, 若还小于就把最小需要容量当作数组的新容量
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
// 再检查新容量是否超出了ArrayList 所定义的最大容量
// 若超出,则调用hugeCapacity()
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
}
如此反复,这里就又有了一个问题,为什么扩容的倍数是1.5,而不是1.2,不是2呢,百度到的都是一些很笼统的答案, 因为 1.2 未免扩容空间太小 , 而 2 倍则扩充的空间太大。这个问题先保留。
如果是有参构造器呢,那就直接从第一个元素就开始扩容,前4次的扩容都是每次多扩容1个长度,因为毕竟有个小数
最后之前的保留问题modCount的作用是什么呢
经过观察我们会发现,再涉及到对列表结构修改时,modCount就会自增,然后我们接着寻找
我们可以看到再涉及到列表的保存以及迭代时都提到了modCount,并且都有一个变量来引用modCount在方法中也会对modCount和expectedModCount
进行判断,如果不相等就会抛出ConcurrentModificationException()异常,
由此,我们不难推断出,当ArrayList进行迭代或者保存时,如果另外有一个操作去修改list集合,就会抛出ConcurrentModificationException(),避免一下不必要的意外。
以上就是在目前能力之下对所看ArrayList部分源码的理解,可能不对,还望指正。
配个图吧