ArrayList 底层原理的剖析

80 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第28天,点击查看活动详情

一、java集合工具类

java工具包中为我们提供了集合工具类,如下 image.png

1-1、ArrayList

ArrayList是我们常用的存储数据的集合,那它有什么特点、底层又是如何存储数据的呢?

特点:

元素有序的存储,并且可以重复

存储结构 底层采用数组来实现

原理

数组采用一段连续的存储单元来存储数据的,其特点为查询O(1)(查询的次数固定可循),删除插入为O(N)(查询的次数不确定),即查询快,插入删除慢,下面来说一下为什么

1-1-1、数组的读取、插入、删除实现过程

首先来看下数组的存储结构,定义的变量存储在栈区,而数据存储在堆区的连续地址中,通过指向堆区的内存地址来进行使用。

image.png

1-1-1-1、查询-(O(1)-速度快)

在一维数组中,存储了4个int类型的值,起始位置从100开始,因为int的字节数为4,因此后面的元素依次+4。

查找元素的计算公式为:a[n]=起始地址+(n*字节数)

比如要查找下标为2的元素,那就是a[2]=100+(2*4),得到108位置,这样a[2]的值就可以查询到了 image.png

1-1-1-2、插入-(O(N)-速度慢)

向数组中某个下标插入值,那么原下标及其后的值的下标地址就都需要依次向后移动。在元素不固定的情况下,我们插入数据,数组中的元素移动下标位置数量是不固定的,这就是O(N)。

image.png

1-1-1-3、删除-(O(N)-速度慢)

和插入类似,在根据下标删除的时候,后面的数据就需要依次向前移动元素,数组不确定,移动的数量也就不确定,因此数组删除元素也是O(N)

image.png

1-1-2、Arraylist.add(E e)-O(1)

首先看下ArrayList.add的源码

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

通过以上可以看到首先先调用了ensureCapacityInternal这个是一个扩容的方法,进入看下

ensureCapacityInternal->ensureExplicitCapacity->grow,在grow方法中最终实现的是Arrays.copyOF数组拷贝的方法

这个地方数组拷贝时间上就是用空间换时间的方式,比如元素组有10个位置的空间,在添加元素的时候,不是对元素进行扩容,而是申请一个新的地址,将原数组的内容复制到新地址中,然后再add新的数据

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 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-1-3、Arraylist.add(int index, E element)-O(N)

这种add方法就是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++;
}

1-1-4、ArrayList.get(i)

可以看到get(i),实际上是一个随机获取的方法

public E get(int index) {
    rangeCheck(index);
    checkForComodification();
    return ArrayList.this.elementData(offset + index);
}

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