Java集合001 --- ArrayList源码解析

前言

List是一种有序、可以重复的数据结构,底层实现是动态数组或者链表;常用的List结构有:ArrayList、LinkedList、CopyOnWriteList还有古老的Vector和Stack。

先带着如下几个问题去学习ArrayList的源码:

1、ArrayList是怎么扩容的?

2、ArrayList的插入、删除、查询的时间复杂度?

3、怎么求两个集合的交集、并集、差集?

4、ArrayList怎么实现序列化和反序列化?

5、集合的toArray方法有什么问题?

ArrayList属性

// 默认容量
private static final int DEFAULT_CAPACITY = 10;

// 空数组, 创建list时容量为0或者初始化集合为空时使用
private static final Object[] EMPTY_ELEMENTDATA = {};

// 空数组
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

// 数组元素
transient Object[] elementData; // non-private to simplify nested class access

// 当前数组中有几个元素, 区别容量
private int size;

构造方法

ArrayList提供了如下三个构造方法:

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

注意一点:可以指定初始容量为0,此时数组元素为EMPTY_ELEMENTDATA

2、无参构造器

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

初始容量为0,第一次添加元素时,指定容量,默认是10

3、入参为集合类型构造器

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

流程为:先将输入集合转换成数组,然后赋值;这里为什么要判断elementData是不是Object类呢、或者说什么时候elementData不是Object类?

答案是在重写了ArrayList的toArray方法的类可能出现上述场景

添加单个元素方法

ArrayList提供了如下两个添加元素的方法:

1、添加单个元素到数组的最后(时间复杂度为O(1))【返回boolean类型】

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 void ensureExplicitCapacity(int minCapacity) {
    modCount++; // 序列化时使用

    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

private static int calculateCapacity(Object[] elementData, int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    return minCapacity;
}

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

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

流程为:计算所需的最小容量,容量不够,做扩容处理,最后添加元素到末尾;这里重点说下扩容;

扩容机制为:先扩容1.5倍,如果还是不够分配需要的容量;如果大于Integer最大数值-8,则按照超大数组流程处理,防止整数溢出

2、添加单个元素到指定索引(涉及元素拷贝,时间复杂度为O(n))【无返回值】

public void add(int index, E element) {
    rangeCheckForAdd(index);

    ensureCapacityInternal(size + 1);  // Increments modCount!!
    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));
}

流程:1、验证索引,超上界or低于下界,抛IndexOutOfBoundsException异常(注意和ArrayIndexOutOfBoundsException的区别,ArrayIndexOutOfBoundsException为数组越界异常,IndexOutOfBoundsException为索引越界异常)

           2、扩容处理

           3、做元素拷贝:将index开始到结尾的元素拷贝到index+1到结尾

           4、元素复制&&元素个数加一

移除元素方法

1、移除指定索引的元素(时间复杂度为O(n))【返回值为被删除的元素】

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

2、移除指定元素(时间复杂度为O(n))【返回值为删除结果boolean类型】

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 remove method that skips bounds checking and does not
 * return the value removed.
 */
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
}

这里注意的是:按需重写list对象的equals方法,否则默认的equals方法比较的是两个对象的地址

获取&修改元素方法

1、获取指定索引的元素(时间复杂度为O(1))

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

    return elementData(index);
}

2、修改指定索引的元素为指定值(时间复杂度为O(1))【返回值为被修改的元素】

public E set(int index, E element) {
    rangeCheck(index);

    E oldValue = elementData(index);
    elementData[index] = element;
    return oldValue;
}

求两个集合的并集

实际上就是在指定list中批量添加元素,提供了如下两个方法:

1、在末尾批量添加元素(时间复杂度为O(n))【返回值为新添加元素个数是否为0(boolean)】

public boolean addAll(Collection<? extends E> c) {
    Object[] a = c.toArray(); // 转换为数组
    int numNew = a.length;
    ensureCapacityInternal(size + numNew); // 扩容处理
    System.arraycopy(a, 0, elementData, size, numNew); // 元素拷贝到原数组末尾
    size += numNew;
    return numNew != 0;
}

2、在指定索引批量添加元素(时间复杂度为O(n))【返回值为新添加元素个数是否为0(boolean)】

public boolean addAll(int index, Collection<? extends E> c) {
    rangeCheckForAdd(index); // 上界&下界校验

    Object[] a = c.toArray(); // 转换为数组
    int numNew = a.length;
    ensureCapacityInternal(size + numNew);  // 扩容处理

    int numMoved = size - index;
    if (numMoved > 0)
        System.arraycopy(elementData, index, elementData, index + numNew,
                         numMoved); // 先将原数组从index开始, 预留位置

    System.arraycopy(a, 0, elementData, index, numNew); // 将元素批量插入到index位置到index+numNew
    size += numNew;
    return numNew != 0;
}

求两个集合的交集

求两个集合的差集

ArrayList序列化

猜你喜欢

转载自www.cnblogs.com/sniffs/p/12911706.html