集合ArrayList源码解读

ArrayList是一个基于数组实现的动态数组结构。与普通 Java 数组不同,普通数组在创建时需指定长度且长度固定,而 ArrayList 的容量可根据元素的添加或删除动态变化,可看作是能自动调整大小的数组 。下面我们来解读一下它的源码,体会当初创作者更深的思考。

1.构造

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

 这代表创建ArrayList时会自动调用无参构造,

  • elementDataArrayList的核心成员变量,是一个Object[]数组,用于存储实际元素。
  • DEFAULTCAPACITY_EMPTY_ELEMENTDATAArrayList类中的静态常量,定义为一个空数组{}。其作用是标记该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);
        }
    }

有参构造,非常好懂。

2.add方法,增加元素

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

 这是add的方法的实现,但是我们只能明白它对  elementData[size++] = e;进行赋值,但是我们并没有看到扩容操作。那么就是与 ensureCapacityInternal(size + 1); 有关。我们点进来

private void ensureCapacityInternal(int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
    }

这里的 点进来发现是常量DEFAULT_CAPACITY10

private static final int DEFAULT_CAPACITY = 10;

就是如果本身为空,就比较期望与10,如果无参构造,就是1和10比较,自然选10,如果是有参构造,就选较大值。用这句ensureExplicitCapacity(minCapacity);扩容。

点进来看

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

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
  • modCountArrayList的父类AbstractList中的一个字段,记录集合结构被修改的次数。这里比较minCapacity ,elementData.length,就是如果要求最小长度大于elementData.length数组长度,就扩容 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);
    }

int oldCapacity = elementData.length; int newCapacity = oldCapacity + (oldCapacity >> 1);这两句表明新长度先扩容原数组长度的1.5倍,下面第一个if表示如还够大,就直接newCapacity = minCapacity;

  • MAX_ARRAY_SIZEArrayList允许的最大容量,通常为Integer.MAX_VALUE - 8(避免某些 VM 的内存溢出)。
  • 若新容量超过该值,则调用hugeCapacity方法处理极端情况
    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

  • minCapacity为负数(溢出),抛出OutOfMemoryError
  • 否则,返回Integer.MAX_VALUEMAX_ARRAY_SIZE(取决于minCapacity是否超过MAX_ARRAY_SIZE)。

正常来说还是走elementData = Arrays.copyOf(elementData, newCapacity);这句。使用Arrays.copyOf创建一个新的、更大的数组,并将原数组元素复制到新数组中。

3.删除 

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

 

调用rangeCheck(index)确保索引在有效范围内(0 ≤ index < size)。若索引越界,抛出IndexOutOfBoundsExceptionmodCount记录集合结构修改次数。通过elementData(index)获取指定索引位置的元素,后续作为返回值。numMoved表示需要移动的元素数量。例如,删除索引3的元素后,索引4size-1的元素需向前移动一位。用arraycopy高效地将后续元素前移,覆盖被删除的元素。将原最后一个元素置为null,帮助垃圾回收(避免内存泄漏)。--size将列表大小减 1。

System.arraycopy方法高效的原因主要有以下几点:

  • 底层实现优化arraycopy是一个本地方法,由 Java 虚拟机(JVM)在底层使用 C 或 C++ 等语言实现。这种底层实现能够直接操作内存,避免了 Java 语言层面的一些开销,如方法调用的栈操作、边界检查等,从而提高了复制效率。
  • 批量复制:它可以一次性将一个数组中的一段元素复制到另一个数组中,而不需要像在 Java 中使用循环逐个复制元素那样,每次复制都需要进行循环条件判断、数组索引计算等操作。arraycopy通过一次操作就可以完成大量元素的复制,减少了操作的次数,提高了效率。
  • 内存连续性:数组在内存中是连续存储的,arraycopy利用了这一特性,可以通过内存指针的移动和批量数据传输来实现高效复制。它能够利用现代计算机硬件的内存访问优化机制,如缓存行预取等,提高内存访问效率,进一步加快复制速度。

 上面我们是删索引,那如果是上元素的值会怎么样?就是如果你要删的是值怎么办?

加一个Object即可。 

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

 由于null元素只能通过==比较,非null元素需使用equals确保逻辑相等性。这里我们分开比较,下面我们点进来fastRemove(index);

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
    }

和上面删除索引几乎一模一样。注意这里只删除第一个。

4.改

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

        E oldValue = elementData(index);
        elementData[index] = element;
        return oldValue;
    }
  • 调用rangeCheck(index)确保索引在有效范围内(0 ≤ index < size)。
  • 若索引越界,抛出IndexOutOfBoundsException

剩下的就是替换,非常简单。

5.查

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

        return elementData(index);
    }

也很简单,就是要注意是否越界。点进elememtData

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

也很简单 ,这里要注意的是无论泛型类型E是什么(如StringInteger),数组的实际类型始终是Object[]。因此,从数组中获取元素时,需要将Object强制转换为泛型类型E。

总结:我们发现源码确实很精妙和细节,我们有时有疑问可以考虑读源码来解决。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值