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

Java集合框架之ArrayList源码分析

文章目录

    • 简介
    • ArrayList底层数据结构
    • 初始化
    • 集合操作
      • 追加元素
      • 插入数据
      • 删除数据
      • 修改数据
      • 查找
    • 扩容操作
    • 总结

简介

ArrayList是Java提供的线性集合,本篇笔记将从源码(java SE 17)的角度学习ArrayList:

  • 什么是ArrayList?
  • ArrayList底层数据结构是怎么实现的?
  • 作为一个容器,分析增删改查的过程
  • ArrayList的扩容机制

ArrayList底层数据结构

public class ArrayList<E> extends AbstractList<E>implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{@java.io.Serialprivate static final long serialVersionUID = 8683452581122892189L;/*** Default initial capacity.*/private static final int DEFAULT_CAPACITY = 10;/*** Shared empty array instance used for empty instances.*/private static final Object[] EMPTY_ELEMENTDATA = {};/*** Shared empty array instance used for default sized empty instances. We* distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when* first element is added.*/private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};/*** The array buffer into which the elements of the ArrayList are stored.* The capacity of the ArrayList is the length of this array buffer. Any* empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA* will be expanded to DEFAULT_CAPACITY when the first element is added.*/transient Object[] elementData; // non-private to simplify nested class access/*** The size of the ArrayList (the number of elements it contains).** @serial*/private int size;...}

由ArrayList的定义可知,ArrayList继承了AbstractList抽象类,实现了List、RandomAccess、Cloneable、Serializable接口

通过这一行:

/*** The array buffer into which the elements of the ArrayList are stored.* The capacity of the ArrayList is the length of this array buffer. Any* empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA* will be expanded to DEFAULT_CAPACITY when the first element is added.*/transient Object[] elementData; // non-private to simplify nested class access

这就是ArrayList维护的底层数据结构,用于存放元素,是一个Object数组。还注意到使用了一个关键字:transient ,该关键字是在对ArrayList进行序列化时,禁止对该字段进行序列化。该字段是默认访问权限

初始化

    /*** Constructs an empty list with the specified initial capacity.** @param  initialCapacity  the initial capacity of the list* @throws IllegalArgumentException if the specified initial capacity*         is negative*/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);}}/*** Constructs an empty list with an initial capacity of ten.*/public ArrayList() {this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;}/*** Constructs a list containing the elements of the specified* collection, in the order they are returned by the collection's* iterator.** @param c the collection whose elements are to be placed into this list* @throws NullPointerException if the specified collection is null*/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;}}

ArrayList提供了三个构造器:

  • 空的构造器
public ArrayList() {this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
使用该构造器创建一个ArrayList时,统一使用默认的空的一个数组来初始化elementData
  • 指定容量
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);}
}
当使用一个整型变量初始化ArrayList时,根据initialCapacity的大小:- 小于0会抛出非法参数异常
- 等于0使用内部维护的EMPTY_ELEMENTDATA来创建
- 大于0,此时创建一个指定大小的Object数组
  • 通过Collection来创建,主要用于从其他类型的集合创建ArrayList

集合操作

ArrayList源码中提供了一堆add函数,但是可以将添加元素的操作分为两类:

  • 追加元素
  • 插入元素

追加元素

在末尾追加数据调用的都是同一个方法

    private void add(E e, Object[] elementData, int s) {if (s == elementData.length)elementData = grow();elementData[s] = e;size = s + 1;}

这里的elementData.length其实就是当前数组的容量,s这里传入的是size,也就是当前数组中实际元素的个数,首先会判断是否需要扩容,然后将数据追加到数组末尾。

插入数据

    public void add(int index, E element) {rangeCheckForAdd(index);modCount++;final int s;Object[] elementData;if ((s = size) == (elementData = this.elementData).length)elementData = grow();System.arraycopy(elementData, index,elementData, index + 1,s - index);elementData[index] = element;size = s + 1;}

这里的操作也比较常规,就是将数组index后面的元素向后面一次迁移,然后将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;if ((newSize = size - 1) > i)System.arraycopy(es, i + 1, es, i, newSize - i);es[size = newSize] = null;}

逻辑也很简单,没啥说的,就是使用后面的数据覆盖掉index位置的数据

修改数据

    public E set(int index, E element) {Objects.checkIndex(index, size);E oldValue = elementData(index);elementData[index] = element;return oldValue;}

没什么说的

查找

E elementData(int index) {return (E) elementData[index];
}public E get(int index) {Objects.checkIndex(index, size);return elementData(index);
}

扩容操作

从上面有些方法的API中看到,如果向集合中添加元素时,此时会检查ArrayList的容量,如果容量不足会引发扩容,主要调用grow方法

    private Object[] grow() {return grow(size + 1);}private Object[] grow(int minCapacity) {int oldCapacity = elementData.length;if (oldCapacity > 0 || elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {int newCapacity = ArraysSupport.newLength(oldCapacity,minCapacity - oldCapacity, /* minimum growth */oldCapacity >> 1           /* preferred growth */);return elementData = Arrays.copyOf(elementData, newCapacity);} else {return elementData = new Object[Math.max(DEFAULT_CAPACITY, minCapacity)];}}

这里的preferred growth(参考增量)是原数组大小的一半,minGrowth是最小增量。

扩容可以分为四种情况:

  • 如果原数组长度为0或者为默认的空数组对象

    其实就是第一次向一个空的ArrayList添加元素,此时返回一个默认容量的数组, 默认容量为 10

  • 如果根据计算得到的参考分配长度小于等于SOFT_MAX_ARRAY_LENGTH ,就返回该参考长度(这里我看网上很多文章说是扩容1.5倍,这其实是当最小增量小于原长度的一半时才发生的,所以并不准确,或者通过for循环依次追加时会发生,但是当批量添加时并不一定是1.5倍)

    public static int newLength(int oldLength, int minGrowth, int prefGrowth) {// preconditions not checked because of inlining// assert oldLength >= 0// assert minGrowth > 0int prefLength = oldLength + Math.max(minGrowth, prefGrowth); // might overflowif (0 < prefLength && prefLength <= SOFT_MAX_ARRAY_LENGTH) {return prefLength;} else {// put code cold in a separate methodreturn hugeLength(oldLength, minGrowth);}}
  • 如果参考长度(prefLength)大于SOFT_MAX_ARRAY_LENGTH , 如果实际需要的容量小于SOFT_MAX_ARRAY_LENGTH,就分配SOFT_MAX_ARRAY_LENGTH
    private static int hugeLength(int oldLength, int minGrowth) {int minLength = oldLength + minGrowth;if (minLength < 0) { // overflowthrow new OutOfMemoryError("Required array length " + oldLength + " + " + minGrowth + " is too large");} else if (minLength <= SOFT_MAX_ARRAY_LENGTH) {return SOFT_MAX_ARRAY_LENGTH;} else {return minLength;}}
  • 实际需要的长度大于SOFT_MAX_ARRAY_LENGTH,要多少给多少

总结

通过简单分析ArrayList的源码,学习了Java中ArrayList的一些通用操作,增删改查以及扩容,当然,翻看源码还可以发现,ArrayList还提供了很多批量操作的API,其逻辑也不复杂。

ArrayList内部包含了一个实现了Iterator接口的内部类,用于遍历操作,但是Java的集合框架中,迭代器是一个通用的操作接口,后面再独立拿出来进行分析。

ArrayList中,扩容操作涉及到内存的分配和数据迁移操作,如果程序频繁发生扩容将会降低程序的性能,此时可以考虑提前预估ArrayList的大小,提前进行内存分配,减少内存重分配发生的次数。

ArrayList的查找和插入操作的平均时间复杂度是O(N), 如果在频繁插入和查找的场景可以尝试使用更高效的数据结构来代替。

相关文章:

Java集合框架之ArrayList源码分析

文章目录 简介ArrayList底层数据结构初始化集合操作追加元素插入数据删除数据修改数据查找 扩容操作总结 简介 ArrayList是Java提供的线性集合&#xff0c;本篇笔记将从源码(java SE 17)的角度学习ArrayList&#xff1a; 什么是ArrayList&#xff1f;ArrayList底层数据结构是…...

TensorFlow入门(二十、损失函数)

损失函数 损失函数用真实值与预测值的距离指导模型的收敛方向,是网络学习质量的关键。不管是什么样的网络结构,如果使用的损失函数不正确,最终训练出的模型一定是不正确的。常见的两类损失函数为:①均值平方差②交叉熵 均值平方差 均值平方差(Mean Squared Error,MSE),也称&qu…...

MySQL中死锁

数据库的死锁是指不同的事务在获取资源时相互等待&#xff0c;导致无法继续执行的一种情况。当发生死锁时&#xff0c;数据库会自动中断其中一个事务&#xff0c;以解除死锁。在数据库中&#xff0c;事务可以分为读事务和写事务。读事务只需要获取读锁&#xff0c;而写事务需要…...

【LeetCode刷题(数据结构)】:给定一个链表 每个节点包含一个额外增加的随机指针 该指针可以指向链表中的任何节点或空节点 要求返回这个链表的深度拷贝

给你一个长度为 n 的链表&#xff0c;每个节点包含一个额外增加的随机指针 random &#xff0c;该指针可以指向链表中的任何节点或空节点 构造这个链表的 深拷贝。 深拷贝应该正好由 n 个 全新 节点组成&#xff0c;其中每个新节点的值都设为其对应的原节点的值。新节点的 next…...

uniapp封装loading 的动画动态加载

实现效果 html代码 <view class"loadBox" v-if"loading"><img :src"logo" class"logo"> </view> css代码 .loadBox {width: 180rpx;min-height: 180rpx;border-radius: 50%;display: flex;align-items: center;j…...

Kopler.gl笔记:可视化功能总览

1 添加数据 2 添加图层 打开“数据层”菜单&#xff0c;开始可视化。 层&#xff08;Layers&#xff09;简单来说就是可以相互叠加的数据可视化。 3 添加过滤器 在地图上添加过滤器以限制显示的数据。过滤器必须基于数据集中的列。要创建新的过滤器&#xff0c;打开“过滤器…...

rust学习Cell、RefCell、OnceCell

背景 Rust 内存安全基于以下规则:给定一个对象 T,它只能具有以下之一: 对对象有多个不可变引用 (&T)(也称为别名 aliasing)对对象有一个可变引用 (&mut T)(也称为可变性 mutability)这是由 Rust 编译器强制执行的。然而,在某些情况下,该规则不够灵活(this r…...

基于SSM的摄影约拍系统

基于SSM的摄影约拍系统的设计与实现 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;SpringSpringMVCMyBatisJSP工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 【主要功能】 前台系统&#xff1a;首页拍摄作品展示、摄影师展示、模特展示、文章信息、交流论…...

分析智能平台VMware Greenplum 7 正式发布!

&#x1f4e2;&#x1f4e2;&#x1f4e2;&#x1f4e3;&#x1f4e3;&#x1f4e3; 哈喽&#xff01;大家好&#xff0c;我是【IT邦德】&#xff0c;江湖人称jeames007&#xff0c;10余年DBA及大数据工作经验 一位上进心十足的【大数据领域博主】&#xff01;&#x1f61c;&am…...

动态规划算法(3)--0-1背包、石子合并、数字三角形

目录 一、0-1背包 1、概述 2、暴力枚举法 3、动态规划 二、石子合并问题 1、概述 2、动态规划 3、环形石子怎么办&#xff1f; 三、数字三角形问题 1、概述 2、递归 3、线性规划 四、租用游艇问题 一、0-1背包 1、概述 0-1背包&#xff1a;给定多种物品和一个固定…...

Linux C/C++ 嗅探数据包并显示流量统计信息

嗅探数据包并显示流量统计信息是网络分析中的一种重要技术&#xff0c;常用于网络故障诊断、网络安全监控等方面。具体来说&#xff0c;嗅探器是一种可以捕获网络上传输的数据包&#xff0c;并将其展示给分析人员的软件工具。在嗅探器中&#xff0c;使用pcap库是一种常见的方法…...

Vitis导入自制IP导致无法构建Platform

怎么还有这种问题&#xff08; 解决Vitis导入自制IP导致无法构建Platform – TaterLi 个人博客 Vitis报错&#xff1a;fatal error: xxx.h: No such file or directory._ly2lj的博客-CSDN博客 在指定位置黏入以上代码即可&#xff1a; INCLUDEFILES$(wildcard *.h) LIBSOUR…...

SQLAlchemy 使用封装实例

类封装 database.py #! /usr/bin/env python # -*- coding: utf-8 -*-import sys import json import logging from datetime import datetimefrom core.utils import classlock, parse_bool from core.config import (MYSQL_HOST,MYSQL_PORT,MYSQL_USER,MYSQL_PASS,MYSQL_DA…...

Android Framework通信:Binder

文章目录 前言一、Linux传统跨进程通信原理二、Android Binder跨进程通信原理1、动态内核可加载模块2、内存映射3、Binder IPC 实现原理 三、Android Binder IPC 通信模型1、Client/Server/ServiceManager/驱动Binder与路由器之间的角色关系 2、Binder通信过程3、Binder通信中的…...

如何用精准测试来搞垮团队?

测试行业每年会冒出来一些新鲜词&#xff1a;混沌工程、精准测试、AI测试…… 这些新概念、新技术让我们感到很焦虑&#xff0c;逼着自己去学习和了解这些新玩意&#xff0c;担心哪一天被淘汰掉。 以至于给我这样的错觉&#xff0c;当「回归测试」、「精准测试」这两个词摆在一…...

暴力递归转动态规划(十)

题目 给定一个二维数组matrix[][]&#xff0c;一个人必须从左上角出发&#xff0c;最终到达右下角&#xff0c;沿途只可以向下或者向右走&#xff0c;沿途的数字都累加就是距离累加和。返回最小距离累加和。 这道题中会采用压缩数组的算法来进行优化 暴力递归 暴力递归方法的整…...

深度学习-房价预测案例

1. 实现几个函数方便下载数据 import hashlib import os import tarfile import zipfile import requests#save DATA_HUB dict() DATA_URL http://d2l-data.s3-accelerate.amazonaws.com/def download(name, cache_diros.path.join(.., data)): #save"""下载…...

【26】c++设计模式——>命令模式

c命令模式 C的命令模式是一种行为模式&#xff0c;通过将请求封装成对象&#xff0c;以实现请求发送者和接受者的解耦。 在命令模式中&#xff0c;命令被封装成一个包含特定操作的对象&#xff0c;这个对象包含的执行该操作的方法&#xff0c;以及一些必要的参数。命令对象可以…...

ElasticSearch容器化从0到1实践(一)

背景 通过kubernetes集群聚合多个Elasticsearch集群碎片资源&#xff0c;提高运维效率。 介绍 Kubernetes Operator 是一种特定的应用控制器&#xff0c;通过 CRD&#xff08;Custom Resource Definitions&#xff0c;自定义资源定义&#xff09;扩展 Kubernetes API 的功能…...

【Vue面试题二十四】、Vue项目中有封装过axios吗?主要是封装哪方面的?

文章底部有个人公众号&#xff1a;热爱技术的小郑。主要分享开发知识、学习资料、毕业设计指导等。有兴趣的可以关注一下。为何分享&#xff1f; 踩过的坑没必要让别人在再踩&#xff0c;自己复盘也能加深记忆。利己利人、所谓双赢。 面试官&#xff1a;Vue项目中有封装过axios…...

AI-调查研究-01-正念冥想有用吗?对健康的影响及科学指南

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; &#x1f680; AI篇持续更新中&#xff01;&#xff08;长期更新&#xff09; 目前2025年06月05日更新到&#xff1a; AI炼丹日志-28 - Aud…...

[2025CVPR]DeepVideo-R1:基于难度感知回归GRPO的视频强化微调框架详解

突破视频大语言模型推理瓶颈,在多个视频基准上实现SOTA性能 一、核心问题与创新亮点 1.1 GRPO在视频任务中的两大挑战 ​安全措施依赖问题​ GRPO使用min和clip函数限制策略更新幅度,导致: 梯度抑制:当新旧策略差异过大时梯度消失收敛困难:策略无法充分优化# 传统GRPO的梯…...

Leetcode 3576. Transform Array to All Equal Elements

Leetcode 3576. Transform Array to All Equal Elements 1. 解题思路2. 代码实现 题目链接&#xff1a;3576. Transform Array to All Equal Elements 1. 解题思路 这一题思路上就是分别考察一下是否能将其转化为全1或者全-1数组即可。 至于每一种情况是否可以达到&#xf…...

React第五十七节 Router中RouterProvider使用详解及注意事项

前言 在 React Router v6.4 中&#xff0c;RouterProvider 是一个核心组件&#xff0c;用于提供基于数据路由&#xff08;data routers&#xff09;的新型路由方案。 它替代了传统的 <BrowserRouter>&#xff0c;支持更强大的数据加载和操作功能&#xff08;如 loader 和…...

镜像里切换为普通用户

如果你登录远程虚拟机默认就是 root 用户&#xff0c;但你不希望用 root 权限运行 ns-3&#xff08;这是对的&#xff0c;ns3 工具会拒绝 root&#xff09;&#xff0c;你可以按以下方法创建一个 非 root 用户账号 并切换到它运行 ns-3。 一次性解决方案&#xff1a;创建非 roo…...

【RockeMQ】第2节|RocketMQ快速实战以及核⼼概念详解(二)

升级Dledger高可用集群 一、主从架构的不足与Dledger的定位 主从架构缺陷 数据备份依赖Slave节点&#xff0c;但无自动故障转移能力&#xff0c;Master宕机后需人工切换&#xff0c;期间消息可能无法读取。Slave仅存储数据&#xff0c;无法主动升级为Master响应请求&#xff…...

JDK 17 新特性

#JDK 17 新特性 /**************** 文本块 *****************/ python/scala中早就支持&#xff0c;不稀奇 String json “”" { “name”: “Java”, “version”: 17 } “”"; /**************** Switch 语句 -> 表达式 *****************/ 挺好的&#xff…...

稳定币的深度剖析与展望

一、引言 在当今数字化浪潮席卷全球的时代&#xff0c;加密货币作为一种新兴的金融现象&#xff0c;正以前所未有的速度改变着我们对传统货币和金融体系的认知。然而&#xff0c;加密货币市场的高度波动性却成为了其广泛应用和普及的一大障碍。在这样的背景下&#xff0c;稳定…...

Pinocchio 库详解及其在足式机器人上的应用

Pinocchio 库详解及其在足式机器人上的应用 Pinocchio (Pinocchio is not only a nose) 是一个开源的 C 库&#xff0c;专门用于快速计算机器人模型的正向运动学、逆向运动学、雅可比矩阵、动力学和动力学导数。它主要关注效率和准确性&#xff0c;并提供了一个通用的框架&…...

保姆级教程:在无网络无显卡的Windows电脑的vscode本地部署deepseek

文章目录 1 前言2 部署流程2.1 准备工作2.2 Ollama2.2.1 使用有网络的电脑下载Ollama2.2.2 安装Ollama&#xff08;有网络的电脑&#xff09;2.2.3 安装Ollama&#xff08;无网络的电脑&#xff09;2.2.4 安装验证2.2.5 修改大模型安装位置2.2.6 下载Deepseek模型 2.3 将deepse…...