1.数据结构
底层使用Object类型的数组实现,线程不安全,添加元素时如果内存已满则会开辟新空间,将原数组copy过去。
transient Object[] elementData; public boolean add(E var1) { this.ensureCapacityInternal(this.size + 1); this.elementData[this.size++] = var1; return true; }
2.初始化
2.1.默认构造
默认构造是将elementData的引用指向一个final类型的Object[],这个final类型的Object[]在声明时指向的是一个空数组,因此采用默认构造实例化出来的ArrayList对象底层的elementData是没有容量的,因此第一次add就需要扩容。
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = new Object[0]; public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; }
这样设计的目的是节约内存,采用默认构造实例化的对象,在没有用到的时,所有数组都指向一块内存,只有用到时才从新分配。
2.2.带参构造
带参构造才会实例化出一个指定大小的elementData。
3.扩容
每次add时都会调用ensureCapacityInternal方法判断当前数组容量够不够,不够则扩容。
3.扩容
每次add时都会调用ensureCapacityInternal方法判断当前数组容量够不够,不够则扩容。
3.1.判断需要多少容量
这一步主要是判断如果是调用默认构造实例化的数组,需要扩容的大小是多少。
如果是采用默认构造实例化出的ArrayList对象,
elementData指向的是DEFAULTCAPACITY_EMPTY_ELEMENTDATA,空间大小为0,
如果需要的空间数量不超过DEFAULT_CAPACITY(即10个),则扩容出10个空间即可。
如果需要的空间数量超过DEFAULT_CAPACITY(10个),则扩容出实际需要的空间数量。
3.2.判断是否需要扩容
判断需要的容量是否超过当前数组的容量如果超过则扩容
3.3.扩容
新建一个新数组,长度为老数组的1.5倍(老数组长度+老数组长度右移1位(老数组长度/2^1)),然后将老数组中的元素copy到新数组中。
4.遍历
ArrayList的遍历器(iterator)以内部类的方式实现,实现Iterator接口
三个成员变量:
cursor:游标,用于遍历
lastRet:
expectedModCount:操作数,用来确认数据是否还有一致性
fail-fast:
为了保证遍历时数据的一致性,ArrayList的iterator有一个fail-fast机制,
即使用iterator遍历期间,只允许通过iterator来对该对象中的数据进行操作。
而不允许在使用iterator遍历期间,通过其他手段修改对象中的数据。
5.拷贝
ArrayList实现了Cloneable接口,默认实现浅拷贝,即栈上内容分裂,堆上内容公用。想实现深拷贝,需要手动对成员变量进行一次clone。
6.序列化
ArrayList实现了Serializable接口,
并且重写了writeObject()、readObject(),
elementData[]空间很可能没有全部用完,没用到的位置为空,为了避免浪费内存,所以拷贝时只需要将elementData中的非空元素序列化走。
重写的序列化读写方法主要是为了实现上面的想法。
elementData[]被transient修饰,不会被序列化。
size记录了前面多少位存有元素,遍历、序列化至size位置即可。