ArrayList源码分析(JDK17)

news2024/9/24 11:21:55

ArrayList类

  • 简介
  • 类层次结构
  • 构造
    • 无参构造
    • 有参构造
  • 添加元素
    • add:添加/插入一个元素
    • addAll:添加集合中的元素
  • 扩容
  • mount与迭代器
  • 其他常见方法
  • 不常见方法
  • 不常见方法的源码和小介绍
  • 常见方法的源码和小介绍
  • 积累面试题
    • ArrayList是什么?可以用来干嘛?
    • ArrayList 的默认长度
    • ArrayList如何扩容
    • ArrayList频繁扩容导致性能下降该怎么办
    • 什么情况下你会使用ArrayList?什么时候你会选择LinkedList?
    • ArrayList的插入或删除一定比LinkedList慢吗


写在前面:

第一次写源码分析,感觉写起来,不知道从何下手,不过慢慢的也就写完了,也不知道条理怎么样,内容应该还是都说了。希望能对ArrayList的理解有所帮助.
本文通过ArrayList对动态数组的实现/生命周期来进行源码的解析。会列举源码并注释代码的作用。但不会讲述动态数组的实现过程,实现过程在数组–java–动态数组已经写过了

明天在继续写

本文内容包括:

    • 源码分析
    • 扩容机制
    • 迭代器
    • 面试题
    • 简介

      此类是 Java 集合框架的成员。 ArrayList 是一个数组队列,相当于动态数组。与Java中的数组相比,它的容量能动态增长。使用量很大,所以作为第一个进行源码分析的类。

      类层次结构

      继承于AbstractList抽象类
      在这里插入图片描述
      实现了:

      1. List
        有序集合(也称为 序列)的接口
      2. RandomAccess
        标记性接口,使得随机访问比迭代器快
      3. Cloneable
        标记性接口,克隆
        所以重写clone方法完成浅克隆
      4. java.io.Serializable
        标记性接口,序列化

      构造

      ArrayList的构造方法一共有3个
      在这里插入图片描述

      无参构造

      在这里插入图片描述
      elementData代表着的就是存储元素的数组了。
      transient关键字代表着其不能被序列化
      在这里插入图片描述

      注释上写着构造一共初始容量为10的空列表。但是我们打开DEFAULTCAPACITY_EMPTY_ELEMENTDATA这个类查看。很明显,这就是一个空数组,所以最开始的数组大小就是0。
      这个也算是慢初始,因为可能构造了不用,所以设置为空就会减少空间的浪费。
      在这里插入图片描述
      但为什么要这么写呢?
      我们可以看到这样一个属性,我们在扩容的时候用到这个,会导致无参构造扩容后就会是10.而在调用add后动态的初始化。

      在这里插入图片描述

      有参构造

      根据你传入的参数来构建大小,如果为0则会赋值一个空数组(但是这个和无参的不一样)
      在这里插入图片描述

      这个构造先判断传入集合的大小,如果为空则设置为空数组。
      如果不为空则判断是否的ArrayList类,是则直接赋值了,不是则进行拷贝。
      在这里插入图片描述

      添加元素

      add:添加/插入一个元素

      这里和jdk8会不一样
      modCount继承于AbstractList,记录着集合的修改次数
      然后调用了add(E e, Object[] elementData, int s)方法(jdk8直接实现出来了,而不是抽取方法)
      返回一个true代表添加成功
      在这里插入图片描述
      这段代码是很平常的添加代码,有意思的是这个注释。
      查阅资料发现,当方法字节码大小小于35的时候,会进行方法内联
      而这个操作由c1编译器进行。c1编译器(适用于执行时间较短或对启动性能有要求的程序)会比c2要快,所以可以让这个add成为热点,而被优化。
      这个部分应该属于JIT优化。
      在这里插入图片描述

      public void add(int index, E element)

      /**
      在此列表中的指定位置插入指定的元素。将当前位于该位置的元素(如果有)和任何后续元素向右移动(将一个元素添加到其索引中)。
      形参:
      index – 要插入指定元素的索引 element – 要插入的元素
      抛出:
      IndexOutOfBoundsException – 如果索引超出范围 (index < 0 || index > size())
      **/
      
      public void add(int index, E element) {
              rangeCheckForAdd(index); // 进行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;
          }
      

      addAll:添加集合中的元素

      扩容

      扩容判断

      if (s == elementData.length)
      	则扩容
      

      这个是扩容的通用代码。
      记录了旧容量后,如果不是默认空数组,或者容量大于0则if成立。
      这里就是默认空数组和其他构造的空数组的区别了:

      如果是默认空数组则走初始容量为10的路线,否则则按照1.5倍扩容走

           /**
           * 增加容量以确保它至少可以容纳最小容量参数指定的元素数。
           *
           * @param minCapacity 所需的最小容量
           * @throws OutOfMemoryError 如果最小容量小于零
           */
          private Object[] grow(int minCapacity) {
              int oldCapacity = elementData.length;//获取旧容量
              if (oldCapacity > 0 || elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
                  int newCapacity = ArraysSupport.newLength(oldCapacity,
                          minCapacity - oldCapacity, /* 最小增长 */
                          oldCapacity >> 1           /* 首选增长*/);
      		//这个方法会从old的基础上,选取后面2个值的大的来增长。
      		// 也就是,需要的容量和1.5倍比较
                  return elementData = Arrays.copyOf(elementData, newCapacity);
                  //拷贝旧的
              } else {
                  return elementData = new Object[Math.max(DEFAULT_CAPACITY, minCapacity)];
              }
          }
      

      而一般性的,就是长度加一,调用上面的方法。

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

      mount与迭代器

      其他常见方法

      //返回此列表中的元素数。
      public int size()
      
      //如果此列表不包含任何元素,则返回 true 。
      public boolean isEmpty()
      
      //如果此列表包含指定的元素,则返回true。更正式地说,当且仅当此列表包含至少一个元素eObjects.equals(o, e)时,返回 true .
      public boolean contains(Object o)
      
      //返回此列表中指定元素第一次出现的索引,如果此列表中不包含该元素,则返回 -1。更正式地说,返回最低索引,例如 Objects.equals(o, get(i)),如果没有这样的索引i,则返回 -1。
      public int indexOf(Object o)
      
      
      //返回此列表中指定元素最后一次出现的索引,如果此列表中不包含该元素,则返回 -1。更正式地说,返回最高索引,如果没有这样的索引iObjects.equals(o, get(i)),则返回 -1。
      
      public int lastIndexOf(Object o)
      
      

      不常见方法

      //将此实例的容量修剪为列表的 ArrayList 当前大小。
      public void trimToSize()  
      
      //如有必要,增加此 ArrayList 实例的容量,以确保它至少可以容纳最小容量参数指定的元素数。
      //形参:minCapacity – 所需的最小容量
      public void ensureCapacity(int minCapacity) 
      
      //浅克隆
      public Object clone();
      
      

      不常见方法的源码和小介绍

      这是一个缩小空间的方法,把未使用的数组删掉。
      这个方法在ArrayList里面没有调用,但是在其他地方优化的时候还挺多的。
      在这里插入图片描述

          /**
           * 将此实例的容量修剪为列表的 ArrayList 当前大小。
           * 应用程序可以使用此操作来最小化实例的 ArrayList 存储
           */
          public void trimToSize() {
              modCount++;
              if (size < elementData.length) {
                  elementData = (size == 0)
                    ? EMPTY_ELEMENTDATA
                    : Arrays.copyOf(elementData, size);
              }
          }
      

      没有调用的

      public void ensureCapacity(int minCapacity) {
              if (minCapacity > elementData.length
                  && !(elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
                       && minCapacity <= DEFAULT_CAPACITY)) {
                  modCount++;
                  grow(minCapacity);
              }
          }
      
      	public Object clone() {
              try {
                  ArrayList<?> v = (ArrayList<?>) super.clone();
                  v.elementData = Arrays.copyOf(elementData, size);
                  v.modCount = 0;
                  return v;
              } catch (CloneNotSupportedException e) {
                  // this shouldn't happen, since we are Cloneable
                  throw new InternalError(e);
              }
          }
      

      常见方法的源码和小介绍

      把这个放后面呢是因为简单应该看的人少。

      public int size() {
              return size;
          }
      
      
       public boolean isEmpty() {
              return size == 0;
          }
      
      public boolean contains(Object o) {
              return indexOf(o) >= 0;
          }
      
      	 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;
                      }
                  }
              }
              return -1;
          }
      
      	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;
                      }
                  }
              }
              return -1;
          }
      

      积累面试题

      ArrayList是什么?可以用来干嘛?

      ArrayList是个动态数组,实现List接口,主要用来存储数据,只存储包装类。它的特点是:
      增删慢:每次删除元素,都需要更改数组长度、拷贝以及移动元素位置。
      查询快:由于数组在内存中是一块连续空间,因此可以根据地址+索引的方式快速获取对应位置上的元素。
      

      ArrayList 的默认长度

      在jdk8以前和jdk8以后稍有不同
      jdk8以前:创建就会初始数组长度,长度为10
      jdk8及以后:是懒加载,初始的时候为空数组,在第一次添加元素的时候扩容为10
      

      ArrayList如何扩容

      定好容量后,定好会把原来的数组元素拷贝到新数组中,再把指向原数的地址换到新数组。
      这个有2种扩容机制
      对于无参构造的集合:在第一次添加元素的时候会扩容到10,存满后按照1.5倍的进行扩容。
      	如果一次添加了多个元素1.5倍放不下,则会按照实际需要的大小进行扩容。
      对于其他的构造:就没有扩容10的步骤了,按照max(1.5倍,实际大小)
      

      ArrayList频繁扩容导致性能下降该怎么办

      这时候我们可以估算需要的容量,使用
      ArrayList(int capacity)的有参构造来指定容量的空列表
      

      在测试中甚至有几个下图这样这样的比例运行了
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述

      什么情况下你会使用ArrayList?什么时候你会选择LinkedList?

      多数情况下,当你遇到访问元素比插入或者是删除元素更加频繁的时候,你应该使用ArrayList。
      另外一方面,当你在某个特别的索引中,插入或者是删除元素更加频繁,或者你压根就不需要访问元素的时候,你会选择LinkedList。
      这里的主要原因是,在ArrayList中访问元素的最糟糕的时间复杂度是”1″,
      而在LinkedList中可能就是”n”了。
      在ArrayList中增加或者删除某个元素,通常会调用System.arraycopy方法,这是一种极为消耗资源的操作,因此,在频繁的插入或者是删除元素的情况下,LinkedList的性能会更加好一点。
      

      ArrayList的插入或删除一定比LinkedList慢吗

      大多数是的,但是不一定.
      如果删除靠前的元素arraylist需要拷贝后面的元素
      如果靠中间或者靠后,linkedlist找元素需要消耗很多的时间,而arraylist拷贝的元素会少一些
      arraylist取数真的太快了,linkedlist就算能从后面找,就算是取最后一个数,也慢了很多
      

      数组长度n=10000
      a=100
      b=n-a
      c=n/2
      可以看到:

      随机向中间添加,arraylist快
      add方法添加到最后,linkedlist完胜
      随机删除Arraylist快
      删除靠前元素:linkedlist快
      删除中间的,arralist快
      删除后面的,arralist快
      
      TestArrayList.testArrayListRandonAdd          thrpt    2    12289.462          ops/s
      TestArrayList.testLinkedListRandonAdd         thrpt    2    11362.013          ops/s
      TestArrayList.testArrayListRandonAddLast      thrpt    2    12820.688          ops/s
      TestArrayList.testLinkedListRandonAddLast     thrpt    2  2482660.154          ops/s
      TestArrayList.testArrayListRandonRemove       thrpt    2   207213.498          ops/s
      TestArrayList.testLinkedListRandonRemove      thrpt    2    11402.369          ops/s
      TestArrayList.testArrayListRemove100          thrpt    2   110029.496          ops/s
      TestArrayList.testLinkedListRemove100         thrpt    2  1036584.680          ops/s
      TestArrayList.testArrayListRemoveNDividing2   thrpt    2   205019.017          ops/s
      TestArrayList.testLinkedListRemoveNDividing2  thrpt    2     6156.114          ops/s
      TestArrayList.testArrayListRemoveN_100        thrpt    2  2064240.192          ops/s
      TestArrayList.testLinkedListRemoveN_100       thrpt    2  1072619.806          ops/s
      

      在这里插入图片描述
      在这里插入图片描述
      接下来下调参数
      n=1000
      a=10
      b=n-a
      c=n/2

      随机向中间添加,linkedlist快
      add方法添加到最后,linkedlist完胜
      随机删除Arraylist快
      删除靠前元素:linkedlist快
      删除中间的,arralist快
      删除后面的,linkedlist快
      
      Benchmark                                     Mode  Cnt  Score   Error  Units
      TestArrayList.testArrayListRandonAdd          avgt       0.912          us/op
      TestArrayList.testLinkedListRandonAdd         avgt       0.423          us/op
      TestArrayList.testArrayListRandonAddLast      avgt       0.788          us/op
      TestArrayList.testLinkedListRandonAddLast     avgt       0.042          us/op
      TestArrayList.testArrayListRandonRemove       avgt       0.194          us/op
      TestArrayList.testLinkedListRandonRemove      avgt       0.418          us/op
      TestArrayList.testArrayListRemove100          avgt       0.211          us/op
      TestArrayList.testLinkedListRemove100         avgt       0.043          us/op
      TestArrayList.testArrayListRemoveNDividing2   avgt       0.189          us/op
      TestArrayList.testLinkedListRemoveNDividing2  avgt       0.722          us/op
      TestArrayList.testArrayListRemoveN_100        avgt       0.151          us/op
      TestArrayList.testLinkedListRemoveN_100       avgt       0.039          us/op
      
      

      在这里插入图片描述

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

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

    相关文章

    funkyheatmap | 用这个包来完美复刻Nature Biotechnology的高颜值神图吧!~

    1写在前面 天气开始暖和了☀️&#xff0c;发现旅游的人好多啊&#xff01;~&#x1f972; 不知道自己什么时候能有时间出去看看外面的世界&#xff0c;实在是太忙了。&#x1f637; 最近用到的有个包感觉很不错&#xff0c;分享给大家&#xff0c;funkyheatmap包。&#x1f61…

    进程与多线程(入门)

    什么是线程 要了解什么是线程&#xff0c;得先知道什么是程序。 程序&#xff1a;为完成特定任务&#xff0c;用某种语言编写的一组指令的集合。 例如&#xff0c;QQ&#xff0c;Steam&#xff0c;亦或者java写的helloword。 这些都是程序 了解了程序&#xff0c;还得清楚什么…

    d3.js绘制饼状图,悬浮出现字以及点击事件

    代码以及注释如下&#xff1a; const width 300; // 定义圆的宽度 const height 300; // 定义圆的高度 const radius Math.min(width, height) / 2; // 算出半径 const color d3.scaleOrdinal() .range(["#98abc5", "#8a89a6", "#6b486b&qu…

    【MySQL高级篇】第05章_存储引擎

    第05章_存储引擎 1. 查看存储引擎 查看mysql提供什么存储引擎 show engines;2. 设置系统默认的存储引擎 查看默认的存储引擎 show variables like %storage_engine%; #或 SELECT default_storage_engine;修改默认的存储引擎 如果在创建表的语句中没有显式指定表的存储引擎…

    一分钟成为签到达人!Redis BitMap轻松解决,Spring Boot带你飞

    如何实现签到功能&#xff0c;尤其是如何实现高效的签到与统计&#xff0c;是开发者们需要考虑的问题。在本篇文章中&#xff0c;我们将介绍如何利用Spring Boot整合Redis BitMap实现签到与统计。 Redis BitMap简介 在介绍如何利用Redis BitMap实现签到与统计之前&#xff0c;…

    unity动画--动画绑定,转换,用脚本触发

    文章目录如何制作和添加动画大概过程示例图将多组图片转化为动画放在对象身上实现动画之间的切换使用脚本触发Parameters(Trigger)如何制作和添加动画 大概过程示例图 将多组图片转化为动画放在对象身上 首先&#xff0c;我们要为我们要对象添加animator 然后我们要设置对应的…

    计算机网络-应用层

    文章目录前言概述Https协议(443)Http协议(80)HttpsTLS/SSL 协议TLS的四次握手总结前言 本博客仅做学习笔记&#xff0c;如有侵权&#xff0c;联系后即刻更改 科普&#xff1a; 概述 Https协议(443) 参考网址 Http协议(80) 谈到Https必然要先将httpHTTP 请求报文结构 请求…

    谷粒学院开发(一):基础准备

    商业模式 常见商业模式 B2C模式&#xff1a; 两个角色&#xff1a; 管理员&#xff1a;增加&#xff0c;修改&#xff0c;删除普通用户&#xff1a;查询 商家到用户&#xff0c;自己制作大量自有版权的视频&#xff0c;放在自有平台上&#xff0c;让用户付费。 这是这个项目使…

    Linux下查看图片中某点的像素X、Y坐标

    在做目标检测、目标追踪的任务过程中&#xff0c;我们会用到一些开源的数据集&#xff0c;比如MOT16多目标追踪数据集。这些数据集会提供数据标注文件gt.txt,里面的内容如下1,1,912,484,97,109,0,7,12,1,912,484,97,109,0,7,13,1,912,484,97,109,0,7,14,1,912,484,97,109,0,7,1…

    [Openwrt]procd实现hotplug机制介绍

    Linux处理hotplug事件方法kobject_uevent() 产生 uevent 事件(lib/kobject_uevent.c 中), 产生的 uevent 先由 netlink_broadcast_filtered() 发出, 最后调用 uevent_helper[] 所指定的程序来处理.uevent_helper[] 里默认指定 "/sbin/hotplug", 但可以通过 /sys/kern…

    spring boot starter 实现生成行为验证码验证

    最近公司有一个验证用户行为的需求&#xff0c;因此实现了一个用户行为验证码的starter&#xff0c;具体效果如下&#xff1a;代码结构如下&#xff1a;common 下面放的是公共文件枚举类generator 下面放的是生成行为验证码的相关类与扩展接口resource 下面放的是加载解析行为图…

    若依代码生成器的使用

    一、代码生成器的使用1.新建maven模块原则上&#xff0c;我们的业务代码和若依系统本身的系统代码是要做隔离的&#xff0c;一方面是易于之后随着若依系统升级而升级&#xff0c;另一方面则是纯粹的合理性考虑。这里新建一个ruoyi-business模块作为业务代码模块&#xff0c;新建…

    Mac环境安装python

    一、介绍&#xff1a; Python是跨平台的&#xff0c;它可以运行在Windows、Mac和各种Linux/Unix系统上。在Windows上写Python程序&#xff0c;放到Linux上也是能够运行的。 要开始学习Python编程&#xff0c;首先就得把Python安装到你的电脑里。安装后&#xff0c;你会得到Pyt…

    LearnOpenGL-光照-4.光照贴图

    本人刚学OpenGL不久且自学&#xff0c;文中定有代码、术语等错误&#xff0c;欢迎指正 我写的项目地址&#xff1a;https://github.com/liujianjie/LearnOpenGLProject 文章目录光照贴图漫反射贴图例子1镜面光贴图例子2 采样镜面光贴图小结什么是光照贴图光照贴图如何影响颜色光…

    开源一个通用的 HTTP 请求前端组件

    像 Postman 这样可视化的 HTTP 请求工具是调试 API 不可或缺的利器。Postman 虽好但也越来越重&#xff0c;而且如果要整合到其他工具中&#xff0c;显然 Postman 又不是一个可行的方案。于是我想打造一个简单的前端组件&#xff08;widget&#xff09;&#xff0c;它是一个标准…

    天池 DeepRec CTR 模型性能优化大赛 - 夺冠技术分享

    作者&#xff1a;niceperf 团队 (李扬, 郭琳) 大家好&#xff0c;我们是 niceperf 团队&#xff0c;在天池 DeepRec CTR 模型性能优化大赛中&#xff0c;很荣幸取得了冠军的成绩 (Top 1/3802)。这篇文章复盘一下我们的参赛经验&#xff0c;希望对大家有所启发。 1.背景介绍 …

    KDZD5035系列电缆试验油杯

    一、概述 武汉凯迪正大总结十多年的局放试验经验&#xff0c;开发生产了KDZD5035系列电缆试验油杯终端&#xff0c;具有使用方便&#xff0c;性能可靠&#xff0c;本身局放量小等优点&#xff0c;与早期落地式油杯相比&#xff0c;可为用户节约大量的试验成本。 KDZD5520交流…

    单片机学习笔记之点阵(8x8)

    心血来潮&#xff0c;想捡一下丢了很久的单片机&#xff0c;纪录一下单片机学习简单的点阵显示&#xff0c;及踩到的䟘&#xff0c;找到吃灰很久的普中科技开发板&#xff08;非广告&#xff0c;为毕设学习买的&#xff09;。 1. 使用工具 使用开发板&#xff1a; 普中科技开发…

    Hive---自定义函数

    Hive自定义函数 文章目录Hive自定义函数定义自定义函数步骤创建一个Maven工程&#xff0c;导入依赖创建自定义函数类在 hive 的命令行窗口创建函数创建临时函数创建永久函数UDF打成 jar 包上传到服务器/opt/soft/hive312/lib/目录下将 jar 包添加到 hive 的 classpath建临时函数…

    python数据类型与数据结构

    目录 一、数据类型 1.1变量与常量 1.1.1变量 1.1.2常量 1.2字符串类型 1.3整数与浮点数 1.4List列表 1.5 元组tuple 1.6字典dict 二、字符串格式化 三、数据输入和类型转换 四、简单列表习题练习 一、数据类型 变量类型&#xff1a; 整数int&#xff08;4字节&#x…