目录
目录
提示:以下是本篇文章正文内容,下面案例可供参考
一、整体构架
ArrayList整体构架就是一个数组结构,比较简单,如下图:
图中展示的是长度为10的数组,从1开始计数,index表示数组的下标,从0开始计数,elementData表示数组本身,源码中除了这两个概念还有以下三个:
1.DEFAULT_CAPACITY 表示数组的初始大小,默认是 10,这个数字要记住; |
2.size 表示当前数组的大小,类型 int,没有使用 volatile 修饰,非线程安全的; |
3.modCount 统计当前数组被修改的版本次数,数组结构有变动,就会 +1。 |
二、常用方法源码解析
1.初始化
有三种方法初始化:无参数直接初始化、指定大小初始化、指定初始数据初始化,jdk1.8源码如下:
ArrayList 无参构造器初始化时,默认大小是空数组,并不是大家常说的 10,10 是在第一次add 的时候扩容的数组值。
1.1无参数初始化数组,默认值是空。但是如果是空的话岂不是不可以添加数据?其实不然,在进行.add操作的时候,算法会进行一系列计算最终得到minCapacity=10,后面会详细介绍。
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//无参数直接初始化,数组大小为空
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
1.2指定初始数据初始化时,我们发现一个这样子的注释 see 6260652,这是 Java 的一个
bug,意思是当给定集合内的元素不是 Object 类型时,我们会转化成 Object 的类型。
public ArrayList(Collection<? extends E> c) {
Object[] a = c.toArray();
//如果给定的集合有值
if ((size = a.length) != 0) {
if (c.getClass() == ArrayList.class) {
elementData = a;
//如果集合元素类型不是 Object 类型,我们会转成 Object
} else {
elementData = Arrays.copyOf(a, size, Object[].class);
}
} else {
// replace with empty array.给定集合(c)无值,则默认空数组
elementData = EMPTY_ELEMENTDATA;
}
}
2.新增和扩容实现(arrayList.add)
往数组里面添加元素,主要分为两步:判断是否需要扩容,如果需要执行扩容操作;直接赋值。
2、开始
/**
* 新增元素操作
*/
// eg1:第一次新增元素e="a1",
public boolean add(E e) {
/** 确定是否需要扩容,如果需要,则进行扩容操作*/
ensureCapacityInternal(size + 1); // Increments modCount!!
// eg1:size=0,elementData[0]="a1",然后a自增为1
elementData[size++] = e;
return true;
}
3、执行ensureCapaInternal(),第一次新增元素,所以size=0,minCapacity=size+1=1。
// eg1:第一次新增元素,所以size=0,则:minCapacity=size+1=1
private void ensureCapacityInternal(int minCapacity) {
// eg1:第一次新增元素,calculateCapacity方法返回值为DEFAULT_CAPACITY=10
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
4、执行判断calculateCapacity是否为空
5、为空返回minCapacity=10
// eg1:第一次新增元素,elementData={} minCapacity=1
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
// eg1:满足if判断,DEFAULT_CAPACITY=10
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
6、执行ensureExplicitCapacity(),进行操作计数。
// eg1:第一次新增元素,minCapacity=10
private void ensureExplicitCapacity(int minCapacity) {
// eg1: modCount++后,modCount=1
modCount++;
7、进行判断是否需要扩容,/** 如果所需的最小容量大于elementData数组(初始时候为空)的容量,则进行扩容操作 */
/** 如果所需的最小容量大于elementData数组的容量,则进行扩容操作 */
if (minCapacity - elementData.length > 0) { // eg1:10-0=10,满足扩容需求
// eg1:minCapacity=10
grow(minCapacity);
}
}
8、执行grow()方法,需要扩容获取当前元素数量,扩容后的长度=原来长度+原来长度/2,假设oldCapacity=10,那么10=1010,>>1=101=5,所以newcapacity=oldcapacity+(oldCapacity>>1)=15,若果扩容后的长度小于minCapacity,则=minCapacity。扩容完成之后,赋值是非常简单的,直接往数组上添加元素即可:elementData [size++] =e。也正是通过这种简单赋值,没有任何锁控制,所以这里的操作是线程不安全的,
// eg1:第一次新增元素,minCapacity=10,即:需要将elementData的0长度扩容为10长度。
private void grow(int minCapacity) {
/** 原有数组elementData的长度*/
int oldCapacity = elementData.length; // eg1:oldCapacity=0
/**
* A >> 1 等于 A/2
* eg: 3 >> 1 = 3/2 = 1
* 4 >> 1 = 4/2 = 2
* ------------------------
* A << 1 等于 A*2
* eg: 3 << 1 = 3*2 = 6
* 4 << 1 = 4*2 = 8
*
* 000100 >> 1 = 000010
* 000100 << 1 = 001000
*/
/** 新增oldCapacity的一半整数长度作为newCapacity的额外增长长度 */
int newCapacity = oldCapacity + (oldCapacity >> 1); // eg1:newCapacity=0+(0>>1)=0
/** 新的长度newCapacity依然无法满足需要的最小扩容量minCapacity,则新的扩容长度为minCapacity */
if (newCapacity - minCapacity < 0) {
// eg1:newCapacity=10
newCapacity = minCapacity;
}
/** 新的扩容长度newCapacity超出了最大的数组长度MAX_ARRAY_SIZE */
if (newCapacity - MAX_ARRAY_SIZE > 0) {
newCapacity = hugeCapacity(minCapacity);
}
/** 扩展数组长度为newCapacity,并且将旧数组中的元素赋值到新的数组中 */
// eg1:newCapacity=10, 扩容elementData的length=10
elementData = Arrays.copyOf(elementData, newCapacity);
}
3、remove根据值删除元素
新增的时候没有null进行较检,所以删除的时候也是允许删除null值。
找到值在数组中的位置,通过equals()方法进行比较是否相等,相等删除第一个元素。
public boolean remove(Object o) {
// 如果要删除的值是 null,找到第一个值是 null 的删除
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
// 如果要删除的值不为 null,找到第一个和要删除的值相等的删除
for (int index = 0; index < size; index++)
// 这里是根据 equals 来判断值相等的,相等后再根据索引位置进行删除
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
} r
eturn false;
}
上面代码已经找到要删除元素的索引位置了,下面代码是根据索引位置进行元素的删除:
p
rivate void fastRemove(int index) {
// 记录数组的结构要发生变动了
modCount++;
// numMoved 表示删除 index 位置的元素后,需要从 index 后移动多少个元素到前面去
// 减 1 的原因,是因为 size 从 1 开始算起,index 从 0开始算起
int numMoved = size - index - 1;
if (numMoved > 0)
// 从 index +1 位置开始被拷贝,拷贝的起始位置是 index,长度是 numMoved
System.arraycopy(elementData, index+1, elementData, index, numMoved);
//数组最后一个位置赋值 null,帮助 GC
elementData[--size] = null;
4、线程安全
ArrayList 有线程安全问题的本质,是因为 ArrayList 自身的 elementData、size、modConut,在进行各种操作时,都没有加锁,而且这些变量的类型并非是可见(volatile)的,所以如果多个线程对这些变量进行操作时,可能会有值被覆盖的情况。
使用Synchronized()加上锁实现线程安全。
public boolean add(E e) {
synchronized (mutex) {// synchronized 是一种轻量锁,mutex 表示一个当前 SynchronizedList
return c.add(e);
}
}
总结
注解应该比较详细,我们需要注意的四点是:
1、扩容的规则并不是翻倍,是原来容量大小 + 容量大小的一半,直白来说,扩容后的大小是原
来容量的 1.5 倍;
2、ArrayList 中的数组的最大值是 Integer.MAX_VALUE,超过这个值,JVM 就不会给数组分配
内存空间了。
3、新增时,并没有对值进行严格的校验,所以 ArrayList 是允许 null 值的。
从新增和扩容源码中,下面这点值得我们借鉴:
4、源码在扩容的时候,有数组大小溢出意识,就是说扩容后数组的大小下界不能小于 0,上界
不能大于 Integer 的最大值,这种意识我们可以学习。