概述
ArrayList实现了List接口,底层采用数组存储,是一个泛型类,容器中可以放置任意类型的对象。可以自动扩容,其实就是进行内存重分配,申请一个更大长度的数组。它并不是线程安全的,必要时可以手动加锁或者使用Vector代替。
源码实现
底层的数据结构
/**
* The array buffer into which the elements of the ArrayList are stored.
* The capacity of the ArrayList is the length of this array buffer. Any
* empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
* will be expanded to DEFAULT_CAPACITY when the first element is added.
*/
transient Object[] elementData; // non-private to simplify nested class access
/**
* The size of the ArrayList (the number of elements it contains).
*
* @serial
*/
private int size;
诶,这里有个transient关键字诶:
在Java中,transient
关键字是一个修饰符,用于类的成员变量。当一个类的成员变量被声明为transient
时,它不会被序列化,这意味着这个变量的值在对象序列化过程中不会被保存,而且在反序列化时,这个变量的值会被自动设为其类型的默认值(例如,对于整数类型,默认值是0)。
transient
关键字主要用于以下场景:
- 控制序列化:当你不希望某个成员变量被序列化时,可以将其声明为
transient
。这通常用于那些不需要持久保存的变量,比如临时状态信息或者那些可以从其他持久化数据重新计算得到的值。 - 节约空间:通过排除某些不需要序列化的变量,可以减少序列化数据的大小,从而节约存储空间和网络带宽。
- 保护敏感信息:如果某个变量包含敏感信息(如密码、密钥等),将其声明为
transient
可以防止这些信息被序列化和存储。
自动扩容的实现
首先,我们得知道,new ArrayList<?>() 的默认数组大小是多少:
private static final int DEFAULT_CAPACITY = 10;
private static final Object[] EMPTY_ELEMENTDATA = {};
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
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);
}
}
/**
* Constructs an empty list with an initial capacity of ten.
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
很明显,大小为10(把英文翻译一下)。
什么情况下需要自动扩容呢?很明显,数组满的时候,我们还向其中添加元素。所以我们追踪一下add()方法:
private int size;
private void add(E e, Object[] elementData, int s) {
if (s == elementData.length)
elementData = grow();
elementData[s] = e;
size = s + 1;
}
public boolean add(E e) {
modCount++;
add(e, elementData, size);
return true;
}
现在,我们知道了,添加元素的时候,如果size属性的值等于底层数组的长度,那么就会触发扩容函数(grow()),我们一下里面有什么:
private Object[] grow(int minCapacity) {
int oldCapacity = elementData.length;
if (oldCapacity > 0 || elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
int newCapacity = ArraysSupport.newLength(oldCapacity,
minCapacity - oldCapacity, /* minimum growth */
oldCapacity >> 1 /* preferred growth */);
return elementData = Arrays.copyOf(elementData, newCapacity);
} else {
return elementData = new Object[Math.max(DEFAULT_CAPACITY, minCapacity)];
}
}
private Object[] grow() {
return grow(size + 1);
}
没事,看不懂也没关系,给你个概念上的东西,扩容大约50%。
这种扩容操作的代价还是蛮高的,所以在平时的开发中,如果我们能够预知所要保存的元素多少时,可以在构造容器实例的时候指定一个大小,避免频繁的扩容。或者还可以根据实际的业务需求,手动调用ensureCapacity方法来增加ArrayList实例的容量:
public void ensureCapacity(int minCapacity) {
if (minCapacity > elementData.length
&& !(elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
&& minCapacity <= DEFAULT_CAPACITY)) {
modCount++;
grow(minCapacity);
}
}
Fail-Fast 机制
Arraylist不是线程安全的,内部通过一个modCount属性记录修改的次数。在面对并发修改时,迭代器可以很快失败,避免将来可能产生的一些不确定性的风险。
2024.10.28
writeBy kaiven