容器的源码(ArrayList)
ArrayList
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
ArrayList是基于数组实现的,所以支持快速随机访问
数组的默认大小为10
private static final int DEFAULT_CAPACITY = 10;
arraylist提供了三种构造方法。可以使用空参构造,还可以传递一个int值(指定初始容量)或是一个集合
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
transient Object[] elementData;
elementData用来存放实际数据,并使用transient修饰,表明该数据在默认的序列化机制时不会被序列化。arraylist实现了java.io.SeriaLizable接口,arraylist自己实现了序列化与反序列化的方法
DEFAULTCAPACITY_EMPTY_ELEMENTDATA则是一个空的数组。
注意DEFAULTCAPACITY_EMPTY_ELEMENTDATA类型为static final,表明其在内存中只有一份且禁止修改。
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);
}
}
《阿里巴巴Java开发手册》里面建议初始化集合时尽量显示的指定集合大小。为什么?读了上面的源码之后,应该可以知道答案了。
1.节约内存,实际编码中,很多时候我们都可以知道ArrayList里面会放什么元素以及放多少元素。恰当的设置容器大小可以节约内存。
2.避免扩容产生的性能损耗。
逻辑非常简单,如果初始容量>0,则创建一个该大小的数组。如果容量为0,则创建一个空数组。如果容量<0,抛出异常。
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}
逻辑并不复杂,直接将集合转换为Object数组,赋值给了elementData属性。注意注释上面声明了可能抛出NullPointerException,看源码会发现当我们传递进来的元素是null值的时候,会在c.toArray()的时候抛出NPE。
添加元素
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
private int size;
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
//扩容
grow(minCapacity);
}
首先,调用了ensureCapacityInternal()方法,入参传递了size+1,size+1表示elementData所需要的最小长度。这里的size变量,是用来记录ArrayList包含元素的多少的,初始值为0,我们调用ArrayList的size()方法,返回的就是该字段。
ensureCapacityInternal方法做了一件事情,判断当前数组能不能方法即将被添加的元素,如果不能,扩容。调用了calculateCapacity()计算容量
调用完calculateCapacity()后,调用ensureExplicitCapacity(),这个方法做了两件事情:
1.将modCount自增
2.如果容量不够,扩容。
扩容的方法grow()
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);
}
可以看到扩容后的容量为原容量的1.5倍+1。
rrayList不是无限扩容的,它是有限度的。上面的代码有一个判断:
**if** **(**newCapacity **-** MAX_ARRAY_SIZE **>** 0**)**newCapacity **=** hugeCapacity**(**minCapacity**);**
如果扩容后的容量比数组最大容量大,调用hugeCapacity()方法,并将扩容前所需要的最小容量传递的进去。
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
hugeCapacity方法只在扩容时可能被调用,它的逻辑很简单,先做了个简单的判断,之后执行了一个三元表达式,如果扩容前所需最小容量大于数组最大长度,返回Integer的最大值,否则返回MAX_ARRAY_SIZE,MAX_ARRAY_SIZE为Integer的最大值-8。
**private** **static** **final** **int** MAX_ARRAY_SIZE **=** Integer**.**MAX_VALUE **-** 8**;**
int的最大值为2的31次方-1,所以说ArrayList的最大容量为2的31次方-1。
至于空参创造的ArrayList
第一次调用add()方法时发生了什么?扩容,是的,它会将默认的空数组扩容为一个长度为10的数组。
添加到指定.位置add(int index, E element)
注意ArrayList的扩容时机和HashMap有区别,ArrayList只有底层数组已满,不能放下即将存入的对象才会扩容,HashMap的扩容和加载因子有关系,默认情况下,不是容器满了才扩容。
public void add(int index, E element) {
//检查index是否在已有的数组中
if (index > size || index < 0)
throw new IndexOutOfBoundsException("Index:"+index+",Size:"+size);
ensureCapacity(size + 1);//确保对象数组elementData有足够的容量,可以将新加入的元素e加进去
System.arraycopy(elementData, index, elementData, index+1, size-index);//将index及其后边的所有的元素整块后移,空出index位置
elementData[index] = element;//插入元素
size++;//已有数组元素个数+1
}
添加所有addAll(Collection<? extends E> c)
public boolean addAll(Collection<? extends E> c) {
Object[] a = c.toArray();//将c集合转化为对象数组a
int numNew = a.length;//获取a对象数组的容量
ensureCapacity(size + numNew);//确保对象数组elementData有足够的容量,可以将新加入的a对象数组加进去
System.arraycopy(a, 0, elementData, size, numNew);//将对象数组a拷贝到elementData中去
size += numNew;//重新设置elementData中已加入的元素的个数
return numNew != 0;//若加入的是空集合则返回false
}
添加所有到指定位置addAll(int index, Collection<? extends E> c)
publicbooleanaddAll(int index, Collection<?extends E> c){
rangeCheckForAdd(index);
Object[] a= c.toArray();
int numNew= a.length;
ensureCapacityInternal(size+ numNew);// Increments modCount
int numMoved= size- index;
if(numMoved> 0)
System.arraycopy(elementData, index, elementData, index+ numNew,numMoved);
System.arraycopy(a, 0, elementData, index, numNew);
size+= numNew;
return numNew!= 0;
}
类似的移动
删除元素:
3.1 删除指定索引元素 E remove(int index)。
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);
//将最后一个元素置为null,下次gc回收
elementData[--size] = null; // clear to let GC do its work
//返回被删除的值
return oldValue;
}
ArrayList删除元素的时候可能会进行数据拷贝也是其和LinkedList的区别之一。
3.2 删除指定值的元素 remove(Object o)
public boolean remove(Object o) {
if (o == null) {//移除对象数组elementData中的第一个null
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {//移除对象数组elementData中的第一个o
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的时候就会回收掉了
}
remove(Object o)需要遍历数组,remove(int index)不需要,只需要判断索引符合范围即可,所以,通常:后者效率更高。
4 获取元素
4.1 获取单个元素get(int index)
public E get(int index) {
rangeCheck(index);//检查索引范围
return (E) elementData[index];//返回元素,并将Object转型为E
}
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
注意rangeCheck检查的是size的大小,也就是实际存储元素个数,而不是容器的实际容量。
5. 遍历元素 iterator()
public Iterator<E> iterator() {
return new Itr();
}
Itr是ArryList的一个私有内部类,实现了Iterator接口。
private class Itr implements Iterator<E> {
int cursor = 0;//标记位:标记遍历到哪一个元素
int expectedModCount = modCount;//标记位:用于判断是否在遍历的过程中,是否发生了add、remove操作
//检测对象数组是否还有元素
public boolean hasNext() {
return cursor != size();//如果cursor==size,说明已经遍历完了,上一次遍历的是最后一个元素
}
//获取元素
public E next() {
checkForComodification();//检测在遍历的过程中,是否发生了add、remove操作
try {
E next = get(cursor++);
return next;
} catch (IndexOutOfBoundsException e) {//捕获get(cursor++)方法的IndexOutOfBoundsException
checkForComodification();
throw new NoSuchElementException();
}
}
//检测在遍历的过程中,是否发生了add、remove等操作
final void checkForComodification() {
if (modCount != expectedModCount)//发生了add、remove操作,这个我们可以查看add等的源代码,发现会出现modCount++
throw new ConcurrentModificationException();
}
@Override
@SuppressWarnings("unchecked")
public void forEachRemaining(Consumer<? super E> consumer) {
Objects.requireNonNull(consumer);
final int size = ArrayList.this.size;
int i = cursor;
if (i >= size) {
return;
}
final Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length) {
throw new ConcurrentModificationException();
}
while (i != size && modCount == expectedModCount) {
consumer.accept((E) elementData[i++]);
}
// update once at end of iteration to reduce heap write traffic
cursor = i;
lastRet = i - 1;
checkForComodification();
}
}
需要注意的是这里有一个Java集合的fail-fast事件。你可能已经注意到,我们在调用add()、remove()这些修改集合的方法时,都会修改一个属性modCount。而我们在遍历集合时,首先会保存一份modCount,然后在遍历时,将保存的modCount和成员变量modCount对比,如果不一样,说明被集合已经被修改,抛出ConcurrentModificationException,产生fail-fast事件。
序列化
ArrayList 实现了 writeObject() 和 readObject() 来控制只序列化数组中有元素填充那部分内容。
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
elementData = EMPTY_ELEMENTDATA;
// Read in size, and any hidden stuff
s.defaultReadObject();
// Read in capacity
s.readInt(); // ignored
if (size > 0) {
// be like clone(), allocate array based upon size not capacity
ensureCapacityInternal(size);
Object[] a = elementData;
// Read in all elements in the proper order.
for (int i=0; i<size; i++) {
a[i] = s.readObject();
}
}
}
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 behavioural 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();
}
}
序列化时需要使用 ObjectOutputStream 的 writeObject() 将对象转换为字节流并输出。而 writeObject() 方法在传入的对象存在 writeObject() 的时候会去反射调用该对象的 writeObject() 来实现序列化。反序列化使用的是 ObjectInputStream 的 readObject() 方法,原理类似。
ArrayList内部还维护了一个size属性,它是用来记录数组中的实际元素个数。
size,modCount,elementData这些成员变量,都注定了ArrayList线程不安全。
.ArrayList实现了Iterator接口,这表明遍历ArrayList使用普通for循环比使用foreach更快