当前位置: 首页 > news >正文

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

在这里插入图片描述

👨‍💻程序员三明治:个人主页

🔥 个人专栏: 《设计模式精解》 《重学数据结构》

🤞先做到 再看见!

ArrayList是什么?

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

ArrayList的扩容机制是怎样的?

流程图如下:

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

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倍仍不够,则使用minCapacityif (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)):遍历列表中的所有元素与元素数量成比例。

如果我的内容对你有帮助,请辛苦动动您的手指为我点赞,评论,收藏。感谢大家!!
在这里插入图片描述

http://www.dtcms.com/a/428158.html

相关文章:

  • 防静电产品东莞网站建设技术支持张家港保税区建设局网站
  • 基于o2o的旅游网站建设手机网站菜单网页怎么做
  • FLASK与JAVA的文件互传并带参数以及流上传(单文件互传亲测)
  • 新发布一个网站公告怎么做平面设计工资
  • 内网穿透系列十四:基于Websocket传输协议实现的异地组网工具 candy,简单安全稳定
  • 国外做图标网站时代汇创网站建设
  • 唐山专业做网站什么是网页和网站
  • yolov13推理示例
  • 深圳外贸网站外贸网站建设wordpress分享此文章
  • 【手撸IM】高性能HTTP API服务设计与实现
  • ManySpeech 使用 C# 开发人工智能应用
  • 网站备案进程查询网站建设方案报价单
  • 什么是多活演练
  • 关陇集团联姻
  • 初始化electron项目运行后报错 electron uninstall 解决方法
  • 织梦系统网站首页upcache=1专题制作 wordpress
  • WinForm中的圆环
  • 西昌网站制作域名后有个wordpress
  • 2、docker入门基本概念
  • 福州网站建设的公司小型企业建设网站
  • 第三章 神经网络
  • 网络公司发生网站建设费分录北京海淀建设中路哪打疫苗
  • 自己开外销网站怎么做WordPress图片无缝
  • 电脑可以连蓝牙耳机吗?
  • 宁波网站建设费用报价传奇类网页游戏
  • Retina(苹果高分辨率屏幕技术)介绍
  • 做网站是怎么赚钱的违法网络管理系统的基本组件包括哪些?
  • 无人机大脑系统与技术解析
  • 珠宝行业网站建设学室内设计就是失业
  • 动态规划方法详解