JDK17源码系列-ArrayList源码解读

JDK17源码系列-ArrayList接口源码解读

1.List集合接口类图
在这里插入图片描述
2.ArrayList详细类图
ArrayList继承了AbstractList实现了List、Serializable等接口,实现Serializable接口使得ArrayList类的对象可以串行化,串行化后,对象可以进行网络传输,也可以保存到文件。
在这里插入图片描述
3.ArrayList底层使用了数组作为存储结构,默认存储容量为10

// 源码
transient Object[] elementData; // 默认数组存储结构
private static final int DEFAULT_CAPACITY = 10; // 默认存储大小

4.构造函数

  • public ArrayList()
// 源码  生成一个默认的空的数组,所以第一次添加元素时就进行了扩容操作,
// 扩容操作是比较耗费性能的,因此不推荐使用空的构造函数,
// 推荐使用public ArrayList(int initialCapacity),初始容量尽量使用默认值10, 避免空间浪费  
public ArrayList() {this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
  • public ArrayList(int initialCapacity)
// 源码 生成一个指定初始容量的动态数组
public ArrayList(int initialCapacity) {if (initialCapacity > 0) { // 如果指定容量大于0 则初始化一个指定大小的动态数组this.elementData = new Object[initialCapacity];} else if (initialCapacity == 0) {this.elementData = EMPTY_ELEMENTDATA; // 如果指定容量为0 返回一个空的数组} else {throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);}
}
  • public ArrayList(Collection<? extends E> c)
// 源码  初始化一个包含指定集合的动态数组
public ArrayList(Collection<? extends E> c) {Object[] a = c.toArray();    // 将指定的集合转换为数组//否则 调用数组工具类中的copyOf函数进行数组拷贝if ((size = a.length) != 0) {  if (c.getClass() == ArrayList.class) {elementData = a;} else {// 否则 调用数组工具类中的copyOf函数进行数组拷贝elementData = Arrays.copyOf(a, size, Object[].class); }} else {// replace with empty array.elementData = EMPTY_ELEMENTDATA;  // 如果指定集合大小为0 则初始化一个空数组}
}
  1. public void trimToSize() 空间压缩
// 源码
public void trimToSize() {modCount++;if (size < elementData.length) {// 当size小于存储空间大小时,// 将通过调用数组拷贝方法(底层调用的是System.arraycopy),实现空间压缩elementData = (size == 0) ? EMPTY_ELEMENTDATA : Arrays.copyOf(elementData, size);}
}
  1. 添加元素(增)
  • public boolean add(E e) 添加元素
// 源码
public boolean add(E e) {modCount++;add(e, elementData, size); // 调用私有方法addreturn true;
}private void add(E e, Object[] elementData, int s) {// 如果数组元素个数等于数组长度,进行动态扩容, 否则直接添加元素, 数组元素个数+1if (s == elementData.length) elementData = grow();elementData[s] = e;size = s + 1;
}
  • public void add(int index, E element)
// 源码 将元素添加到指定位置
public void add(int index, E element) {// 检查索引是否合法rangeCheckForAdd(index);modCount++;final int s;Object[] elementData;// 如果当前数组元素个数与数组长度相等,则进行动态扩容if ((s = size) == (elementData = this.elementData).length)elementData = grow();// 数组拷贝, 将elementData数组中起始位置为index后的所有元素向后移动一位   System.arraycopy(elementData, index,elementData, index + 1,s - index);elementData[index] = element;  // 将指定元素添加到index位置上size = s + 1;  // 数组元素个数+1
}

System.arrayCopy是个本地方法,由cpp实现, 调用的是jvm.cpp文件中的JVM_ArrayCopy
在这里插入图片描述
JVM_ArrayCopy源码实现(不再向下探究了,有兴趣的小伙伴可以自己去读jvm源码)
在这里插入图片描述

  • public boolean addAll(Collection<? extends E> c) 添加整个集合
// 源码
public boolean addAll(Collection<? extends E> c) {Object[] a = c.toArray();modCount++;int numNew = a.length;if (numNew == 0)   // 如果添加的集合为空是不允许添加的,即添加失败return false;Object[] elementData;final int s;// 如果添加集合的长度大于当前数组所剩空间,则进行扩容操作if (numNew > (elementData = this.elementData).length - (s = size))elementData = grow(s + numNew);System.arraycopy(a, 0, elementData, s, numNew); // 扩容后将需要添加的集合元素拷贝到新数组中size = s + numNew;  // 数组元素个数为原数组元素个数+新添加集合元素的个数return true;
}
  • public boolean addAll(int index, Collection<? extends E> c) 在指定索引位置添加集合
// 源码
public boolean addAll(int index, Collection<? extends E> c) {// 检查索引是否合法rangeCheckForAdd(index);Object[] a = c.toArray(); // 将集合c转换一个临时数组amodCount++;int numNew = a.length;   if (numNew == 0)   // 同样,不允许添加空集合return false;Object[] elementData;final int s;// 同上当添加集合的长度大于当前数组所剩空间,则进行扩容操作if (numNew > (elementData = this.elementData).length - (s = size))elementData = grow(s + numNew);int numMoved = s - index; // 计算需要移动的元素个数// 如果需要移动的元素个数大于0, 则将当前数组(扩容后)索引位置为index后的每个元素向后移动numNew个单位if (numMoved > 0)System.arraycopy(elementData, index,elementData, index + numNew,numMoved);// 拷贝添加的集合a到当前数组的index索引位置                     System.arraycopy(a, 0, elementData, index, numNew);size = s + numNew; // 当前数组元素个数 = 原数组个数 + 添加的集合元素个数return true;
}
  1. 查询数据(查)
  • public E get(int index) 获取指定索引位置的元素
// 源码
public E get(int index) {// 检查索引是否合法Objects.checkIndex(index, size);return elementData(index);  // 返回数组元素
}E elementData(int index) {return (E) elementData[index];
}
  1. 更新数据(改)
  • public E set(int index, E element) 更新指定索引位置的元素
public E set(int index, E element) {// 检查索引位置是否合法, 如果索引位置大于元素个数,则抛出数组越界异常Objects.checkIndex(index, size);E oldValue = elementData(index);elementData[index] = element; // 更新数组索引index的数据return oldValue; // 返回更新前的老数据
}public static <X extends RuntimeException>int checkIndex(int index, int length,BiFunction<String, List<Number>, X> oobef) {if (index < 0 || index >= length)throw outOfBoundsCheckIndex(oobef, index, length);return index;
}
  1. 删除数据(删)
  • public E remove(int index) 删除指定索引位置元素
// 源码
public E remove(int index) {// 检查索引位置是否合法Objects.checkIndex(index, size);// 申明一个不可变的临时数组final Object[] es = elementData;// 快速删除fastRemove(es, index);return oldValue; // 返回删除后的数据
}// 快速删除
private void fastRemove(Object[] es, int i) {modCount++;final int newSize;// 如果当前数组元素个数-1仍大于索引,则将当前数组索引位置index+1之后的元素拷贝到当前数组的index位置,拷贝元素个数为newSize - indexif ((newSize = size - 1) > i) System.arraycopy(es, i + 1, es, i, newSize - i);es[size = newSize] = null;  // 将最后索引位置置空// 简单理解就是index+1后的所有元素向前移动一位, 然后将数组最后一位置空
}
  • public boolean remove(Object o) 删除指定元素
// 源码
public boolean remove(Object o) {final Object[] es = elementData;final int size = this.size;int i = 0;found: {// 如果指定删除的对象为空,则查找数组中第一个为空的索引,然后调用fastRemove删除if (o == null) { for (; i < size; i++)if (es[i] == null)break found;} else {//  否则查找数组中第一个与目标值相等的索引位置,然后调用fastRemove删除for (; i < size; i++)if (o.equals(es[i]))break found;}return false;}fastRemove(es, i);return true;
}
  • public boolean removeAll(Collection<?> c) 删除指定集合(批量删除)
// 源码
public boolean removeAll(Collection<?> c) {return batchRemove(c, false, 0, size);
}
// 批量删除
boolean batchRemove(Collection<?> c, boolean complement, final int from, final int end) {Objects.requireNonNull(c); // 不允许删除空集合final Object[] es = elementData;int r;for (r = from;; r++) {if (r == end) return false; // 如果起始位置等于截止位置,则删除失败if (c.contains(es[r]) != complement) // 查找第一个匹配元素的索引位置break;}int w = r++;try {// 从r的位置截止到end为止,查找集合c中不包含的元素(即不需要删除的数据)for (Object e; r < end; r++)if (c.contains(e = es[r]) == complement)es[w++] = e; // 将r位置的元素赋值为不需要删除的元素e} catch (Throwable ex) {System.arraycopy(es, r, es, w, end - r);w += end - r;throw ex;} finally {modCount += end - w; // 计算修改的次数shiftTailOverGap(es, w, end); // 将删除元素的索引位置中置为空}return true;
}
//通过向下滑动下列元素,消除从低到高的间隙
private void shiftTailOverGap(Object[] es, int lo, int hi) {System.arraycopy(es, hi, es, lo, size - hi);for (int to = size, i = (size -= hi - lo); i < to; i++)es[i] = null;
}

10.public int indexOf(Object o) 获取元素的索引位置

// 源码
public int indexOf(Object o) {return indexOfRange(o, 0, size);
}
int indexOfRange(Object o, int start, int end) {Object[] es = elementData;// 如果查找元素为空,则返数组中第一个为空(如果有的话)的索引值if (o == null) {for (int i = start; i < end; i++) {if (es[i] == null) {return i;}}} else {// 否则返回数组中第一个与之匹配的索引值for (int i = start; i < end; i++) {if (o.equals(es[i])) {return i;}}}// 如果没有找到,返回-1return -1;
}

11.public int lastIndexOf(Object o) 获取元素最后出现的索引位置

//源码
public int lastIndexOf(Object o) {return lastIndexOfRange(o, 0, size);
}
int lastIndexOfRange(Object o, int start, int end) {Object[] es = elementData;// 如果查找元素为空if (o == null) {// 从数组尾部开始查找,返回第一个数组元素为空(如果有的话)的索引值for (int i = end - 1; i >= start; i--) {if (es[i] == null) {return i;}}} else {// 否则,从数组尾部开始查找,返回第一个与之匹配的元素的索引值for (int i = end - 1; i >= start; i--) {if (o.equals(es[i])) {return i;}}}// 如果没找到,则返回-1return -1;
}

11.private Object[] grow() 动态扩容函数

// 源码
private Object[] grow() {return grow(size + 1); // 最小容量为原数组长度+1
}private Object[] grow(int minCapacity) {int oldCapacity = elementData.length; //原数组长度// 如果原数组长度大于0 或 原数组非空if (oldCapacity > 0 || elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {// 获取扩容后的数组长度int newCapacity = ArraysSupport.newLength(oldCapacity,minCapacity - oldCapacity, /* minimum growth */  // 扩容的最小长度oldCapacity >> 1           /* preferred growth */); // 扩容为原数组容量的2倍// 将原数组拷贝到扩容后的数组中        return elementData = Arrays.copyOf(elementData, newCapacity);} else {// 返回一个初始容量为指定扩容的容量与默认容量间最大值的数组return elementData = new Object[Math.max(DEFAULT_CAPACITY, minCapacity)];}
}// 获取扩容后的长度
public static int newLength(int oldLength, int minGrowth, int prefGrowth) {// preconditions not checked because of inlining// assert oldLength >= 0// assert minGrowth > 0// 预设长度为原数组容量 + 最小扩容容量与原数组容量的2倍之间的最大值(每次扩容为原来长度的2倍)int prefLength = oldLength + Math.max(minGrowth, prefGrowth); // might overflow// 如果预设容量大于0 且小于等于Integer.MAX_VALUE - 8 则返回预设长度if (0 < prefLength && prefLength <= SOFT_MAX_ARRAY_LENGTH) {return prefLength;} else {// 否则重置容量return hugeLength(oldLength, minGrowth);}
}private static int hugeLength(int oldLength, int minGrowth) {// 预设扩容后的容量为原数组长度 + minGrowth (指定的扩容长度)int minLength = oldLength + minGrowth;if (minLength < 0) { // overflowthrow new OutOfMemoryError("Required array length " + oldLength + " + " + minGrowth + " is too large");}// 如果指定的扩容长度小于等于Integer.MAX_VALUE - 8 ,返回 Integer.MAX_VALUE - 8else if (minLength <= SOFT_MAX_ARRAY_LENGTH) { return SOFT_MAX_ARRAY_LENGTH;} else {return minLength; // 否则返回预设扩容的容量(比Index.Max_VALUE还大)}
}//所以设置一个比较合适的初始化容量, 避免不断地扩容

12.public boolean contains(Object o) 判断集合中是否包含指定元素

// 源码
public boolean contains(Object o) {return indexOf(o) >= 0;
}
  1. public void ensureCapacity(int minCapacity) 手动扩容
// 为什么有了自动扩容,还需要手动扩容呢, 因为当我们ArrayList未初始化时或初始化容量不太合适时,
// 此时有需要添加大量元素, ArrayList就会多次自动扩容, 这样非常影响效率,因此我们可以显示调用
// public void ensureCapacity(int minCapacity) 来避免频繁扩容, 提高效率public void ensureCapacity(int minCapacity) {// 如果指定的容量大于当前数组的长度且当前数组不为空并且指定的容量大于默认容量,此时进行扩容if (minCapacity > elementData.length&& !(elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA&& minCapacity <= DEFAULT_CAPACITY)) {modCount++;grow(minCapacity);}
}

示例:

       Object obj = new Object();ArrayList<Object> list1 = new ArrayList<>();long startTime = System.currentTimeMillis();for (int i = 0; i < 1000000; i++) {list1.add(obj); // 这种方式会在添加过程中多次扩容,效率较低}System.out.println(System.currentTimeMillis() - startTime);ArrayList<Object> list2 = new ArrayList<>();long startTime2 = System.currentTimeMillis();list2.ensureCapacity(1000000); // 显式设置容量,避免频繁扩容for (int i = 0; i < 1000000; i++) {list2.add(obj); // 这种方式效率更高,因为已经预分配了足够的空间}System.out.println(System.currentTimeMillis() - startTime2);

打印结果:
在这里插入图片描述
14.ArrayList转数组

// 源码 实际也是调用的数组拷贝
public Object[] toArray() {return Arrays.copyOf(elementData, size);
}
public <T> T[] toArray(T[] a) {if (a.length < size) return (T[]) Arrays.copyOf(elementData, size, a.getClass());System.arraycopy(elementData, 0, a, 0, size);if (a.length > size)a[size] = null;return a;
}

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

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

相关文章

VBA学习笔记:点击单元格显示指定的列

应用场景&#xff1a; 表格中列数较多&#xff0c;特定条件下隐藏一些无关的列&#xff0c;只保留相关的列&#xff0c;使表格更加清晰。 示例&#xff1a;原表格如下 点击一年级&#xff0c;只显示一年级相关的科目&#xff1a; 点击二年级&#xff0c;只显示二年级相关的科…

RNN深度学习案例:LSTM火灾温度预测

本文为为&#x1f517;365天深度学习训练营内部文章 原作者&#xff1a;K同学啊 一 前期准备数据 1.导入数据 import numpy as np import pandas as pd import matplotlib.pyplot as plt import seaborn as sns from sklearn.preprocessing import MinMaxScaler from keras.lay…

nmap简单使用

nmap的基本功能 nmap有4个基本功能&#xff0c;分别是 端口扫描 主机探测 服务识别 系统识别 端口扫描 nmap 183.2.172.185 从图中可以看出开放了80和443端口 指定端口扫描 指定端口扫描使用-p参数&#xff0c;可以一次性扫描一个或多个或某个范围内的端口 nmap 183.…

python文件排序都有哪些方法

在python环境中提供两种排序方案&#xff1a;用库函数sorted()对字符串排序&#xff0c;它的对象是字符&#xff1b;用函数sort()对数字排序&#xff0c;它的对象是数字&#xff0c;如果读取文件的话&#xff0c;需要进行处理&#xff08;把文件后缀名‘屏蔽’&#xff09;。 …

uni-app快速入门(五)--判断运行环境及针对不同平台的条件编译

一、判断运行环境 在实际项目开发中&#xff0c;经常需要进行开发环境和生产环境的切换&#xff0c;uni-app可根据process.env.NODE_ENV判断当前运行环境是开发环境和生产环境&#xff0c;根据不同的环境调用不同的后台接口&#xff0c;具体实现方式: 在项目的static目录下建…

QT定时器

QT定时器 1.概述 这篇文章介绍如何使用定时器 2.定时器 2.1.创建项目 新建一个普通的widget类型项目&#xff0c;在widget.ui文件中添加一个label控件 2.1.QTimer 在帮助文档搜索timerEvent&#xff0c;查看QTimer Class 类提供的定时器。 该类提供了一个定时事件的函…

双指针优质算法题集

目录 一、双指针算法介绍 二、移动0 1、题目链接 2、题解 3、代码实现 &#xff08;1&#xff09;、初次实现&#xff1a; &#xff08;2&#xff09;、优化后的实现&#xff1a; 二、复写0 1、题目链接&#xff1a; 2、题解 3、代码实现 一、双指针算法介绍 这里的指…

博奥龙黑胶虫去除剂,新品来袭!

形态特征&#xff1a; 大小方面&#xff1a;黑胶虫的直径通常在 0.5~1 微米左右&#xff0c;一般比细菌要小&#xff0c;但在显微镜下仍可明显观察到。 形状方面&#xff1a;其形态不规则&#xff0c;初始呈现点状&#xff0c;随着培养时间的增加其形态可以转变为密集的小黑点…

使用Element UI实现前端分页,及el-table表格跨页选择数据,切换分页保留分页数据,限制多选数量

文章目录 一、前端分页1、模板部分 (\<template>)2、数据部分 (data)3、计算属性 (computed)4、方法 (methods) 二、跨页选择1、模板部分 (\<template>)2、数据部分 (data)3、方法 (methods) 三、限制数量1、模板部分 (\<template>)2、数据部分 (data)3、方法…

HTML5+CSS前端开发[保姆级教学]+基本文本控制标签介绍

引入&#xff1a;Hello&#xff0c;各位编程猿们&#xff01;前几篇文章介绍了软件的安装以及编写新闻文章的代码初体验&#xff0c;这一篇我们来巩固一些这些知识点&#xff0c;复习一下基本文本控制标签&#xff01;&#xff01;&#xff01; 知识点一&#xff1a;基本文本控…

《FreeRTOS任务基础知识以及任务创建相关函数》

目录 1.FreeRTOS多任务系统与传统单片机单任务系统的区别 2.FreeRTOS中的任务&#xff08;Task&#xff09;介绍 2.1 任务特性 2.2 FreeRTOS中的任务状态 2.3 FreeRTOS中的任务优先级 2.4 在任务函数中退出 2.5 任务控制块和任务堆栈 2.5.1 任务控制块 2.5.2 任务堆栈…

AdaBoost 二分类问题

代码功能 生成数据集&#xff1a; 使用 make_classification 创建一个模拟分类问题的数据集。 数据集包含 10 个特征&#xff0c;其中 5 个是有用特征&#xff0c;2 个是冗余特征。 数据集划分&#xff1a; 将数据分为训练集&#xff08;70%&#xff09;和测试集&#xff08;3…

ffmpeg自动手动编译安装

1.下载linux ndk并配置profile文件 本例以android-ndk-r10e为例 vi /etc/profile export NDK_HOME/root/ffmpeg/android-ndk-r10e export PATH P A T H : PATH: PATH:NDK_HOME source /etc/profile 2.下载x264并生成 git clone https://code.videolan.org/videolan/x264.g…

英伟达基于Mistral 7B开发新一代Embedding模型——NV-Embed-v2

我们介绍的 NV-Embed-v2 是一种通用嵌入模型&#xff0c;它在大规模文本嵌入基准&#xff08;MTEB 基准&#xff09;&#xff08;截至 2024 年 8 月 30 日&#xff09;的 56 项文本嵌入任务中以 72.31 的高分排名第一。此外&#xff0c;它还在检索子类别中排名第一&#xff08;…

Flume1.9.0自定义拦截器

需求 1、在linux日志文件/data/log/moreInfoRes.log中一直会产生如下JSON数据&#xff1a; {"id":"14943445328940974601","uid":"840717325115457536","lat":"53.530598","lnt":"-2.5620373&qu…

RadSystems 自定义页面全攻略:个性化任务管理系统的实战设计

系列文章目录 探索RadSystems&#xff1a;低代码开发的新选择&#xff08;一&#xff09;&#x1f6aa; 探索RadSystems&#xff1a;低代码开发的新选择&#xff08;二&#xff09;&#x1f6aa; 探索RadSystems&#xff1a;低代码开发的新选择&#xff08;三&#xff09;&…

【开发基础】语义化版本控制

语义化版本控制 基础三级结构主版本号次版本号修正版本号 思维导图在node包管理中的特殊规则 参考文件 基础 语义化版本控制是一套通用的包/库的版本管理规范。在各类语言的包管理中都有用到&#xff0c;一般以x.x.x的形式出现在包的命名中。 三级结构 在语义化版本控制中&a…

IDC 报告:百度智能云 VectorDB 优势数量 TOP 1

近日&#xff0c;IDC 发布了《RAG 与向量数据库市场前景预测》报告&#xff0c;深入剖析了检索增强生成&#xff08;RAG&#xff09;技术和向量数据库市场的发展趋势。报告不仅绘制了 RAG 技术的发展蓝图&#xff0c;还评估了市场上的主要厂商。在这一评估中&#xff0c;百度智…

操作系统——同步

笔记内容及图片整理自XJTUSE “操作系统” 课程ppt&#xff0c;仅供学习交流使用&#xff0c;谢谢。 背景 解决有界缓冲区问题的共享内存方法在并发变量上存在竞争条件&#xff0c;即多个并发进程访问和操作同一个共享数据&#xff0c;从而其执行结果与特定访问次序有关。这种…

IAR调试时输出文本数据到电脑(未使用串口)

说明 因为板子没引出串口引脚&#xff0c;没法接USB转TTL&#xff0c;又想要长时间运行程序并保存某些特定数据&#xff0c;所以找到了这个办法。为了写数据到本机&#xff0c;所以板子必须运行在IAR的debug模式下。 参考&#xff1a;IAR环境下变量导出方法&#xff1a;https…