您的位置:首页 > 其它

源码阅读系列——基础篇(ArrayList 集合源码分析)

2020-06-29 05:02 80 查看

ArrayList是我们非常常用的一个线程不安全的List集合。ArrayList和Vector大体比较相似。我们继续阅读源码找答案。

1、类定义

public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
private static final int DEFAULT_CAPACITY = 10; // 默认容量
private static final Object[] EMPTY_ELEMENTDATA = {}; // 无元素的默认数组,见构造函数1
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; // 无元素的默认数组定义,见构造函数2
transient Object[] elementData; // 元素数组
private int size;  // 元素数量

// 构造函数1:指定大小的构造函数
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);
}
}

// 构造函数2
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
}

从上面的源码,得出以下:
ArrayList默认是一个空数组。只有制定长度时,才会生产一个定长数组。

2、扩容机制

已增加元素为例

public void add(int index, E element) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

ensureCapacityInternal(size + 1);  // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index); // 整体后移index后的所有元素
elementData[index] = element;
size++;
}

我看一下ensureCapacityInternal实现

private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); //如果当前长度不到DEFAULT_CAPACITY(10)的长度,先设置为DEFAULT_CAPACITY
}

ensureExplicitCapacity(minCapacity);
}

private void ensureExplicitCapacity(int minCapacity) {
modCount++;

if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);// 原来长度,加上原长度右移一位(相当于1.5倍原长度)
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
}

private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0)
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}

由源码得出:
1、如果长度不到10,则先扩容到10。
2、如果长度大于10了,则每次扩容1.5倍。比Vector每次扩容2倍,省空间,如果不频繁扩容,效率差不多。

3、迭代器

我们常常有这样的需求,遍历数组的过程中,修改元素,但是如果像下面这样的写法,一定会有数组越界的问题:

public void removeElement(int value){
for (int i = 0 ; i < elementList.size() ; i++){
if(value == elementList.get(i).value){
elementList.remove(i);
}
}
}

Java提供了迭代器的方式来满足以上需求,看一下迭代器的主要代码:

private class Itr implements Iterator<E> {
protected int limit = ArrayList.this.size;

int cursor;       // 游标,可以理解为遍历数组的指针(或者索引)。
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount; // modCount表示List被修改操作的次数。(我们在List里面的所有影响list数组的操作,都会自增1,比如List.remove(),List.clear())

public boolean hasNext() {
return cursor < limit;  // 是否已经遍历到头
}

@SuppressWarnings("unchecked")
public E next() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException(); // 如果发现迭代器在遍历的过程中,modCount发生变化了,则抛异常,这就是Fast-fail机制
int i = cursor;
if (i >= limit)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException(); // 发现数组长度越界,抛异常(理论上遍历的过程不会越界,所以理解迭代器之外有改动)
cursor = i + 1;  // 索引后移一位,表示下一个要被遍历的元素
return (E) elementData[lastRet = i]; // lastRet赋值为我们当前遍历的元素
}

public void remove() {
if (lastRet< 0)
throw new IllegalStateException();
if (modCount != expectedModCount)
throw new ConcurrentModificationException();

try {
ArrayList.this.remove(lastRet); // 移除当前遍历到的元素
cursor = lastRet;// 下一个遍历的元素,位置index也需要-1
lastRet = -1;
expectedModCount = modCount; // 更新modCount
limit--;   // 长度减1
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
}

从上面的源码解析,我们大致可以发现迭代器的作用:他其实可以理解建立了一个封闭的空间,可以让开发者在遍历的同时修改元素,甚至影响到元素数组的大小。而由于迭代器里面实现一套对长度、遍历索引的动态变更,所以在遍历的过程中,我们不能在迭代器之外对数组有任何改动。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: