ArrayList的底层机制

高薪程序员&面试题精讲系列32之说说ArrayList的底层原理及扩容机制 ArrayList底层原理、ArrayList扩容机制、LinkedList底层原理 阅读详情

ArrayList底层机制

构造函数

从源码入手,先看属性值当我们List<String> list = new ArrayList<>();创建一个数组时,默认设置容量为10。

	private static final int DEFAULT_CAPACITY = 10;

    /**
     * 用于传入参数为0,或传入Collection长度0的时候使用,让elementData指向该数组。
     */
    private static final Object[] EMPTY_ELEMENTDATA = {};
	/**
     * 用于默认大小的空实例的共享空数组实例。
     * 我们将其与EMPTY_ELEMENTDATA区分开来,唯一区别在于添加第一个元素时扩容多少。
     * 传入0的扩容为add的数据长度,而空参是扩容到10或者是add的数据长度
     * MARK:无参构造函数 使用该数组初始化 与EMPTY_ELEMENTDATA的区别主要是区分作用,用来减少空数组的存在,优化内存使用 1.8后的优化
     */
	private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
	
	transient Object[] elementData;

再来看看三个构造函数,注意空参构造,即便DEFAULT_CAPACITY = 10,要生成elementData长度为10的Object[]数组,是调用了add才完成的

/**
     * 构造具有指定初始容量的空列表。
     * @param  initialCapacity  列表的初始容量
     * @throws IllegalArgumentException
     */
    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);
        }
    }

    /**
     * 构造一个初始容量为10的空列表。
     * MARK:
     * 这里其实是赋值了一个共享的空数组,数组在第一次添加元素时会判断elementData是否等于DEFAULTCAPACITY_EMPTY_ELEMENTDATA,假如等于则会初始化为DEFAULT_CAPACITY 也就是10
     *
     */
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

    /**
     * 构造一个包含指定元素的列表集合
     * @param c 要将其元素放入此列表的集合
     * @throws NullPointerException if the specified collection is null
     */
    public ArrayList(Collection<? extends E> c) {
        //  这里说明所有的 Collection 都可以用数组来承载
        //  这个步骤可能会抛空指针 NullPointerException
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            //必须是Object数组
            if (elementData.getClass() != Object[].class)
                // 然后再copy一份到 elementData 并不是引用 所有改变不会影响到原先的Collection
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // 有参构造函数 当初始化为空数组时 赋值为 EMPTY_ELEMENTDATA
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

扩容机制

然后就是扩容机制add方法后调用的各种方法

public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
public boolean addAll(Collection<? extends E> c) {
        Object[] a = c.toArray();
        int numNew = a.length;
        ensureCapacityInternal(size + numNew);  // Increments modCount
        System.arraycopy(a, 0, elementData, size, numNew);
        size += numNew;
        return numNew != 0;
    }

// minCapacity就是当前size+1或size+numNew
private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }

//空参构造,第一次add需要扩容,也就是jdk1.8 elementData数组并不是默认就是Object[10],如果不进行add,那也是0
private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }

//如果size+ 1或numNew > elementData.length,则需要扩容
private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

//我们熟知的1.5倍扩容
private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);// 1.5倍扩容后的容量
        if (newCapacity - minCapacity < 0)	//addAll 方法可能传入的集合很大minCapacity大于1.5倍后的结果
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);//Integer.MAX_VALUE,考虑到1.5倍后溢出问题
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

流程图如下:

ArrayList扩容流程

扩容示例:
image-20200522005200798

删除机制

接着我们看一下删除的操作。ArrayList支持两种删除方式:

1、按照下标删除

2、按照元素删除,这会删除ArrayList中与指定要删除的元素匹配的第一个元素

对于ArrayList来说,这两种删除的方法差不多,只不过按照元素删除还需要进行一步遍历获取下表index的过程,底层都是通过System.arraycopy将后面的元素复制覆盖到前面,size减一

//按照下标删除
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;
    }
//按照元素删除,这会删除ArrayList中与指定要删除的元素匹配的第一个元素
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;
    }

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
    }

删除实例:
image-20200522005216923

ArrayList的特点

主要特点如下

image-20200521112023580

优点

1、ArrayList底层以数组实现,是一种随机访问模式,再加上它实现了RandomAccess接口,因此查找也就是get的时候非常快

2、ArrayList在顺序添加一个元素的时候非常方便,只是往数组里面添加了一个元素而已

缺点

1、删除元素的时候,涉及到一次元素复制,如果要复制的元素很多,那么就会比较耗费性能

2、插入元素的时候,如果发生扩容涉及到一次元素复制,如果要复制的元素很多,那么就会比较耗费性能

因此,ArrayList比较适合顺序添加、随机访问的场景。

ArrayList与Vector区别

Vector是ArrayList的线程安全版本,其实现90%和ArrayList都完全一样,区别在于:

1、Vector是线程安全的,几乎所有方法都加上了synchronized修饰,ArrayList是线程非安全的,因此ArrayList更高效

2、Vector可以指定增长因子,如果该增长因子指定了,那么扩容的时候会每次新的数组大小会在原数组的大小基础上加上增长因子;如果不指定增长因子,那么就给原数组大小*2

为什么elementData是使用transient修饰的呢

用transient修饰elementData意味着我不希望elementData数组被序列化。这是为什么?因为序列化ArrayList的时候,ArrayList里面的elementData未必是满的,比方说elementData有10的大小,但是我只用了其中的3个,那么是否有必要序列化整个elementData呢?显然没有这个必要,因此ArrayList中重写了writeObject方法:

private void writeObject(java.io.ObjectOutputStream s)
        throws java.io.IOException{
        // Write out element count, and any hidden stuff
        int expectedModCount = modCount;
        s.defaultWriteObject();

        // Write out size as capacity for behavioural compatibility with clone()
        s.writeInt(size);

        // Write out all elements in the proper order.
        for (int i=0; i<size; i++) {
            s.writeObject(elementData[i]);
        }

        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }
    }

每次序列化的时候调用这个方法,先调用defaultWriteObject()方法序列化ArrayList中的非transient元素,elementData不去序列化它,然后遍历elementData,只序列化那些有的元素,这样:

1、加快了序列化的速度

2、减小了序列化之后的文件大小

SAP PCE私有云MM02修改物料主数据基本单位报错 当前发现只有改为AAAADL(无量纲)单位时会报错,其他的正常。一开始以为配置的ISO码换算有问题,对配置排查后,排除配置问题。最后发现是PCE当前版本的原因,需要打上NOTE。SAP PCE私有云在没有库存和业务单据的情况下,MM02修改物料主数据基本单位报错“EA CPZP MK000000000000:PCS 不可转换为”,详见截图。点击【显示错误】,提示EA CPZP MK000000000000:PCS 不可转换为。从SAP FOR ME下载NOTE 3531359,链接。 阅读详情

相关推荐

杂记 | 使用keycloak实现SSO单点登录(新手向,概念、原理、逻辑、详细步骤、难点解释)

使用keycloak实现SSO单点登录(概念、原理、逻辑、详细步骤、难点解释)

野生猿林仔的博客 2万+

ArrayList详解

1、简介 ArrayList是Java集合框架中的一个重要的类,它继承于AbstractList,实现了List接口,是一个长度可变的集合,提供了增删改查的功能。集合中允许null的存在。ArrayList类还是实现了RandomAccess接口,可以对元素进行快速访问。实现了Serializable接口,说明ArrayList可以被序列化,还有Cloneable接口,可以被复制。和Ve

Valineliu 4万+

基于强化学习方法的PID参数整定

前言 PID控制器在工业界应用非常广泛,但是PID的参数调节一般需要人工根据经验法来试。对于有经验的工程师来说,一般试几次就可以获得满足调节的参数。然而对于新手工程师却很难确定一套比较好的参数。 这里我们采用强化学习的方法来调节PID参数。 这个例子展示了如何使用双延迟深度确定性策略梯度(TD3:twin-delayed deep deterministic policy gradient)强化学习算法来调整PI控制器。整定控制器的性能与使用Control System Tuner app整定的控制器的性能

weixin_42188287的博客 2万+

Java集合篇:ArrayList详解

一、ArrayList概述: ArrayList是实现了List接口的动态数组,所谓动态数组就是他的大小是可变的。实现了所有可选列表操作,并允许包括Null在内的所有元素。除了实现 List 接口外,此类还提供一些方法来操作内部用来存储列表的数组的大小。 每个ArrayList实例都有一个容量,该容量是指用来存储列表元素的数组的大小。默认初始容量是10。默认初始容量为10。随着ArrayLis...

张维鹏的博客 1万+

【Stata双重差分模型】双重差分DID的具体操作步骤

本案例将详细阐述如何使用双重差分法进行实证分析,并展示具体的STATA操作代码。

a519573917的博客 2万+

【位运算进阶之----右移(>>)】

😄嘻嘻,朋友们,大家好!昨天我们学习了左移,今天我们来谈谈右移>>。⭐️简单来说,,也就是去掉一个数的二进制表达的末位,右移一位就去掉一位,右移两位就去掉两位。下面就让我们来详细看看吧!

weixin_73453526的博客 8931

Java集合框架:ArrayList详解

1. ArrayList是以泛型的方式实现的,使用时必须先实例化2. ArrayList实现了randomAccess接口,所以ArrayList支持随机访问3. ArrayList实现了Cloneable接口,ArrayList时可以克隆的4. ArrayList不是线程安全的,(Vector是线程安全的)5. ArrayList是一段连续的空间,可以动态扩容,是动态的顺序表。

weixin_64874291的博客 6488

银河麒麟arm64 V10 SP1桌面系统高效部署onnxruntime推理环境实战

本文详细介绍了在银河麒麟arm64 V10 SP1桌面系统上高效部署onnxruntime推理环境的完整实战流程。从系统环境准备、关键依赖安装,到onnxruntime的安装验证,并以YOLOv5模型部署为例进行性能对比分析,提供了在国产化飞腾D2000平台上进行AI边缘推理的实用指南和避坑技巧。

ss78901的博客 636

ArrayList简介

ArrayList 是一个数组列表。它的主要底层实现是Object数组,但与 Java 中的数组相比,它的容量能动态变化,可看作是一个动态数组结构。特别注意的是,当我们装载的是基本类型的数据 int,long,boolean,short,byte… 的时候,我们只能存储他们对应的包装类。

NRapid&JRapid 3541

NS-3 和Aqua-sim-ng 学习笔记(一)

因为要搞水下网络,OMNET++不太合适,就转Aqua-sim。这是基于NS-2上的模型,NS-2需要额外学一门tcl脚本语言。 正好查到它的一个升级版Aqua-sim-NG,在github上,https://github.com/rmartin5/aqua-sim-ng是基于NS-3的,只需要基本的C++,不用学tcl语言。美滋滋。 装好ubuntu之后,安装NS-3和Aqu...

diboshang8820的博客 2191

AIGC实战——变分自编码器(Variational Autoencoder, VAE)

变分自编码器通过在模型中引入随机性,并限制潜空间中的点的分布来解决自编码器存在的问题。只需进行一些微小的调整,就可以将自编码器转换为变分自编码器,从而使其成为真正的生成模型。在本节中,我们介绍了变分自编码器的基本原理,并使用 `Keras` 实现了一个变分自编码器用于生成 Fashion-MNIST 图像。

盼小辉丶的博客 1万+

ArrayList 扩容机制及底层代码

意思非常简单就是 initialCapacity>0,就按initialCapacity的大小创建集合,initialCapacity==0,就按10的大小创建集合,initialCapacity<0就报错。意思是 elementData数组此时的容量小于添加元素后起码要有的最小容量,此时就要执行grow(minCapacity) 方法进行扩容。意思是如果 elementData是空的数组,就让DEFAULT_CAPACITY和minCapacity比大小。以上为我个人的小分享,如有问题,欢迎讨论!

WHabc2002的博客 1354

stata 工具变量法【计量经济系列(七)】

stata 工具变量法【计量经济系列(七)】 1. 内生性 2. 工具变量3. 2SLS (二阶段最小二乘法) 4. 弱工具变量5. 工具变量外生性的过度识别检验 6. 豪斯曼检验6.1 传统豪斯曼 6.2 杜宾-吴-豪斯曼检验(DWH)

侯小啾技术博客 2万+

ArrayList底层扩容机制

ArrayList学习 List集合下常用的两个实现类是ArrayList和LinkedList,在LinkedList中我们知道了LinkedList的内部是用双向链表实现的,而本文要说的ArrayList是用一个Object型的数组实现的。 ArrayList继承和实现类有哪些 图中红色线代表的是继承关系,蓝色线代表的是实现关系,A表示这是一个抽象类,I表示这是一个接口 Arra...

乙壳虫的博客 5659

Statistics and Machine Learning Toolbox

Statistics and Machine Learning Toolbox(带书签),MATLAB统计学习工具

华为的C语言编程规范.pdf

华为技术有限公司内部技术规范。本规范制定了编写C语言程序的基本原则、规则和建议。从代码的清晰、简洁、可测试、安全、程序效率、可移植各个方面对C语言编程作出了具体指导。

ArrayList 扩容机制【底层原理】

ArrayList采用1.5倍扩容机制实现动态数组功能。当数组容量不足时,会创建1.5倍大小的新数组并复制元素。这种设计既避免了2倍扩容的内存浪费,又比1.2倍扩容减少了操作频率。在极端情况下,若计算容量仍不足,则直接采用所需最小容量。通过位运算(oldCapacity+(oldCapacity>>1))高效计算新容量,兼顾了性能与空间利用率。

beiguo666的博客 1260

RU DOS版、EFI版32bit/64bit BIOS常用工具

DOS版、EFI版的RU工具,可用来查看IO、MEM、PCI、SMBIOS等资源。亲测在EFI SHELL下可用。

【ArrayList 扩容机制源码解析】|打破屏障,彻底搞懂底层扩展机制

这里首先列出 ArrayList 扩容的几个特点,看完这些特点再去阅读体验会比较好 1)`ArrayList` 中维护了一个 `Object` 类型的数组,`elementData`。 2)当每次创建 `ArrayList` 对象的时候,如果使用的是无参构造器,则初始的 `elementData` 的容量为 0,第一次添加的时候则扩容 `elementData` 的容量为 10,如果需要再次扩容,则扩容为原来的 `1.5` 倍 3)如果使用的是指定大小的构造器,则初始的 `elementData` 的

kkq的博客 982

Devexpress16.2.6破解工具

Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具

「集合底层」ArrayList底层扩容机制及源码分析

ArrayList作为List接口的实现类,它是一种可以根据需要动态增长的数组。在Java中标准的数组都是定长的,当一个数组被创建后,它不能再被修改长度,也就是说我们在创建数组时要确定数组所需的长度。但有时我们需要动态程序中获取数组长度,此时,我们就可以使用ArrayList来存储数据,但是它并不是线程安全的。先说结论ArrayList中维护了一个Object类型的数组elementData.[debug看源码]transienttransient表示瞬间,短暂的,表示该属性不会被序列号。

frozenpenguin的博客 1767

ArrayList 的底层与自动扩容机制

ArrayList是基于动态数组实现的顺序表,默认构造方法采用懒加载策略,初始容量为0,首次添加元素时扩容至默认10。底层使用Object数组存储数据,通过1.5倍扩容机制(Arrays.copyOf)实现动态增长。扩容操作具有O(n)时间复杂度但因摊销仍接近O(1)。size记录实际元素数而非数组容量。ArrayList支持快速随机访问(O(1)),但插入删除需移动元素(O(n))。多线程环境下非线程安全,可通过Collections.synchronizedList或CopyOnWriteArrayLi

写代码写久了才发现:人生和程序一样,经常 Bug 多、需求乱,重构难,但总能跑起来。这个博客记录我从迷茫到搞懂、从踩坑到归纳、从混乱到清晰的过程。写给过去迷茫的自己,也写给同样在路上折腾的你。没有炫技,没有玄学,只有真实的思考、实践和成长。 546

ArrayList 的底层实现与扩容机制

ArrayList基于索引的快速访问(O (1) 时间复杂度)灵活的自动扩容机制,无需手动管理容量中间位置的增删操作效率低(需要移动元素)扩容会导致数组复制,产生性能开销线程不安全,多线程环境需额外处理了解这些底层原理,能帮助我们在实际开发中更好地选择和使用ArrayList已知元素数量时指定初始容量频繁在中间位置操作时考虑LinkedList批量添加元素时提前调用减少扩容次数ArrayList。

2401_89052850的博客 477

总结-ArrayList的扩容机制和BigDecimal大数的底层

Java集合扩容机制与BigDecimal精度控制 ArrayList扩容机制:初始空数组,首次添加元素扩容至10,后续每次扩容1.5倍(位运算实现),超过Integer.MAX_VALUE-8时特殊处理。HashMap扩容也采用1.5倍策略。 BigDecimal精度问题:解决浮点数二进制存储导致的精度丢失,内部采用整数数组分段存储大数,维护符号、精度等元数据。通过compareTo()比较数值,equals()会同时比较精度。运算时精确处理符号和精度,特别是除法可避免浮点误差

LUCIAZZZ的博客 694

ArrayList底层实现及扩容机制

注:本篇文章所用的java版本为java8,java8之前的arrayList和hashMap底层实现有着肉眼可见的区别。 在单线程开发中,我们常用的容器就是ArrayList以及HashMap。下面我就这两个类的扩容进行一个对比: 首先,我们打开ArrayList的源码进行查看,在这里我们先来查看ArrayList的初始化方法: (重点关注无参和int型参的方法) 首先,我们先来查看ArrayList无参的方法 /** * Constructs an empty list with an initial

qq_42649567的博客 1150

ArrayList的扩容机制和核心底层数据结构

ArrayList 底层数据结构 transient Object[] elementData; // 用于存储数据的buffer数组 在 ArrayList集合的底层是由一个可变的动态 Object[]数组组成的,由于他是由数组构成的,所以该集合类框架对于随机访问的效率很高,时间复杂度为 O(1)。 public boolean add(E e) { modCount++;// 结构更改次数 add(e, elementData, size);// 套娃

LengDanRan 893

ArrayList 底层扩容机制

4.特殊情况,当突然添加100个元素时,当数组长度仅仅为10,会新建一个长度为110的数组。2.当添加第一个元素时,新建长度为10的数组。3.当数组存满时,数据会扩容1.5倍。1.创建集合时,默认集合长度为0。

alanyGG_的博客 114

ArrayList 的底层操作机制源码分析

ArrayList 的底层操作机制源码分析 扩容机制: 1)ArrayList中维护了一个0bject类型的数组elementData. transient Object[] elementData; //transient表示瞬间,短暂的,表示该属性不会被序列化 2)当创建ArrayList对象时,如果使用的是无参构造器,则初始elementData容量为0,第1次添加,则扩容elementData为10,如需要再次扩容,则扩容elementData为1.5倍。 3)如果使用的是指定大小的构造器,则初

liuyiyi_的博客 398

ArrayList的底层操作机制源码分析

ArrayList的底层操作机制源码分析 底层操作机制 1.ArrayList中维护了一个Object类型的数组elementData transient Object[] elementData;transient 表示瞬间,短暂的,表示该属性不会被序列化 源码解析 2.当创建ArrayList对象时,如果使用的是无参构造器,则初始elementData容量为0,第一次添加,则扩容elementData为10,如果需再次扩容elementData为1.5倍 3.如果使用的是指定大小的构造器,则初始e

weixin_50272652的博客 193

【集合框架ArrayList底层扩容机制】

本文深入解析了Java中ArrayList的动态扩容机制。通过源码分析,揭示了ArrayList初始创建时仅生成空数组,在首次添加元素时才分配默认容量10的空间。其核心扩容策略采用1.5倍增长模式,同时针对批量添加操作会智能调整为所需最小容量。文章详细阐述了size变量的双重作用、扩容时机判断逻辑以及不同场景下的扩容处理,展现了ArrayList如何通过延迟初始化和智能扩容策略实现高效的内存管理与性能平衡。

weixin_64178283的博客 1063
下一篇: 超详细!JDK1.7和JDK1.8 HashMap底层原理
温冷zero
博客等级 码龄7年 2粉丝 5原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值