三个变量的作用
private static final Object[] EMPTY_ELEMENTDATA = {};
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
transient Object[] elementData; // non-private to simplify nested class access
初始化的时候如果不指定长度,将会使用构造器 public ArrayList()
,这时将 DEFAULTCAPACITY_EMPTY_ELEMENTDATA
赋值给 elementData
,会创建一个大小为 10 的 Object 数组。也就是,当 elementData == 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);
}
}
这是如果等于 0 的话,这是 EMPTY_ELEMENTDATA
被赋值给 elementData
.
集合扩容是怎么扩的?
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
我们可以看到一句代码
int newCapacity = oldCapacity + (oldCapacity >> 1);
发现增长率为 50%
计算 HashCode
首先我们从 List
类中看出对 hasCode
的定义:这个方法主要是用于对比两个 list
类是否相等。所以当我们使用这个方法的时候,具体实现(AbstractList
)是遍历好所有里面的数据元素的。
public int hashCode() {
int hashCode = 1;
for (E e : this)
hashCode = 31*hashCode + (e==null ? 0 : e.hashCode());
return hashCode;
}
这一般来说,是用来做比较两个 ArrayList
是否相等的情况。
ArrayList 是如何 remove 元素的?
其实 ArrayList
的 remove
是通过将删除的元素设为 null
,然后等着 jvm
进行垃圾内存回收。
public E remove(int index) {
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index, numMoved);
elementData[--size] = null; // 清除等待虚拟机进行回收
return oldValue;
}
不仅仅是 remove
方法,还有 clear
。
关于 elementData 已经被 transient 修饰了为啥还可以被序列化?
其实这个问题很简单,我们直接看序列化的源码
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException{
int expectedModCount = modCount;
// 让 jvm 知道你要写入 non-transient 和 non-static 的数据
s.defaultWriteObject();
// 确定写入的个数
s.writeInt(size);
// 按照顺序序列化
for (int i=0; i<size; i++) {
s.writeObject(elementData[i]);
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
我们可以看出,其实被 transient
修饰的变量是告诉 jvm,在序列化 ArrayList
的时候,不要处理 elementData
,由我自己来处理这些数据。为什么这么做呢?这个节省空间有关。因为如果一个 ArrayList
定义了 100 个元素,但实际上只有 69 个元素。那么多出来的造成序列化浪费了。所以我们需要手动操作。
补充:在手动序列化的过程中,记得序列和反序列的字段顺序需要一致。例如你序列化了字段 a=1
,b=2
,c=3
。那么你读的时候也要按照这个顺序来。否则出现反序列数据错误。