ArrayList 源码解读与扩容机制,揭秘背后的“魔法”

开篇语

哈喽,各位小伙伴们,你们好呀,我是喵手。运营社区:C站/掘金/腾讯云/阿里云/华为云/51CTO;欢迎大家常来逛逛

  今天我要给大家分享一些自己日常学习到的一些知识点,并以文字的形式跟大家一起交流,互相学习,一个人虽可以走的更快,但一群人可以走的更远。

  我是一名后端开发爱好者,工作日常接触到最多的就是Java语言啦,所以我都尽量抽业余时间把自己所学到所会的,通过文章的形式进行输出,希望以这种方式帮助到更多的初学者或者想入门的小伙伴们,同时也能对自己的技术进行沉淀,加以复盘,查缺补漏。

小伙伴们在批阅的过程中,如果觉得文章不错,欢迎点赞、收藏、关注哦。三连即是对作者我写作道路上最好的鼓励与支持!

前言

在 Java 中,ArrayList 是最常见的动态数组实现之一,它为我们提供了快速的随机访问能力。在日常开发中,我们经常会用到 ArrayList,但对它的底层实现和扩容机制的理解往往只是停留在表面。今天,我将带你深入剖析 ArrayList 的源码,探讨它如何高效地处理数据、扩容机制是如何工作的,以及它的优势和不足。

好了,动起来!带着好奇心一起“解锁” ArrayList 的奥秘吧!

1. ArrayList 源码基础

1.1 ArrayList 概述

ArrayList 是一个可变大小的数组实现,属于 List 接口的一个类。它内部使用一个数组来存储数据,并允许快速访问和修改元素。由于它是动态的,大小会根据元素的增加而自动扩展。

public class ArrayList<E> extends AbstractList<E>
    implements List<E>, RandomAccess, Cloneable, Serializable {
    private static final long serialVersionUID = 8683452581122892189L;
    
    // 底层数组
    transient Object[] elementData;

    // ArrayList 中元素的个数
    private int size;

    // 空构造函数,初始化容量为 10
    public ArrayList() {
        this(10);
    }

    // 带初始容量的构造函数
    public ArrayList(int initialCapacity) {
        if (initialCapacity < 0) {
            throw new IllegalArgumentException("Illegal Capacity: " +
                                                initialCapacity);
        }
        this.elementData = new Object[initialCapacity];
    }

    // 获取元素数量
    public int size() {
        return size;
    }

    // 添加元素
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // 确保容量足够
        elementData[size++] = e;
        return true;
    }

    // 确保容量足够
    private void ensureCapacityInternal(int minCapacity) {
        if (elementData.length < minCapacity)
            grow(minCapacity);
    }

    // 扩容方法
    private void grow(int minCapacity) {
        // 计算新的容量
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);  // 1.5倍扩容
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        // 重新分配内存
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
}

1.2 核心成员变量

  • elementData:这是一个 Object[] 数组,用来存储 ArrayList 中的元素。elementData 在初始化时有一个默认的容量。
  • sizeArrayList 中当前元素的数量。注意,size 只是记录实际元素的数量,不代表数组的长度。

1.3 添加元素

ArrayListadd 方法会首先调用 ensureCapacityInternal 来确保数组的容量。如果容量不足,就会调用 grow 方法进行扩容。

2. ArrayList 扩容机制揭秘

2.1 扩容的时机

ArrayList 在以下两种情况下会进行扩容:

  1. 添加元素时容量不足:当你向 ArrayList 中添加元素,但元素数组的容量已满时,ArrayList 会自动扩容。
  2. 手动调整容量:你可以通过 ensureCapacity 方法提前扩容,避免在添加元素时频繁扩容。

2.2 扩容的具体实现

扩容的逻辑在 grow 方法中实现,下面是扩容的关键点:

  • oldCapacity >> 1:这部分是右移运算,相当于除以 2。意思是,新容量是原容量的 1.5 倍。
  • Arrays.copyOf(elementData, newCapacity):这是对原数组的复制,扩容后数据会被复制到新数组。
private void grow(int minCapacity) {
    // 获取当前容量
    int oldCapacity = elementData.length;
    // 扩容1.5倍
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    // 如果新容量仍然不足,使用minCapacity
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    // 复制数据到新数组
    elementData = Arrays.copyOf(elementData, newCapacity);
}

2.3 扩容的例子

假设初始容量是 10,当添加第 11 个元素时,ArrayList 会扩容到 15(1.5倍扩容)。然后,如果再添加第 16 个元素,容量将会被扩展到 22(1.5倍扩容)。这种扩容的方式能保证性能不会受到过度扩容的影响。

扩容过程:
  • 初始容量:10
  • 第一次扩容:10 * 1.5 = 15
  • 第二次扩容:15 * 1.5 ≈ 22
  • 第三次扩容:22 * 1.5 ≈ 33

这样,每次扩容后,ArrayList 的容量都能够有效满足新的元素添加需求,同时避免了频繁扩容带来的性能问题。

3. 进一步优化:确保容量

在某些场景中,我们知道 ArrayList 需要存储一定数量的元素。此时可以通过 ensureCapacity 方法提前调整容量,避免频繁扩容带来的性能损耗。

3.1 ensureCapacity 方法

ensureCapacity 方法允许你手动设置 ArrayList 的容量,以减少扩容的次数。

public void ensureCapacity(int minCapacity) {
    // 容量调整逻辑
    int minExpand = (elementData != EMPTY_ELEMENTDATA) ? 0 : DEFAULT_CAPACITY;
    if (minCapacity > minExpand) {
        ensureCapacityInternal(minCapacity);
    }
}

通过这种方式,你可以确保 ArrayList 在添加大量数据时,避免多次扩容导致性能问题。

4. 内存消耗与性能分析

4.1 内存消耗

ArrayList 会根据容量动态分配内存,而扩容机制的核心是按 1.5 倍增长容量,这样可以在提高性能的同时,避免内存浪费。

但是如果元素数量比较少,ArrayList 会浪费部分内存。比如,如果你仅存储 10 个元素,但 ArrayList 扩容到 100 时,就会有 90 个空闲空间。这种内存浪费是不可避免的,但通过合理的容量控制,可以减小这种浪费。

4.2 性能分析

  • 添加元素:当容量足够时,add 操作的时间复杂度是 O(1),但如果发生扩容,扩容时的时间复杂度为 O(n)。
  • 查询和更新:由于 ArrayList 是基于数组实现的,所以查询和更新操作的时间复杂度为 O(1),非常高效。
  • 删除元素:删除操作需要移动数组中的元素,时间复杂度为 O(n)。

5. 总结

ArrayList 是一个高效的动态数组实现,通过扩容机制,它能够平衡内存使用和性能。当数据量较少时,ArrayList 会保持较小的内存占用,但随着数据量的增长,它会自动扩容,保证操作的高效性。理解 ArrayList 的扩容机制,可以帮助我们在实际开发中做出更加精准的性能优化,避免不必要的扩容和内存浪费。

如果你还对其他数据结构或算法有兴趣,记得持续关注!

… …

文末

好啦,以上就是我这期的全部内容,如果有任何疑问,欢迎下方留言哦,咱们下期见。

… …

学习不分先后,知识不分多少;事无巨细,当以虚心求教;三人行,必有我师焉!!!

wished for you successed !!!


⭐️若喜欢我,就请关注我叭。

⭐️若对您有用,就请点赞叭。
⭐️若有疑问,就请评论留言告诉我叭。


版权声明:本文由作者原创,转载请注明出处,谢谢支持!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值