ArrayList集合底层原理

news2024/7/4 6:30:15

ArrayList集合底层原理

  • ArrayList集合底层原理
    • 1.介绍
    • 2.底层实现
    • 3.构造方法
    • 3.1集合的属性
    • 4.扩容机制
    • 5.其他方法
    • 6.总结

ArrayList集合底层原理

1.介绍

​ ArrayList是List接口的可变数组的实现。实现了所有可选列表操作,并允许包括 null 在 内的所有元素。 每个 ArrayList 实例都有一个容量,该容量是指用来存储列表元素的数组的大小。它是至少等于列表的大小。随着向 ArrayList 中不断添加元素,其容量也自动增长。自动增长会带来数据向新数组的重新拷贝,因此,如果可预知数据量的多少,可在构造 ArrayList 时 指定其容量。

ArrayList集合特点为什么是增删慢、查询快

2.底层实现

底层使用数组实现
transient Object[] elementData;

3.构造方法

​ ArrayList 提供了三种方式的构造器,可以构造一个默认初始容量为 10 的空列表、构造 一个指定初始容量的空列表以及构造一个包含指定 collection 的元素的列表,这些元素按照 该 collection 的迭代器返回它们的顺序排列的。

// 空参构造
ArrayList<String> list1 = new ArrayList<>(); 
// 源码
public ArrayList() {
    /*
    DEFAULTCAPACITY_EMPTY_ELEMENTDATA: 指向private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};空数组.
    注意:默认长度是在第一次添加元素时赋值的数组
    */
     this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
 }

// 指定初始长度
ArrayList<String> list2 = new ArrayList<>(100);
// 源码
public ArrayList(int initialCapacity) {
    // 判断传入的长度大小
    if (initialCapacity > 0) {
        // 根据长度创建数组
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {
        // 如果等于0,返回默认长度数组
        // EMPTY_ELEMENTDATAprivate: 指向static final Object[] EMPTY_ELEMENTDATA = {};
        this.elementData = EMPTY_ELEMENTDATA;
    } else {
        throw new IllegalArgumentException("Illegal Capacity: "+initialCapacity);
    }
}
// 包含指定 collection 的元素的列表
List<String> list = new ArrayList<>();
ArrayList<String> list3 = new ArrayList<>(list);
// 源码
public ArrayList(Collection<? extends E> c) {
    // 先把传入的集合转成数组
    elementData = c.toArray();
    // 判断集合的长度是否等于0
    if ((size = elementData.length) != 0) {
       	// 判断数组字节码类型 为什么要判断,因为c.toArray()有可能转变的不是Object数组
        if (elementData.getClass() != Object[].class)
            // copyOf把参数集合的元素拷贝到定义数组
            // elementData:要复制的数组
            // size:长度
            // Object[].class:要返回的新数组
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    } else {
        //EMPTY_ELEMENTDATA:指向:private static final Object[] EMPTY_ELEMENTDATA = {};
        this.elementData = EMPTY_ELEMENTDATA;
    }
}

3.1集合的属性

//默认容量的大小
private static final int DEFAULT_CAPACITY = 10;

//空数组常量
private static final Object[] EMPTY_ELEMENTDATA = {};

//默认的空数组常量
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

//存放元素的数组,从这可以发现 ArrayList 的底层实现就是一个 Object数组
transient Object[] elementData;

//数组中包含的元素个数
private int size;

//数组的最大上限
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

4.扩容机制

​ 图解:
请添加图片描述

​ 扩容源码:

public boolean add(E e) {
    // 计数器,返回实时增加的元素个数,比如调用size()方法返回的集合元素个数
    modCount++;
    /*
    	1.e:表示现在要添加的元素
    	2.elementData集合底层数组名
    	3.size本次要添加的索引位置,第一次添加size的值为0
    */
    add(e, elementData, size);
    return true;
}

private void add(E e, Object[] elementData, int s) {
    // 判断存入的位置索引
    if (s == elementData.length)
        // 调用grow方法进行扩容
        elementData = grow();
    // 索引位置小于数组长度正常存入
    elementData[s] = e;
    size = s + 1;
}

private Object[] grow() {
    // 第一次存入元素size默认为0,进行长度加1
    return grow(size + 1);
}

private Object[] grow(int minCapacity) {
    /*
    	创建一个新的数组长度为10
    	把原来数组元素拷贝进去
    	minCapacity:传入的容量
    */
    return elementData = Arrays.copyOf(elementData,
                                       newCapacity(minCapacity));
}

private int newCapacity(int minCapacity) {
    // overflow-conscious code
    // 旧容量
    int oldCapacity = elementData.length;
    // 新容量 = 旧容量 * 1.5
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    // 拿新的容量 - 传入的大小 如果结果小于0,
    if (newCapacity - minCapacity <= 0) {
        // 第一次扩容
        // 判断数组是否是同一个数组
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
            // DEFAULT_CAPACITY默认容量=10
            // 比较默认容量与传入容量大小,把最大返回
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        if (minCapacity < 0) // overflow 内存溢出
            throw new OutOfMemoryError();
        return minCapacity;
    }
    // 返回新的长度
    return (newCapacity - MAX_ARRAY_SIZE <= 0)
        ? newCapacity
        : hugeCapacity(minCapacity);
}

ArrayList的add方法,在插入元素之前,它会先检查是否需要扩容,然后再把元素添加到数组中最后一个元素的后面。
在 newCapacity 方法中,我们可以看见,如果当 elementData 为空数组时,它会使用默认的大小去扩容。所以说,
通过无参构造方法来创建 ArrayList 时,它的大小其实是为 0 的,只有在使用到的时候,才会通过 grow 方法去创建
一个大小为 10 的数组。第一个 add 方法的复杂度为 O(1),虽然有时候会涉及到扩容的操作,但是扩容的次数是非常
少的,所以这一部分的时间可以忽略不计。如果使用的是带指定下标的 add方法,则复杂度为 O(n),因为涉及到
对数组中元素的移动,这一操作是非常耗时的。

5.其他方法

// 修改方法
public E set(int index, E element) {
    // 判断是否会发生异常
    Objects.checkIndex(index, size);
    // 根据索引获取元素
    E oldValue = elementData(index);
    // 把传入的元素替换原来的元素
    elementData[index] = element;
    // 返回原来的元素
    return oldValue;
}


// 指定索引处添加,如果当前有元素,则向右移动当前位与该位置的元素以及所有后续元素
public void add(int index, E element) {
    // 判断是否发生异常
    rangeCheckForAdd(index);
    // 记录修改次数, 一般研究线程安全性问题,关注这个变量
    modCount++;
    final int s;
    Object[] elementData;
    /*
    	(s = size):数组长度,下面拷贝使用了
    	(elementData = this.elementData):当前数组,下面有用到
    	
    	判断元素个数是否与数组长度相同
    */
    if ((s = size) == (elementData = this.elementData).length)
        // 数组扩容前面已经讲解
        elementData = grow();
    // 将elementData中从index位置开始,长度为s-index的元素
    // 拷贝到从下标为index+1位置开始的新的elementData数组中
    // 也就是当前位于该位置的元素以及后面的元素向后移动一位
    System.arraycopy(elementData, index,
                     elementData, index + 1,
                     s - index);
    // 把元素赋值到指定索引处
    elementData[index] = element;
    // 长度加一
    size = s + 1;
}

// 返回此列表中指定位置上的元素。
public E get(int index) {
    // 判断异常
    Objects.checkIndex(index, size);
    // 返回元素
    return elementData(index);
}

// 移除指定索引处元素
public E remove(int index) {
    // 判断异常
    Objects.checkIndex(index, size);
    // 临时变量数组赋值
    final Object[] es = elementData;
	// 拿到指定索引处的元素
    @SuppressWarnings("unchecked") E oldValue = (E) es[index];
    fastRemove(es, index);
    return oldValue;
}
private void fastRemove(Object[] es, int i) {
    // 记录修改次数, 一般研究线程安全性问题,关注这个变量
    modCount++;
    // 定义临时变量
    final int newSize;
    // 判断数组长度-1,是否大于变量索引
    if ((newSize = size - 1) > i)
        /*
        	将es中从索引i+1位置开始,长度为newSize - i的元素
        	拷贝到从下表i + 1位置开始的新的es数组中
        	也就是当前位于该位置的元素以及后面的元素向前一定一位
        */
        System.arraycopy(es, i + 1, es, i, newSize - i);
    // 临时变量在赋值为null
    es[size = newSize] = null;
}
// 移除指定元素
public boolean remove(Object o) {
    // 定义临时数组,并赋值
    final Object[] es = elementData;
    // 定义临时长度变量,并赋值
    final int size = this.size;
    int i = 0;
    // found标识,发现指定元素
    found: {
        if (o == null) {
            for (; i < size; i++)
                if (es[i] == null)
                    break found;
        } else {
            for (; i < size; i++)
                if (o.equals(es[i]))
                    break found;
        }
        return false;
    }
    // 移除元素并移动
    fastRemove(es, i);
    return true;
}

6.总结

从上述代码中可以看出,数组进行扩容时,会将老数组中的元素重新拷贝一份到新的数组中,每次数组容量的增长大约是其原容量的 1.5 倍。这种操作的代价是很高的,因此在实际使用时,我们应该尽量避免数组容量的扩张。当我们可预知要保存的元素的多少时,要在构造 ArrayList 实例时,就指定其容量,以避免数组扩容的发生。或者根据实际需求,通过调用 ensureCapacity 方法来手动增加 ArrayList 实例的容量。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/393949.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

静态库和动态库的打包与使用

静态库和动态库 静态库和动态库的打包 生成可执行程序时链接使用 运行可执行程序时加载使用 提前声明&#xff0c;笔者示例的文件有mian.c/child.c/child.h。OK&#xff0c;我们先了解一下&#xff0c;库文件是什么&#xff1f;它其实就是打包了一堆实现常用功能的代码文件. ⭐…

Python之re库用法细讲

文章目录前言一、使用 re 模块的前期准备工作二、使用 re 模块匹配字符串1. 使用 match() 方法进行匹配2. 使用 search() 方法进行匹配3. 使用 findall() 方法进行匹配三、使用 re 模块替换字符串四、使用 re 模块分割字符串总结前言 在之前的博客中我们学习了【正则表达式】的…

C++ typedef用法详解

typedef的4种常见用法&#xff1a;给已定义的变量类型起个别名定义函数指针类型定义数组指针类型为复杂的声明定义一个新的简单的别名总结一句话&#xff1a;“加不加typedef&#xff0c;类型是一样的"&#xff0c;这句话可以这样理解&#xff1a;没加typedef之前如果是个…

云原生架构设计原则及典型技术

云原生是面向云应用设计的一种思想理念&#xff0c;充分发挥云效能的最佳实践路径&#xff0c;帮助企业构建弹性可靠、松耦合、易管理可观测的应用系统&#xff0c;提升交付效率&#xff0c;降低运维复杂度。代表技术包括不可变基础设施、服务网格、声明式 API 及 Serverless 等…

Apk加固后多渠道打包

之前一直使用360加固宝进行apk的加固打包&#xff0c;可以一键加固并打多渠道打包。但是&#xff0c;现在360加固宝收费了&#xff0c;在进行加固&#xff0c;多渠道打包&#xff0c;就得一步一步自己操作了&#xff0c;会很繁琐。所以&#xff0c;本文使用 360加固美团Wallet …

c++11 标准模板(STL)(std::unordered_map)(五)

定义于头文件 <unordered_map> template< class Key, class T, class Hash std::hash<Key>, class KeyEqual std::equal_to<Key>, class Allocator std::allocator< std::pair<const Key, T> > > class unordered…

Java开发 - 单点登录初体验(Spring Security + JWT)

目录​​​​​​​ 前言 为什么要登录 登录的种类 Cookie-Session Cookie-Session-local storage JWT令牌 几种登陆总结 用户身份认证与授权 创建工程 添加依赖 启动项目 Bcrypt算法的工具 创建VO模型类 创建接口文件 创建XML文件 补充配置 添加依赖 添加配…

凭一部手机,7天赚20万?会剪辑的人有多吃香!

影视剪辑容易遇到哪些问题&#xff1a; 1、视频格式格式不对&#xff0c;剪辑软件不支持&#xff1b; 2、视频封面不会做&#xff1b; 3、PR导出视频时&#xff0c;没办法做其他事&#xff0c;效率不高&#xff1b; 4、自己配音不好听&#xff0c;配音软件又不好找&#xff1b;…

第14章 局部波动率模型

这学期会时不时更新一下伊曼纽尔德曼&#xff08;Emanuel Derman&#xff09; 教授与迈克尔B.米勒&#xff08;Michael B. Miller&#xff09;的《The Volatility Smile》这本书&#xff0c;本意是协助导师课程需要&#xff0c;发在这里有意的朋友们可以学习一下&#xff0c;思…

影响redis性能的一些潜在因素

影响 Redis 性能的 5 大方面的潜在因素&#xff0c;分别是&#xff1a; Redis 内部的阻塞式操作&#xff1b; CPU 核和 NUMA 架构的影响&#xff1b; Redis 关键系统配置&#xff1b; Redis 内存碎片&#xff1b; Redis 缓冲区。 先学习了解下 Redis 内部的阻塞式操作以及应对的…

【数据架构系列-03】数据仓库、大数据平台、数据中台... 我不太认同《DataFun数据智能知识地图》中的定义

关注DataFunTalk有2年多了&#xff0c;DataFun确实像创始人王大川讲的那样&#xff0c;践行选择、努力和利他原则&#xff0c;专注于大数据、人工智能技术应用的分享与交流&#xff0c;秉承着开源开放的精神&#xff0c;免费的共享了很多有营养的行业实践专业知识&#xff0c;对…

1.win10环境搭建Elasticsearch7.2.0环境

环境介绍jdk1.8安装Elasticsearch7.2.0下载安装包直接解压进入到bin目录&#xff0c;双击elasticsearch.bates启动成功访问http://localhost:9200/jdk版本1.8,很有可能因为jdk版本的问题es启动失败支持连接https://www.elastic.co/cn/support/matrix#matrix_jvm安装Kibana7.2.0…

云计算介绍,让你更了解云计算

同学们好&#xff01; 第一次接触IT行业吗&#xff1f;没关系&#xff0c;看完这篇文章肯定会让你不再陌生。给自己几分钟时间&#xff0c;认真看完哦&#xff01; 1、不知道什么是云计算&#xff1f; 网络计算云计算 官方定义是&#xff1a;通过网络提供可伸缩的分布式计算…

建立相关在线社群的3个简单步骤

在线社群管理和社交媒体营销通常被视为一回事。虽然社群管理确实是社交媒体营销的一个关键部分&#xff0c;但它的意义超越了社交媒体的内容发布。因此&#xff0c;在线社群对于企业的数字营销十分重要。创建、维护和发展社群不是一件容易的工作&#xff0c;也不是一个快速的过…

枚举学习贴

1. 概述 1.1 是什么 枚举对应英文(enumeration, 简写 enum)枚举是一组常量的集合。可以这里理解&#xff1a;枚举属于一种特殊的类&#xff0c;里面只包含一组有限的特定的对象 1.2 枚举的二种实现方式 自定义类实现枚举使用 enum 关键字实现枚举 1.3 什么时候用 存在有限…

利用HGT聚类单细胞多组学数据并推理生物网络

单细胞多组学数据允许同时对多种组学数据进行定量分析&#xff0c;以捕捉复杂的分子机制和细胞异质性。然而现有的工具不能有效地推断不同细胞类型的活性生物网络以及这些网络对外部刺激的反应。 来自&#xff1a;Single-cell biological network inference using a heterogen…

操作系统_Linux_问答_2023_自用

GeeksforGeeks&#xff08;https://www.geeksforgeeks.org/&#xff09;&#xff1a;GeeksforGeeks是一个技术学习平台&#xff0c;它提供了广泛的操作系统知识&#xff0c;包括操作系统概念、进程管理、内存管理、文件系统等内容。IBM Developer&#xff08;https://developer…

代理模式-大话设计模式

一、定义 代理模式的定义&#xff1a;为其他对象提供一种代理以控制对这个对象的访问。在某些情况下&#xff0c;一个对象不适合或者不能直接引用另一个对象&#xff0c;而代理对象可以在客户端和目标对象之间起到中介的作用。 著名的代理模式例子为引用计数&#xff08;英语…

如何基于AI智能视频技术实现公园景区的人流量实时统计?

一、方案背景春暖花开的季节来临&#xff0c;外出旅游的人群也越来越多。无论是景区、公园、博物馆、步行街等场所&#xff0c;客流超载非常大&#xff0c;给游客带来的体验较差&#xff0c;同时也存在安全隐患。当前景区面临的管理痛点包括&#xff1a;客流信息查询难&#xf…

Hadoop3.1.3单机(伪分布式配置)

参考&#xff1a;林子雨老师网站博客 Hadoop安装搭建伪分布式教程&#xff08;全面&#xff09;吐血整理 环境 Vmare12 Ubuntu16.04 创建Hadoop用户 若安装Ubuntu不是用的“hadoop”用户&#xff0c;则需要增加一个名为"hadoop"的用户 直接快捷键ctrlaltt或者点…