ArrayList源码阅读笔记
ArrayList
ArrayList继承自AbstractList抽象类,实现了RandomAccess, Cloneable, java.io.Serializable接口,其中RandomAccess是一个标志接口,代表可以支持快速随机访问,实现该接口的类使用for循环比使用迭代器要快,LinkedList并未实现该接口,它用迭代器进行遍历要比ArrayList用迭代器要快。可以用instanceof确定某obj是否实现RandomAccess。Cloneable:标志接口,表示可被克隆;java.io.Serializable可序列化标志接口。(克隆和序列化还能继续研究 。。。)
ArrayList是动态的、长度可变的,但底层由长度不可变的数组实现,默认数组大小为10,可以用ArrayList(int lenth)
初始化数组长度,也可以使用ensureCapacity(int minCapacity)
来预估数据数量而提前扩容(用此方法扩容时ArrayList不能为空数组)。
在进行扩容时多次使用Arrays.copyOf(obj [],newObj [])
方法,扩容大小由内部方法newCapacity(int minCapacity)
确定:
/**
* Returns a capacity at least as large as the given minimum capacity.返回至少与给定最小容量相同的容量
* Returns the current capacity increased by 50% if that suffices.如果满足,则返回当前容量增加50%
* Will not return a capacity greater than MAX_ARRAY_SIZE unless
* the given minimum capacity is greater than MAX_ARRAY_SIZE.
*除非给定的最小容量大于MAX_ARRAY_SIZE,否则不会返回大于MAX_ARRAY_SIZE的容量。
* @param minCapacity the desired minimum capacity
* @throws OutOfMemoryError if minCapacity is less than zero
*/
private int newCapacity(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;//原长度
int newCapacity = oldCapacity + (oldCapacity >> 1);//原长度1.5倍
if (newCapacity - minCapacity <= 0) {//如果给定长度大于1.5倍
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
return Math.max(DEFAULT_CAPACITY, minCapacity);//为空则返回给定和默认中大的
if (minCapacity < 0) // overflow不能给负数
throw new OutOfMemoryError();
return minCapacity;//否则就返回给定值
}
return (newCapacity - MAX_ARRAY_SIZE <= 0)
? newCapacity
: hugeCapacity(minCapacity);//这里,MAX_ARRAY_SIZE为Integer.MAX_VALUE-8,因为部分虚拟机会存储head code(头部信息?),所以直接给Integer.MAX_VALUE会报错:数组大小超出VM限制。但它还是会把这8个位置给你233333
}
说完扩容,说缩容,正常情况下开辟过的容量并不会减少,不使用了会赋null(比如clear()方法,以及内部的protected void removeRange()
),但这样会造成浪费,因此可以使用trimToSize()方法把其中的数组大小修改为元素数量(trim:修剪)。
其中add、remove方法使用System.arraycopy(Object src, int srcPos,Object dest, int destPos,int length)
来创建新的数组,从而表现出长度可变的特性。//这么做代表数组中间不会有null值,但是数组尾部未使用的会有null值。
removeAll(c),可以删除和c相同的元素,也就是求与c的差集;
retainAll(c),可以保留和c相同的元素,也就是求与c的交集。这两个方法都调用了期内部方法`batchRemove方法:
boolean batchRemove(Collection<?> c, boolean complement,
final int from, final int end) {
Objects.requireNonNull(c);//c中不能有null
final Object[] es = elementData;//获取原数组
int r;//声明遍历标志位
// Optimize for initial run of survivors
//优化幸存者的初始运行,也就是处理需要保留的元素
for (r = from;; r++) {
if (r == end)//removeAll与retainAll方法调用时传参from为0,end为数组大小size,这里判断是否需要执行删除操作
return false;//false代表没有元素删除
if (c.contains(es[r]) != complement)
break;//代表需要删除元素
}
int w = r++;//声明保留标志位,初始化为第一个要删除的元素,同时r+1继续遍历
try {
for (Object e; r < end; r++)
if (c.contains(e = es[r]) == complement)
es[w++] = e;//这里执行的不是删除,而是将后面需要保留的元素覆盖掉第一个需要删除的元素,相当于把需要保留的元素向前移动了
} catch (Throwable ex) {
// Preserve behavioral compatibility with AbstractCollection,
// even if c.contains() throws.
//保留与AbstractCollection的行为兼容性,即使c.contains()抛出也是如此。
System.arraycopy(es, r, es, w, end - r);
w += end - r;
throw ex;
} finally {
modCount += end - w;//修改(删除)的次数
shiftTailOverGap(es, w, end);//拷贝需要保留的元素,同时将后面移动过后的元素赋null
}
return true;//代表修改(删除)了元素
}
该方法还是比较精妙的。
至于迭代器了,恕笔者学识尚浅,就不发表拙见了//待更新。。。
疑点:其中两个私有的io方法,这俩玩意有什么用?私有不说,光见它声明没见它调用啊//待更新。。。
/**
* Saves the state of the {@code ArrayList} instance to a stream
* (that is, serializes it).
*
* @param s the stream
* @throws java.io.IOException if an I/O error occurs
* @serialData The length of the array backing the {@code ArrayList}
* instance is emitted (int), followed by all of its elements
* (each an {@code Object}) in the proper order.
*/
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException {
// Write out element count, and any hidden stuff
int expectedModCount = modCount;
s.defaultWriteObject();
// Write out size as capacity for behavioral compatibility with clone()
s.writeInt(size);
// Write out all elements in the proper order.
for (int i=0; i<size; i++) {
s.writeObject(elementData[i]);
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
/**
* Reconstitutes the {@code ArrayList} instance from a stream (that is,
* deserializes it).
* @param s the stream
* @throws ClassNotFoundException if the class of a serialized object
* could not be found
* @throws java.io.IOException if an I/O error occurs
*/
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
// Read in size, and any hidden stuff
s.defaultReadObject();
// Read in capacity
s.readInt(); // ignored
if (size > 0) {
// like clone(), allocate array based upon size not capacity
SharedSecrets.getJavaObjectInputStreamAccess().checkArray(s, Object[].class, size);
Object[] elements = new Object[size];
// Read in all elements in the proper order.
for (int i = 0; i < size; i++) {
elements[i] = s.readObject();
}
elementData = elements;
} else if (size == 0) {
elementData = EMPTY_ELEMENTDATA;
} else {
throw new java.io.InvalidObjectException("Invalid size: " + size);
}
}