jdk源码之ArrayList

50 阅读5分钟

一、再走读源码之前,先来复习一下简单的数据结构

1、数据结构:
  • 线性结构:数据结构中的元素存在一对一的相互关系;
  • 树形结构:数据结构中的元素存在一对多的相互关系;
  • 图形结构:数据结构中的元素存在多对多的相互关系。
2、线性结构

线性结构有且仅有一个开始结点和一个结束结点,并且每个结点最多只有一个前驱和一个后继,常见的线性结构如下:

  • 顺序表
  • 链表(单向,双向,循环)

以上3种数据的逻辑结构一致,指反映数据元素之间的逻辑关系的数据结构,其中的逻辑关系是指数据元素之间的前后间关系。

以上3种数据的物理结构(也叫存储结构)不一致,指数据的逻辑数据在计算机存储空间上的存放形式,具体实现的方法有顺序、链接、索引、散列等多种

二、先看ArrayList的几个成员变量

private static final int DEFAULT_CAPACITY = 10;   // 集合默认长度
​
​
private static final Object[] EMPTY_ELEMENTDATA = {};
​
​
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
​
​
transient Object[] elementData;  // 用于存储元素的object数组
​
​
private int size;  // 集合实际长度

可以看出,ArrayList底层使用的是object数组存储元素,也就是线性结构种的线性表,学过数据结构都知道,线性表可以根据下标直接访问元素,所以查询效率较高,但是再中间添加元素的时候,需要将index以后的所有元素后移一位,删除中间元素,需要将index以后的所有元素前移一位,所以增删效率较低。

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

当指定集合长度时,直接创建等长度的数组,为的是后续添加元素的时候减少数组的扩展和元素的复制。

2、无参构造器
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

空数组,长度为0

3、以一个集合作为参数的构造器
public ArrayList(Collection<? extends E> c) {
    Object[] a = c.toArray();
    if ((size = a.length) != 0) {
        if (c.getClass() == ArrayList.class) {
            elementData = a;
        } else {
            elementData = Arrays.copyOf(a, size, Object[].class);
        }
    } else {
        // replace with empty array.
        elementData = EMPTY_ELEMENTDATA;
    }
}

先将入参集合转成object数组a,当数组a长度不为空时,先判断集合c是不是ArrayList类型,是的话就elementData直接指向c的堆地址了,不是的话就将a数组拷贝给elementData

四、ArrayList常用api

1、添加一个元素

这个方法看懂之后,后面的都是同样的逻辑了

public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // 确保数组长度够用,不够用就扩容
    elementData[size++] = e;  // 数组第size++位置赋值为e
    return true;
}
​
​
private void ensureCapacityInternal(int minCapacity) {   // minCapacity为添加元素之后的长度
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
​
​
private static int calculateCapacity(Object[] elementData, int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { // 当集合为空数组时,返回默认值10和需要长度的 较大者
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    return minCapacity;
}
​
​
private void ensureExplicitCapacity(int minCapacity) {
    modCount++;
​
    // overflow-conscious code
    if (minCapacity - elementData.length > 0)  //  当需要的长度,大于当前数组的长度时,扩容
        grow(minCapacity);
}
​
​
private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + (oldCapacity >> 1);  // 新数组长度为老数组的1.5倍
    if (newCapacity - minCapacity < 0)  // 扩充1.5倍还是不够,直接把需要的长度赋值给新数组长度
        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);  // elementData指向copy之后的新数组
}
2、指定位置添加元素
public void add(int index, E element) {
    rangeCheckForAdd(index);
​
    ensureCapacityInternal(size + 1);  // 确保数组长度够用,不够用就扩容
    System.arraycopy(elementData, index, elementData, index + 1,  //index以后的元素后移一位,空出来index位置
                     size - index);
    elementData[index] = element; // index位置赋值element
    size++;
}
​
​
public static native void arraycopy(Object src,  int  srcPos,
                                        Object dest, int destPos,
                                        int length);
// 它是一个静态本地方法,由虚拟机实现  它的含义是  从源数组src取元素,范围为下标srcPos到srcPos+length-1,取出共length个元素,存放到目标数组中,存放位置为下标destPos到destPos+length-1
3、addAll
public boolean addAll(Collection<? extends E> c) {
    Object[] a = c.toArray();
    int numNew = a.length;
    ensureCapacityInternal(size + numNew);  // 与上续add一样,确保数组长度够用,不够用就扩容(包括老数组元素的复制)
    System.arraycopy(a, 0, elementData, size, numNew);// 将a数组复制到 elementData
    size += numNew; // 数组长度增加
    return numNew != 0;
}
4、移除元素remove
public E remove(int index) {  // 按照坐标移除
    rangeCheck(index);  // 检查坐标是否越界
​
    modCount++;
    E oldValue = elementData(index);  //根据坐标取出元素
​
    int numMoved = size - index - 1;  //上面讲过线性表删除元素,index以后的元素前移一位
    if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    elementData[--size] = null;   //移动一位之后,最后一位置为null,然后长度减一  但是这时候数组elementData的长度是不变的,只不过是size减一了
​
    return oldValue;
}
​
E elementData(int index) {
    return (E) elementData[index];
}
​
private void rangeCheck(int index) {
    if (index >= size)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
​
​
public boolean remove(Object o) {  // 按照元素删除,实现就是遍历elementData数组,找到相等的,就fastRemove删除
    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
}
5、get/set
public E get(int index) {
    rangeCheck(index);  // 检查下标
​
    return elementData(index);  // 数组根据下标取元素
}
​
​
public E set(int index, E element) {
    rangeCheck(index);  // 检查下标
​
    E oldValue = elementData(index  // 数组根据下标取元素
    elementData[index] = element;  // 该下标设置新元素
    return oldValue;
}
​
​
E elementData(int index) {
    return (E) elementData[index];
}
​
​
private void rangeCheck(int index) {
    if (index >= size)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
6、indexOf/contains
public int indexOf(Object o) { //其实看懂上面之后,这些就很简单了
    if (o == null) {
        for (int i = 0; i < size; i++)
            if (elementData[i]==null)
                return i;
    } else {
        for (int i = 0; i < size; i++)
            if (o.equals(elementData[i]))
                return i;
    }
    return -1;
}
​
​
public boolean contains(Object o) {
    return indexOf(o) >= 0;
}

五、总结

本文只是列举了ArrayList一些常用的方法,更多的感兴趣的话可以自行阅读,感觉以前学数据结构的时候,总是知道有这么个东西,有这些特性。而ArrayList的实现方案其实就是线性表的一个很完美的实践。