Java ArrayList源码阅读笔记(基于JDK17)

news2024/9/22 13:24:43

Java ArrayList源码阅读笔记(基于JDK17)

虽然不喜欢看源码,但是据说会让人变强啊,看别的大佬的代码也许才知道怎么处理自己的一坨吧,因此冒着秃顶的风险还是来看看吧。。。
第一遍先简单看看吧,搞不清楚的地方也许会在遥远的将来再复习下吧

1、简介

其实在搞不清楚这玩意儿究竟是什么的情况下,已经稀里糊涂地用它写了好多代码了,而且每当找不到数据结构的时候,口袋里一摸就是ArrayList,所以它到底是个啥?

https://www.runoob.com/java/java-arraylist.html

ArrayList 类是一个可以动态修改的数组,与普通数组的区别就是它是没有固定大小的限制,我们可以添加或删除元素

ArrayList 继承了 AbstractList ,并实现了 List 接口

以上便是比较标准的定义,大概就是一个特殊的数组,可以方便地变大变小

2、类图及类定义

在这里插入图片描述
在这里插入图片描述
ArrayList继承自AbstractList类,实现了ListRandomAccessCloneablejava.io.Serializable接口

  • List接口:表明它是一个有序列表支持添加、删除、查找等操作,并且可以通过下标进行访问
  • RandomAccess接口:标记接口,表明实现这个接口的List支持快速随机访问,即直接通过索引下标访问列表的元素
  • Cloneable接口:表明支持拷贝操作,支持深拷贝或浅拷贝操作
  • java.io.Serializable接口:表明支持序列化操作,可以将对象转换为字节流进行持久化存储或网络传输,非常方便

3、扩容机制

这块经常会被提到,并且所谓“动态”指的也就是这个机制,是该数据结构设计的关键

3.1、构造函数

要看ArrayList怎样进行扩容,那么自然绕不开创建类的实例
以下两个常量,其中DEFAULT_CAPACITY 表示创建ArrayList默认的初始容量10,而EMPTY_ELEMENTDATA 表示的是一个空数组,这是一个共享的空对象数组

// 默认的初始容量
private static final int DEFAULT_CAPACITY = 10;
// 共享的空数组实例
private static final Object[] EMPTY_ELEMENTDATA = {};

ArrayList中,有3个构造函数:

  • ArrayList()
  • ArrayList(int initialCapacity)
  • ArrayList(Collection<? extends E> c)

ArrayList()是无参构造,默认会创建一个空对象数组,在第一次添加元素时会扩容到容量10

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

ArrayList(int initialCapacity)构造函数需要指定一个初始容量,依据给定的初始容量的大小,创建不同长度的对象数组,或是抛出异常

public ArrayList(int initialCapacity) {
	if (initialCapacity > 0) {   // 初始容量大于0
    	this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {
        // 创建空数组
        this.elementData = EMPTY_ELEMENTDATA;
    } else {
        // 小于0抛出异常
        throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
   }
}

ArrayList(Collection<? extends E> c)构造函数可以指定另一个集合对象作为参数,将其转换为对象数组(存在空指针风险),按顺序进行返回

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

3.2、扩容时机

那么,到底什么时候需要扩容呢,以及怎样进行扩容呢?
接下来,借助ArrayListadd(E e)方法一探究竟

public boolean add(E e) {
    modCount++;   // 修改次数加1
    add(e, elementData, size);   // 调用重载方法
    return true;
}

add(E e)内部又调用了另一个重载方法add(E e, Object[] elementData, int s),是为了在字节码转化上优化,把方法进行了拆分

/**
 * This helper method split out from add(E) to keep method
 * bytecode size under 35 (the -XX:MaxInlineSize default value),
 * which helps when add(E) is called in a C1-compiled loop.
 */
private void add(E e, Object[] elementData, int s) {
    if (s == elementData.length)
        elementData = grow();   // 长度达到最大,调用grow()扩容
    elementData[s] = e;  // 数组索引赋值,并且size+1
    size = s + 1;
}

s代表size,用来记录元素的个数,当元素个数达到当前对象数组的长度的时候,就需要进行扩容了,此时会调用grow()方法,扩容后,长度就够应付一阵子了,此时将元素填到对应位置上就可以了,结合前面默认容量为10,可知,第一次扩容将会是在增加第11个元素的时候

3.3、扩容方法

grow()便是用来处理扩容的方法,内部又调用了另一个重载方法,将当前size + 1作为参数

private Object[] grow() {
    return grow(size + 1);
}

oldCapacity 代表的就是旧对象数组的长度,判断其大于0也就是有元素,elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA则是判断数组元素是否由默认无参构造器初始化
通过ArraysSupport.newLength(int oldLength, int minGrowth, int prefGrowth)进行扩容
其中oldLength代表的就是旧的数组容量,minGrowth则是最少需要保证的扩容大小,没有富余,prefGrowth则是表示推荐的扩容大小,一般会有富余
oldCapacity >> 1是一个位运算,表示0.5倍的原数组容量,那么对应的含义就是推荐扩容到原数组的1.5倍

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

newLength()是有可能OOM的,如果成功的话就返回扩容后的长度

public static int newLength(int oldLength, int minGrowth, int prefGrowth) {
    // preconditions not checked because of inlining
    // assert oldLength >= 0
    // assert minGrowth > 0

    int prefLength = oldLength + Math.max(minGrowth, prefGrowth); // might overflow
    if (0 < prefLength && prefLength <= SOFT_MAX_ARRAY_LENGTH) {
        return prefLength;
    } else {
        // put code cold in a separate method
        return hugeLength(oldLength, minGrowth);
    }
}

Arrays.copyOf(elementData, newCapacity)将原数组依照新的容量进行拷贝

public static <T> T[] copyOf(T[] original, int newLength) {
    return (T[]) copyOf(original, newLength, original.getClass());
 }
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
    @SuppressWarnings("unchecked")
    T[] copy = ((Object)newType == (Object)Object[].class)
        ? (T[]) new Object[newLength]
        : (T[]) Array.newInstance(newType.getComponentType(), newLength);
    System.arraycopy(original, 0, copy, 0,
                     Math.min(original.length, newLength));
    return copy;
}

最终其实调用的是nativearraycopy(),将源数组src从指定位置srcPos拷贝指定长度length到目标数组destdestPos位置
最终,实现将数据从旧数组拷贝到扩容后的新数组,返回给elementData
这边应该是浅拷贝,但是引用的对象数组地址肯定变了

public static native void arraycopy(Object src,  int  srcPos,
                                    Object dest, int destPos,
                                    int length);

比如我们先创建ArrayList,扔10个数据给它,此时引用的对象数组地址为728,然后再增加一个,触发扩容
在这里插入图片描述
新容量计算出来是15,然后创建一个地址在760的新数组,容量为15
在这里插入图片描述
拷贝完成后,原来的元素地址没有变,说明仅仅是拷贝了引用,内容没有变,因此拷贝效率比较高,但是elementData的对象数组地址变了
在这里插入图片描述
先看这么多,如有问题和不足欢迎指正~~

https://www.runoob.com/manual/jdk11api/java.base/java/util/ArrayList.html
https://javaguide.cn/java/collection/arraylist-source-code.html
https://www.cnblogs.com/KRDecad3/p/17800902.html

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

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

相关文章

双阈值最大最小值筛选

问题&#xff1a; 如下图所示的问题&#xff0c;给定最小阈值、最大阈值以及一段数据队列&#xff0c;对数据队列中超过阈值部分的极值进行保存&#xff0c;即从队列中得到P1-P6 计算规则 规则类似状态机 首先定义last_type标志位&#xff1a; { 上一时刻大于 m a x _ t h…

win7安装mysql-installer-community-8.0.11.0

1、安装Microsoft Visual C 2019 Redistributable Package (x64) 官网下载地址&#xff1a;https://learn.microsoft.com/en-us/cpp/windows/latest-supported-vc-redist?viewmsvc-160#latest-microsoft-visual-c-redistributable-version 通过百度网盘分享的文件&#xff1…

VBA_MF系列技术资料1-680

MF系列VBA技术资料1-680 WORD 目录下载地址&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/17TrFO37OgnjiwvACvMna_A?pwdbr3g 提取码&#xff1a;br3g 为了让广大学员在VBA编程中有切实可行的思路及有效的提高自己的编程技巧&#xff0c;我参考大量的资料&#xff…

大数据-63 Kafka 高级特性 分区 副本机制 宕机恢复 Leader选举

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; 目前已经更新到了&#xff1a; Hadoop&#xff08;已更完&#xff09;HDFS&#xff08;已更完&#xff09;MapReduce&#xff08;已更完&am…

【全国大学生电子设计竞赛】2022年D题

&#x1f970;&#x1f970;全国大学生电子设计大赛学习资料专栏已开启&#xff0c;限时免费&#xff0c;速速收藏~

详解Xilinx FPGA高速串行收发器GTX/GTP(2)--什么是GTX?

GTX本质上是基于SerDes技术的高速串行收发器,它是FPGA内部的底层电路,也叫做Gigabit Transceiver(吉比特收发器,简称为GT)。其中A7系列使用的GT叫GTP,K7系列使用的GT叫GTX,V7系列使用的GT叫GTH和GTZ,它们的结构大致相同,但是线速率的关系是 GTZ>GTH>GTX>GTP,…

Android进程保活:如何让app一直运行

目录 1&#xff09;为什么需要进行进程保活呢&#xff1f;需求是什么&#xff1f; 2&#xff09;进程分类 3&#xff09;进程的优先级 4&#xff09;如何提高进程优先级 5&#xff09;如何进行进程保活 一、为什么需要进行进程保活呢&#xff1f;需求是什么&#xff1f; 比如…

国标GB28181视频平台LntonCVS视频融合共享平台视频汇聚应用方案

近年来&#xff0c;国内视频监控应用迅猛发展&#xff0c;系统接入规模不断扩大&#xff0c;导致了大量平台提供商的涌现。然而&#xff0c;不同平台的接入协议千差万别&#xff0c;使得终端制造商不得不为每款设备维护多个不同平台的软件版本&#xff0c;造成了资源的严重浪费…

工业大数据通过哪些方式实现价值?详解实施工业大数据的难点!

在数字化转型的浪潮中&#xff0c;工业大数据正成为推动制造业革新的核心动力。它不仅重塑了生产流程&#xff0c;还为企业带来了前所未有的洞察力和竞争优势。本文将深入探讨工业大数据的类别、价值实现方式&#xff0c;以及在实施过程中存在的挑战和解决方案。 更多详细内容&…

JavaScript和vue实现左右两栏,中间拖动按钮可以拖动左右两边的宽度

JavaScript实现&#xff1a; <!DOCTYPE html> <html lang"en"> <head><title>拖动效果</title><style> body, html {margin: 0;padding: 0;height: 100%;font-family: Arial, sans-serif; }.container {display: flex;height: …

pytest测试框架之http协议接口测试

1 接口测试 日常测试中接口测试是一项重要的工作&#xff0c;尤其是http协议的接口测试更加普遍,比如一些常用的测试框架或者工具&#xff08;robotframework框架&#xff0c;testng框架&#xff0c;postman等&#xff09;都支持http接口的测试&#xff0c;而这节内容主要介绍…

函数:全局,局部和静态变量

文章目录 &#x1f34a;自我介绍&#x1f34a;全局变量&#x1f34a;局部变量&#x1f34a;静态局部变量 你的点赞评论就是对博主最大的鼓励 当然喜欢的小伙伴可以&#xff1a;点赞关注评论收藏&#xff08;一键四连&#xff09;哦~ &#x1f34a;自我介绍 Hello,大家好&#x…

力扣SQL50 餐馆营业额变化增长 子连接

Problem: 1321. 餐馆营业额变化增长 &#x1f468;‍&#x1f3eb; 参考题解 Code select a.visited_on,sum(b.amount) as amount, round(sum(b.amount) / 7,2) as average_amount from (select distinct visited_on from customer) a join customer bon datediff(a.visited…

window安装elasticsearch和可视化界面kibana

ElasticSearch 官网下载zip安装包并解压 Elasticsearch&#xff1a;官方分布式搜索和分析引擎 | Elastic 修改配置文件 改选项是指定ssl访问还是普通http访问 不改的话使用http访问不了&#xff0c;得使用https 浏览器访问 localhost:9200 Kibana Download Kibana Free |…

MySQL 将文件导入数据库(load data Statement)

前面我们介绍过如何用select…into outfile语句将SQL查询结果导出到文件&#xff1a; MySQL 将查询结果导出到文件&#xff08;select … into Statement&#xff09; MySQL同时也提供互补的功能&#xff0c;可以使用load data infile语句将文件中的数据加载到数据库中&#x…

Robot Operating System——Action通信机制的服务端

大纲 回调接受或者拒绝请求执行任务的回调终止任务回调 创建服务完整代码总结 在《Robot Operating System——Action通信机制概念及Client端》一文中&#xff0c;我们介绍了Action客户端的主要流程。本文我们将介绍Action服务端的编写。 回顾下Action的构成: 目标&#xff0…

cesium canvas广告牌

在有些业务中&#xff0c;对场景中的广告牌样式要求比较高&#xff0c;需要动态显示一些数据&#xff0c;这个时候&#xff0c;我们可以通过将复杂背景样式制作成图片&#xff0c;通过canvas绘制图片和动态数据&#xff0c;从而达到比较好的显示效果。 1 CanvasMarker 类封装 …

ICM-20948芯片详解(2)

接前一篇文章&#xff1a;ICM-20948芯片详解&#xff08;1&#xff09; 二、详述 ICM-20948是一款9轴运动跟踪设备&#xff0c;全部采用3x3x1mm QFN封装。ICM-20948是一个多芯片模块&#xff08;MCM&#xff09;&#xff0c;由集成在单个QFN封装中的两个管芯组成。一个芯片内装…

2024年【制冷与空调设备运行操作】考试技巧及制冷与空调设备运行操作考试试题

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 制冷与空调设备运行操作考试技巧考前必练&#xff01;安全生产模拟考试一点通每个月更新制冷与空调设备运行操作考试试题题目及答案&#xff01;多做几遍&#xff0c;其实通过制冷与空调设备运行操作作业模拟考试很简…

leetcode173. 二叉搜索树迭代器,注意vector中的size()的无符号整数类型,无符号整数和有符号整数的加减比大小有着种种大坑

leetcode173. 二叉搜索树迭代器 实现一个二叉搜索树迭代器类BSTIterator &#xff0c;表示一个按中序遍历二叉搜索树&#xff08;BST&#xff09;的迭代器&#xff1a; BSTIterator(TreeNode root) 初始化 BSTIterator 类的一个对象。BST 的根节点 root 会作为构造函数的一部分…