主要内容:
- arraylist的本质?
- arraylist在添加元素的时候如何给数组进行合理的扩容?
- arraylist删除某个元素是怎么删除的?
- arraylist可以存null和重复值吗?
- arraylist的序列化机制
add
/**
* 构造一个初始容量为10的空列表。
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
如上所示,使用无参构造器创建的话会初始化初始容量为10的空列表。
/**
* 注释意思大概是这个数组在第一次使用add方法的时候才会被赋初始容量大小10
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
所以我们来看看add方法中做了什么:
public boolean add(E e) {
ensureCapacityInternal(size + 1); // 这个size默认是0
elementData[size++] = e;
return true;
}
在执行了ensureCapacityInternal(size + 1)之后就开始为先前的空数组elementData赋值了,那么猜测数据容量初始化肯定是在ensureCapacityInternal这个方法中完成的,我们进入这个方法:
/**
* 我们可以看到这里做了个if判断,毋庸置疑是true
*/
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
//minCapacity值是1,DEFAULT_CAPACITY值是10,这样写的意思
//就是当你初始化list时设置的长度(包括不写长度的情况下)小于
//10时,会将list最小容量设置为10
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
当需要的长度大于原来数组长度的时候就需要扩容了,相反的则不需要扩容
点击强制步入进入grow方法:
/**
* 增加容量,以确保它至少可以容纳
* 由最小容量参数指定的元素数量。
*
* @param minCapacity 所需的最小容量
*/
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
// 用位运算确保newCapacity始终大于oldCapacity,以1.5的速度扩容
int newCapacity = oldCapacity + (oldCapacity >> 1);
// 使得newCapacity始终大于或等于10
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
// 当新容量大于数组最大长度时,这个方法会在minCapacity
// 大于数组最大长度时返回Integer的最大值,小于就返回数组
// 最大长度
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity通常接近于size,所以这是一个优势
// 扩容之后空位补全为0
elementData = Arrays.copyOf(elementData, newCapacity);
}
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
//对minCapacity和MAX_ARRAY_SIZE进行比较
//若minCapacity大,将Integer.MAX_VALUE作为新数组的大小
//若MAX_ARRAY_SIZE大,将MAX_ARRAY_SIZE作为新数组的大小
//MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
remove
/**
* 从列表中删除指定元素的第一个匹配项,
* 如果它存在。如果列表不包含该元素,则它包含
* 不变
*
* @param o 元素将从此列表中删除(如果存在)
*/
public boolean remove(Object o) {
//因为arraylist可以存null
if (o == null) {
//遍历整个数组,拿到第一个值为null的索引
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
//拿到这个索引之后调用这个方法进行删除
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
/*
* 私人删除方法,跳过界限检查,不
* 返回删除的值。
*/
private void fastRemove(int index) {
modCount++;
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // 将数组中最后一个元素设置为null,交给GC来自动清除
}
可以看出arraylist删除数组中的某个元素其实本质就是移动数组,要移除的元素越在前面,需要移动的元素就越多,开销就越大
toArray
/**
* 会创建一个新的数组,你可以随意修改它而并不会导致原来的arraylist发生变动
*
* @return 包含列表中所有元素的数组
* 正确的序列
*/
public Object[] toArray() {
return Arrays.copyOf(elementData, size);
}
transient
//将不需要序列化的属性前添加关键字transient,序列化对象的时候,这个属性就不会被序列化。
transient Object[] elementData;
为什么在elementData上添加这个关键字呢?我们来看看arraylist的writeObject和readObject方法:
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException{
...
for (int i=0; i<size; i++) {
s.writeObject(elementData[i]);
}
...
}
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
elementData = EMPTY_ELEMENTDATA;
...
for (int i=0; i<size; i++) {
a[i] = s.readObject();
}
...
}
这个size是数组有实际意义数据的真实长度
为什么不直接用elementData来序列化,而采用上诉的方式来实现序列化呢?原因在于elementData是一个缓存数组,它通常会预留一些容量,等容量不足时再扩充容量,那么有些空间可能就没有实际存储元素,采用上诉的方式来实现序列化时,就可以保证只序列化实际存储的那些元素,而不是整个数组,从而节省空间和时间。