ArrayList

求知探索 1年前 ⋅ 670 阅读

一、ArrayList 介绍

ArrayList 是 java 集合框架中比较常用的数据结构了。继承自 AbstractList,实现了 List 接口。底层基于数组实现容量大小动态变化。允许 null 的存在。同时还实现了 RandomAccessCloneableSerializable 接口,所以 ArrayList 是支持快速访问、复制、序列化的。

二、ArrayList 源码解析

2.1 成员变量

// 存储集合
transient Object[] elementData; 
// elementData中存储的实际元素个数,elementData.length 表示最大的可存储容量
private int size;  
// elementData 默认的初始容量
private static final int DEFAULT_CAPACITY = 10;
// 用于构造函数创建对象,唯一的区别在于区分是通过有参构造函数还是无参构造函数被初始化,以便确认扩容方式
private static final Object[] EMPTY_ELEMENTDATA = {};
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
// 这个变量定义在 AbstractList 中,记录对 List 操作的次数,主要使用是在 Iterator,是防止在迭代的过程中集合被修改。
protected transient int modCount = 0;

2.2 构造函数

无参构造函数: 创建一个空 list 集合,初始容量为10 将会在第一次添加元素时实现

public ArrayList() {
	this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

指定容量构造函数:构造一个初始容量大小为 initialCapacity 的 ArrayList

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);
    }
}

指定 Collection 构造函数: 将 Collection 转化为数组并赋值给 elementData,把 elementData 中元素的个数赋值给 size。 如果 size 不为零,则判断 elementData 的 class 类型是否为 Object[],不是的话则做一次转换。 如果 size 为零,则把 EMPTY_ELEMENTDATA 赋值给 elementData,相当于 new ArrayList(0)

public ArrayList(Collection<? extends E> c) {
    elementData = c.toArray();
    if ((size = elementData.length) != 0) {
        // c.toArray might (incorrectly) not return Object[] (see 6260652)
        // 当使用 Arrays.asList(array) 创建的集合会进入这个判断
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    } else {
        // replace with empty array.
        this.elementData = EMPTY_ELEMENTDATA;
    }
}

2.3 常用方法

2.3.1 add

public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}

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++;
    // 可能存在溢出,需要进行扩容操作
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

流程说明:

1.确认集合容量大小
2.如果是无参构造函数创建的,设置默认容量大小为10  
3.对 modCount 进行自增
4.判断是否超出集合容量,如果超出则进行扩容操作    

然后我们看看扩容操作:

// 进行扩容
private void grow(int minCapacity) {
    int oldCapacity = elementData.length;
    // 默认扩容至原来容量的 1.5 倍
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    // 扩容后也可能容量不够用,所以需要再次判断
    // 如果太小,则将我们需要的容量赋值给 newCapacity
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    // 如果太大,则重新计算容量
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);  
    // 将原数组的数据复制到新的数组中去,并赋值给 elementData
    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;
}

我们来看下指定添加位置的 add:

public void add(int index, E element) {
    // 检查是否可添加
    rangeCheckForAdd(index);
	// 和 add(E element) 一样
    ensureCapacityInternal(size + 1);  
    // 拷贝数组对象
    System.arraycopy(elementData, index, elementData, index + 1,
                     size - index);
    elementData[index] = element;
    size++;
}

// 检查添加范围
private void rangeCheckForAdd(int index) {
    if (index > size || index < 0)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

2.3.2 addAll

addAll 是将一个集合对象一起添加到 list 中来:

public boolean addAll(Collection<? extends E> c) {
    // 将集合转为数组
    Object[] a = c.toArray();
    int numNew = a.length;
    ensureCapacityInternal(size + numNew);  // Increments modCount
    System.arraycopy(a, 0, elementData, size, numNew);
    size += numNew;
    return numNew != 0;
}
public boolean addAll(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;
}

private void rangeCheckForAdd(int index) {
    if (index > size || index < 0)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

2.3.3 remove

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; // clear to let GC do its work
    return oldValue;
}

public boolean remove(Object o) {
    if (o == null) {
        for (int index = 0; index < size; index++)
            if (elementData[index] == null) {
                fastRemove(index);
                return true;
            }
    } else {
        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; // clear to let GC do its work
}

2.3.4 get

ArrayList 底层是基于数组实现的,所以获取元素就相当简单了

public E get(int index) {
    rangeCheck(index);

    return elementData(index);
}

private void rangeCheck(int index) {
    if (index >= size)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

E elementData(int index) {
    return (E) elementData[index];
}
 

全部评论: 0

    我有话说: