首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >手撕ArrayList,ArrayList底层原理是什么,它是怎么扩容的?

手撕ArrayList,ArrayList底层原理是什么,它是怎么扩容的?

作者头像
程序员三明治
发布2025-12-18 20:37:33
发布2025-12-18 20:37:33
1930
举报
文章被收录于专栏:码力up码力up

👨‍💻程序员三明治个人主页 🔥 个人专栏: 《设计模式精解》 《重学数据结构》

🤞先做到 再看见!


ArrayList是什么?

ArrayList其实就是List接口下的一个重要实现,也就是一个动态数组,当更多的元素加入到ArrayList中时,其大小将会动态地增长,内部的元素可以直接通过get与set方法进行访问。

ArrayList的扩容机制是怎样的?

流程图如下:

下面我们来自己手撕一个ArrayList,来看看它的底层是怎么样的。

代码语言:javascript
复制
import java.util.ArrayList;
import java.util.List;
import java.util.Arrays;

public class ArrayListCapacityDemo {

    // 自定义ArrayList类用于演示扩容过程
    static class DebuggableArrayList<E> {
        // 实际存储元素的数组
        private Object[] elementData;
        // 当前元素数量
        private int size;
        // 默认初始容量
        private static final int DEFAULT_CAPACITY = 10;
        // 共享的空数组实例
        private static final Object[] EMPTY_ELEMENTDATA = {};

        public DebuggableArrayList() {
            this.elementData = EMPTY_ELEMENTDATA;
            this.size = 0;
        }

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

        // 内部容量检查
        private void ensureCapacityInternal(int minCapacity) {
            // 计算所需容量
            int capacity = calculateCapacity(elementData, minCapacity);
            // 明确是否需要扩容
            ensureExplicitCapacity(capacity);
        }

        // 容量计算
        private static int calculateCapacity(Object[] elementData, int minCapacity) {
            // 如果是空数组,返回默认容量和所需容量的较大值
            if (elementData == EMPTY_ELEMENTDATA) {
                return Math.max(DEFAULT_CAPACITY, minCapacity);
            }
            return minCapacity;
        }

        // 明确是否需要扩容
        private void ensureExplicitCapacity(int minCapacity) {
            // 修改计数器(用于迭代器快速失败机制)
            // modCount++;

            // 如果所需容量大于当前数组长度,则扩容
            if (minCapacity - elementData.length > 0) {
                grow(minCapacity);
            }
        }

        // 最大数组大小(一些VM在数组中保留头信息)
        private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

        // 扩容核心方法
        private void grow(int minCapacity) {
            int oldCapacity = elementData.length;

            // 新容量 = 旧容量 + 旧容量/2 (即1.5倍增长)
            int newCapacity = oldCapacity + (oldCapacity >> 1);

            // 如果1.5倍仍不够,则使用minCapacity
            if (newCapacity - minCapacity < 0) {
                newCapacity = minCapacity;
            }

            // 如果超过最大限制,则调整
            if (newCapacity - MAX_ARRAY_SIZE > 0) {
                newCapacity = hugeCapacity(minCapacity);
            }

            // 打印扩容信息(调试用)
            System.out.printf("扩容: %d -> %d (添加第%d个元素时触发)%n", 
                              oldCapacity, newCapacity, size + 1);

            // 实际扩容操作
            elementData = Arrays.copyOf(elementData, newCapacity);
        }

        // 处理超大容量
        private static int hugeCapacity(int minCapacity) {
            if (minCapacity < 0) { // 溢出
                throw new OutOfMemoryError();
            }
            return (minCapacity > MAX_ARRAY_SIZE) ? 
            Integer.MAX_VALUE : MAX_ARRAY_SIZE;
        }

        // 获取当前容量(仅用于演示)
        public int capacity() {
            return elementData.length;
        }
    }
    
    public static void main(String[] args) {
        System.out.println("ArrayList扩容演示:");
        System.out.println("----------------");
        
        DebuggableArrayList<Integer> list = new DebuggableArrayList<>();
        
        // 添加30个元素,观察扩容过程
        for (int i = 0; i < 30; i++) {
            list.add(i);
            System.out.printf("添加元素: %2d, 当前大小: %2d, 当前容量: %2d%n",
                i, i + 1, list.capacity());
        }
    }
}

整体流程概览:

  1. add(E e) 方法调用: 用户调用 add 方法尝试添加一个新元素。
  2. 容量检查 (ensureCapacityInternal): add 方法首先会调用 ensureCapacityInternal 来确保内部数组有足够的容量。
  3. 计算所需容量 (calculateCapacity): 如果是第一次添加元素(即内部数组是 EMPTY_ELEMENTDATA),则计算出初始容量(通常是 DEFAULT_CAPACITY 即 10 或用户指定的 minCapacity 中的较大值)。
  4. 明确容量需求 (ensureExplicitCapacity): 这一步会判断当前数组的实际长度是否小于所需的最小容量。
  5. 触发扩容 (grow): 如果 minCapacity 大于 elementData.length,则会调用 grow 方法执行实际的扩容操作。
  6. 计算新容量: grow 方法会根据当前容量计算一个新的容量值(通常是旧容量的 1.5 倍)。
  7. 容量调整: 如果计算出的新容量仍然小于 minCapacity,或者新容量超过了 MAX_ARRAY_SIZE,则会进行相应的调整。
  8. 数组复制: 使用 Arrays.copyOf 将旧数组的元素复制到一个新的、更大容量的数组中。
  9. 添加元素: 扩容完成后,新元素被添加到 elementData 数组中,size 增加。
在这里插入图片描述
在这里插入图片描述
那为什么不是在原有数组上做扩展?

因为数组是在创建的时候就指定好大小的,不可变。所以说他需要new一个新的数组然后拷贝过去,然后旧的数组会被gc标记为一个没引用的就会被回收掉

sizecapacity 的区别
  • sizeArrayList 中实际存在的元素数量。这是 list.size() 返回的值。
  • capacity:内部数组在不进行扩容的情况下可以容纳的最大元素数量。这是 elementData.length
  • 关系:size 总是小于或等于 capacity。当 size 等于 capacity 时,添加新元素就会触发扩容。
性能特点(时间复杂度)
  • add(E e) (摊销常数时间 O(1)):在列表末尾添加元素。大多数情况下是 O(1)。只有当发生扩容时,由于所有元素需要被复制,它会变为 O(N)。然而,由于扩容不常发生且以固定倍数(1.5 倍)增长,平均(摊销)成本仍然是 O(1)。
  • get(int index) (常数时间 O(1)):通过索引访问元素非常快,因为它是一个直接的数组查找。
  • set(int index, E element) (常数时间 O(1)):替换特定索引处的元素也是 O(1)。
  • add(int index, E element) (线性时间 O(N)):在任意位置(非末尾)插入元素。从该索引开始到末尾的所有元素都必须向右移动一个位置。在最坏情况下(插入到索引 0),所有 N 个元素都需要移动。
  • remove(int index) (线性时间 O(N)):删除任意位置的元素。从 index + 1 到末尾的所有元素都必须向左移动一个位置。在最坏情况下(删除索引 0),所有 N-1 个元素都需要移动。
  • remove(Object o) (线性时间 O(N)):查找并删除一个对象。这首先需要遍历列表找到该对象,然后移动元素。
  • contains(Object o) (线性时间 O(N)):查找元素。
  • 迭代 (O(N)):遍历列表中的所有元素与元素数量成比例。

如果我的内容对你有帮助,请辛苦动动您的手指为我点赞,评论,收藏。感谢大家!!

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-12-18,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • ArrayList是什么?
  • ArrayList的扩容机制是怎样的?
  • 那为什么不是在原有数组上做扩展?
  • size 和 capacity 的区别
  • 性能特点(时间复杂度)
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档