ArrayList底层实现原理

news2024/11/20 14:40:51

ArrayList

ArrayList最早出现在 JDK 1.2中,底层基于数组实现,它是一个动态数组列表结构的容器。

  • 元素有序,可重复
  • 增删元素的速度慢。每次增加删除元素,都需要更改数组长度、拷贝元素及移动元素位置。
  • 查询元素的速度快。底层数据结构是基于Object 数组,可以根据 地址+索引的方式快速获取对应位置上的元素。
  • 实现 Serializable 标记性接口。ArrayList 实现该标记性接口可提供为类提供序列化和反序列化功能,这意味着 ArrayList 支持序列化,能通过序列化去传输。
  • 实现 Cloneable 标记性接口,提供了克隆功能。
  • 实现 RandomAccess 标记性接口。为 ArrayList 提供了随机访问功能,也就是通过下标获取元素对象的功能。
  • 实现 List 接口,是 List 的实现类之一。
  • 继承了 AbstractList接口,可以使用 for-each迭代。
ArrayList类图UML

ArrayList 的数据结构

ArrayList最早出现在 JDK 1.2中,底层基于数组实现,它是一个动态数组列表结构的容器。​

源码剖析

  • 成员变量

//默认容量10
private static final int DEFAULT_CAPACITY = 10;
//空对象数组。使得所有ArrayList空实例都指向同一个空数组。
private static final Object[] EMPTY_ELEMENTDATA = {};
//默认空对象数组。
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//存储ArrayList元素的数组缓冲区,ArrayList的容量是这个数组缓冲区的长度。
transient Object[] elementData; // non-private to simplify nested class access
//ArrayList的大小
private int size;

ArrayList 构造方法

无参构造方法

public ArrayList() {
   this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

jdk 1.2 ~ jdk 1.6 中,ArrayList 的确是会通过空参构造方法生成一个指定底层数据结构容量为 10 的空数组。

jdk 1.7 后,ArrayList 的空参构造方法为了避免无用内存占用,仅仅只是创建了一个底层数据结构长度为 0 的空数组。只有在初次添加元素时才将容量扩容为 10。

指定初始容量值的构造方法

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);
   }
}

包含指定集合元素列表的构造方法 

public ArrayList(Collection<? extends E> c) {
    Object[] a = c.toArray();
    if ((size = a.length) != 0) {
        if (c.getClass() == ArrayList.class) {
                elementData = a;
        } else {
                elementData = Arrays.copyOf(a, size, Object[].class);
        }
     } else {
       // replace with empty array.
       elementData = EMPTY_ELEMENTDATA;
  }
}

add添加方法

add(E e) 将元素添加到列表末尾方法

/**
* 将指定的元素追加到此列表的末尾。
* @param e 要添加到此列表的元素
*/
public boolean add(E e){
    // 添加元素之前,先调用 ensureCapacityInternal 对内部容量进行校验
    // 因为要添加一个元素,故方法里是 size + 1
    ensureCapacityInternal(size +1);
    // 元素添加进去实质就是给最后x一个数组元素赋值
    elementData[size++]= e;
    return true;
}

ensureCapacityInternal() 方法

private void ensureCapacityInternal(int minCapacity) {
   ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

//计算得到最小扩容量
private static int calculateCapacity(Object[] elementData, int minCapacity) {
    // 判断集合存数据的数组是否等于空容量的数组,实际就是看数组有没有存在数据
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        // 通过最小容量和默认容量求出最大值
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    return minCapacity;
}

private void ensureExplicitCapacity(int minCapacity) {
     modCount++;
    // 实际修改集合次数++ (在扩容的过程中没用,主要是用于迭代器中)
    // overflow-conscious code预防溢出
    if (minCapacity - elementData.length > 0)
            //调用grow方法进行扩容
            grow(minCapacity);
}

  add添加概述:

  1. 当添加 1 个元素到 ArrayList 中时,如若 ArrayList 为没有存放任何元素的空集合,那么在执行 ensureCapacityInternal()中 calculateCapacity() 方法过后,minCapacity 会变为 10。此时, minCapacity-elementData.length>0成立,会进入到 grow(minCapacity) 方法。
  2. 当 add 添加第 2 个元素时,minCapacity 为 2,此时 elementData.length (容量)在添加第一个元素后扩容成 10 了。此时, minCapacity-elementData.length>0 不成立,所以不会进入 grow(minCapacity) 方法。
  3. 倘若一直添加元素,直至添加第 11 个元素, minCapacity-elementData.length>0成立(即 11 - 10 > 0),进入 grow() 方法进行扩容。

grow()扩容方法

 private void grow(int minCapacity) {
        // 记录原数组的实际长度
        int oldCapacity = elementData.length;
        // 核心扩容算法,扩容后的容量为原容量的 1.5 倍。
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        // 扩容后的容量小于最小需要的容量,就把最小需要容量当作数组的新容量
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        // 检查新容量超出了ArrayList所定义的最大容量,就调用 hugeCapacity() 来比较 minCapacity 
        // 和 MAX_ARRAY_SIZE
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
}

//获取最大容量 Integer.MAX_VALUE
private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        // 如果 minCapacity 大于 MAX_ARRAY_SIZE,则新容量则为 Interger.MAX_VALUE,否则,新容量                
        // 大小则为 MAX_ARRAY_SIZE。
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
}

add(int index, E element) 在指定索引处添加元素方法

public void add(int index, E element) {
        // 添加范围检查
        rangeCheckForAdd(index);
        
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        // arraycopy()方法实现数组自己复制自己
        // elementData:源数组;index:源数组中的起始位置;
        // elementData:目标数组;index + 1:目标数组中的起始位置;
        // size - index:要复制的数组元素的数量;
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;
        size++;
}

//add和addAll使用的rangeCheck版本
private void rangeCheckForAdd(int index) {
        //如若超出范围则抛出异常
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

ensureCapacity方法

ensureCapacity 方法ArrayList内部没有被调用过,是提供给用户调用,主要作用是:

增加ArrayList实例的容量,如果必需的,以确保它至少可以容纳元素的数量由最小容量参数指定。

public void ensureCapacity(int minCapacity) {
        int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
            // any size if not default element table
            ? 0
            // larger than default for default empty table. It's already
            // supposed to be at default size.
            : DEFAULT_CAPACITY;

        if (minCapacity > minExpand) {
            ensureExplicitCapacity(minCapacity);
        }
}

最好在 add 大量元素之前用 ensureCapacity 方法,以减少增量重新分配的次数。

trimToSize方法 

ensureCapacity 方法ArrayList内部没有被调用过,是提供给用户调用,主要作用是:

将elementData数组调整为ArrayList中实际元素个数大小的容量

/**
 * 将ArrayList的实际容量调整为实际元素总个数大小,原是数组容量大小
 */ 
public void trimToSize() {
        // modCount用来记录修改次数,是父类AbstractList中的属性
        modCount++;// 修改次数加1
        // 如果ArrayList中实际元素个数小于数组长度,那么就清除掉数组中空的元素,然后将数组长度设置为ArrayList中实际元素个数
        if (size < elementData.length) {
            // 判断ArrayList中是否没有元素,即0个元素,则将elementData设置为一个空数组
            // 如果元素个数大于0,则调用Arrays.copyOf()方法重新生成一个长度为size的数组,然后赋给elementData
            elementData = (size == 0)
              ? EMPTY_ELEMENTDATA
              : Arrays.copyOf(elementData, size);
        }
}

默认初始容量是0或10,当添加的元素个数大于10后,会自动扩容,容量变为原来的1.5倍,也就是说elementData数组的长度是15,但如果在添加10个元素之后,只添加了1个元素,现在ArrayList的size其实是11,但elementData数组的length却是15,那么还有4个数组空间没有利用起来,浪费资源,就可以调用该方法调整elementData数组,将其设置为length等于size的数组,底层是复制了一个长度为size的elementData数组返回。

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

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

相关文章

qt简易网络聊天室 数据库的练习

qt网络聊天室 服务器&#xff1a; 配置文件.pro QT core gui networkgreaterThan(QT_MAJOR_VERSION, 4): QT widgetsCONFIG c11# The following define makes your compiler emit warnings if you use # any Qt feature that has been marked deprecated (the exac…

酷派30/锋尚40/大观40S首发解锁BL+完美root权限+去除密码黑砖线刷修复

早前的中华酷联&#xff0c;随着时代的发展&#xff0c;酷派手机虽热发展的并没有其他手机那么快&#xff0c;但也 是坚强的活了下来。目前主打机型为Cool系列&#xff0c;最高为Cool30机型&#xff0c;并且发布酷派锋尚 40酷派大观40S&#xff0c;起头并进。该系列机型&#x…

动手学深度学习(五)Kaggle房价预测

Kaggle房价数据集&#xff0c;前四个为房价特征&#xff0c;最后一个为标签&#xff08;房价&#xff09;。 一、下载数据集 import numpy as np import pandas as pd import torch from torch import nn from d2l import torch as d2l import hashlib import os import tarfi…

XSS简单介绍

目录 一、认识XSS 1.XSS原理 2.XSS分类 二、XSS漏洞复现 1.搭建靶机进行复现 2.案例解析 2.1第一关 2.2第二关 2.3第三关 2.4第四关 一、认识XSS 1.XSS原理 XSS跨站脚本攻击是指恶意攻击者往Web页面里插入恶意Script代码&#xff0c;当用户浏览该页之时&#xff0c;…

linux并发服务器 —— linux网络编程(七)

网络结构模式 C/S结构 - 客户机/服务器&#xff1b;采用两层结构&#xff0c;服务器负责数据的管理&#xff0c;客户机负责完成与用户的交互&#xff1b;C/S结构中&#xff0c;服务器 - 后台服务&#xff0c;客户机 - 前台功能&#xff1b; 优点 1. 充分发挥客户端PC处理能力…

分布式锁之redis实现

docker安装redis 拉取镜像 docker pull redis:6.2.6 查看镜像 启动容器并挂载目录 需要挂在的data和redis.conf自行创建即可 docker run --restart always -d -v /usr/local/docker/redis/redis.conf:/usr/local/etc/redis/redis.conf -v /usr/local/docker/redis/data:/dat…

leetcode986. 区间列表的交集(java)

区间列表的交集 题目描述贪心 - 合并区间代码演示 题目描述 难度 - 中等 leetcode986. 区间列表的交集 给定两个由一些 闭区间 组成的列表&#xff0c;firstList 和 secondList &#xff0c;其中 firstList[i] [starti, endi] 而 secondList[j] [startj, endj] 。每个区间列表…

【数学建模竞赛】超详细Matlab二维三维图形绘制

二维图像绘制 绘制曲线图 g 是表示绿色 b--o是表示蓝色/虚线/o标记 c*是表示蓝绿色(cyan)/*标记 ‘MakerIndices,1:5:length(y) 每五个点取点&#xff08;设置标记密度&#xff09; 特殊符号的输入 序号 需求 函数字符结构 示例 1 上角标 ^{ } title( $ a…

Arthas教程 - 命令篇 (二)

目录 一、Attach 黏附一个进程 1.1 准备代码 1.2 启动Demo 1.3 启动arthas 1.4 通过浏览器连接arthas 二、常用命令 2.1 dashboard 仪表盘 2.2 cls 清屏 2.3 thread 线程 2.4 jad 反编译类 2.5 watch 监视 2.6 退出arthas 三、基础命令 3.1 help 3.2 cat 3.3 …

小米新机代号“Manet”:搭载高通8 Gen 3 处理器 + 金属中框设计

根据数码闲聊站和体验more的消息爆料&#xff0c;小米Redmi K70 Pro被代号为“Manet”&#xff0c;将搭载高通SM8650处理器&#xff0c;这是骁龙8 Gen 3移动平台的一部分。该处理器基于台积电N4P工艺制程打造&#xff0c;具有强大的性能表现。 CPU包含1*3.19GHz X45*2.96GHz A7…

Python Opencv实践 - 矩形轮廓绘制(直边矩形,最小外接矩形)

import cv2 as cv import numpy as np import matplotlib.pyplot as pltimg cv.imread("../SampleImages/stars.png") plt.imshow(img[:,:,::-1])img_gray cv.cvtColor(img, cv.COLOR_BGR2GRAY) #通过cv.threshold转换为二值图 ret,thresh cv.threshold(img_gray,…

Error from server (NotFound): pods “nginx-57d84f57dc-b866m“ not found

原因&#xff1a;机房断电&#xff0c;导致服务重启 [rootmaster1 logs]# kubectl get pod NAME READY STATUS RESTARTS AGE nginx-57d84f57dc-57fkf 1/1 Running 0 75s [rootmaster1 logs]# kubectl logs -f nginx-5…

nginx使用详解

文章目录 一、前言二、nginx使用详解2.1、nginx特点2.2 静态文件处理2.3 反向代理2.4 负载均衡2.5 高级用法2.5.1 正则表达式匹配2.5.2 重定向 三、总结 一、前言 本文将详细介绍nginx的各个功能使用&#xff0c;主要包括 二、nginx使用详解 2.1、nginx特点 高性能&#xff…

【JVM】垃圾收集算法

文章目录 分代收集理论标记-清除算法标记-复制算法标记-整理算法 分代收集理论 当前商业虚拟机的垃圾收集器&#xff0c;大多数都遵循了“分代收集”&#xff08;Generational Collection&#xff09;[1]的理论进 行设计&#xff0c;分代收集名为理论&#xff0c;实质是一套符…

C#循环定时上传数据,失败重传解决方案,数据库标识

有些时候我们需要定时的上传一些数据库的数据&#xff0c;在数据不完整的情况下可能上传失败&#xff0c;上传失败后我们需要定时在重新上传失败的数据&#xff0c;该怎么合理的制定解决方案呢&#xff1f;下面一起看一下&#xff1a; 当然本篇文章只是提供一个思路&#xff0…

windows系统bat脚本调用powershell脚本

前言 项目上有些项目既使用了bat脚本&#xff0c;又使用了powershell脚本&#xff1b; 需要两种脚本配合使用&#xff1b; bat调用powershell 不隐藏窗口运行 bat脚本代码&#xff1a;执行当前路径下的1.ps1脚本文件 start powershell .\1.ps1pause powershell脚本代码&…

025: vue父子组件中传递方法控制:$emit,$refs,$parent,$children

第025个 查看专栏目录: VUE ------ element UI 专栏目标 在vue和element UI联合技术栈的操控下&#xff0c;本专栏提供行之有效的源代码示例和信息点介绍&#xff0c;做到灵活运用。 &#xff08;1&#xff09;提供vue2的一些基本操作&#xff1a;安装、引用&#xff0c;模板使…

prometheus通过blackbox-exporter监控web站点证书

1 概述 线上站点普遍是https&#xff0c;因此监控https web站点的证书的过期时间&#xff0c;是一个基础性需求。例如&#xff0c;证书过期会导致tls握手失败&#xff0c;进而导致用户无法正常访问web站点。 blackbox-expoter是一个web服务&#xff0c;它暴露了一个接口&#…

如何在面试中处理竞争与压力

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

WebSocket与SSE区别

一&#xff0c;websocket WebSocket是HTML5下一种新的协议&#xff08;websocket协议本质上是一个基于tcp的协议&#xff09; 它实现了浏览器与服务器全双工通信&#xff0c;能更好的节省服务器资源和带宽并达到实时通讯的目的 Websocket是一个持久化的协议 websocket的原理 …