ArrayList源码解析(一)

2023-11-17

以下分析均以jdk1.8为准。

首先来看一下ArrayList的继承体系:
ArrayList继承体系
ArrayList继承自AbstractList,实现了 List, Cloneable, Serializable, RandomAccess接口。这一点从源码上也可以看到

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable

先来看看ArrayList的内部成员概览吧。
这里写图片描述
从上图看到ArrayList内部,有7个成员变量,4个内部类。

  private static final long serialVersionUID = 8683452581122892189L;   //用作序列化,暂不管
//默认初始化容量为10
    private static final int DEFAULT_CAPACITY = 10;

//空的对象数组
    private static final Object[] EMPTY_ELEMENTDATA = {};

//默认的空对象数组,调用ArrayList的无参构造函数时创建的对象数组。                                   
    private static final Object[] EFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

// 实际存放List元素的对象数组缓存变量。ArrayList的容量就是这个数组的长度(length)。
    transient Object[] elementData;

//ArrayList的大小,实际所拥有元素的数量。
    private int size;

//最大数组容量,大约为2147483639。20多亿,绝对够用了
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

注意到elementData被transient所修饰。
有个关键字需要解释:transient。

Java的serialization提供了一种持久化对象实例的机制。当持久化对象时,可能有一个特殊的对象数据成员,我们不想用serialization机制来保存它。为了在一个特定对象的一个域上关闭serialization,可以在这个域前加上关键字transient。
tansient是Java语言的关键字,用来表示一个域不是该对象串行化的一部分。当一个对象被串行化的时候,transient型变量的值不包括在串行化的表示中,然而非transient型的变量是被包括进去的。

带默认初始化容量的构造函数 ArrayList ( int initialCapacity )

public ArrayList(int initialCapacity) {
    //如果初始化容量大于0
    if (initialCapacity > 0) {
    //直接new一个该大小的对象数组赋值给elementData
           this.elementData = new Object[initialCapacity];
       } else if (initialCapacity == 0) {  //如果初始化容量为0
       //直接将空数组赋值给elementData
           this.elementData = EMPTY_ELEMENTDATA;
       } else {//初始化参数为负数,抛出异常,非法参数
            throw new IllegalArgumentException("Illegal Capacity:"+initialCapacity);
        }
    }

无参构造函数ArrayList( )

 public ArrayList() {
 //将空数组赋值给elementData,这时候容量还是0,之后添加元素时会扩容
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

通过指定的集合对象c来构造ArrayList( Collection< ? extends E  > c )

按照集合对象的迭代器返回的元素顺序进行填充。如果集合对象为null,抛出NullPointerException异常

 public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();//将集合c转换为对象数组再赋值给elementData。不同的Collection实现有各自不同的toArray()方法。
        if ((size = elementData.length) != 0) {//elementData大小不为0
            if (elementData.getClass() != Object[].class) //c.toArray()返回的不一定是Object[],这里进行一个类型转换
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            //如果c.toArray()为空数组,elementData默认为空数组
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

修整容量为当前实际大小: trimTosize( )。

(容量一般会由于扩容的原因大于实际大小,例如添加了一个元素的ArrayList实例容量为10,大小为1,该操作就是将容量缩小为1),该操作可以减少ArrayList的内存使用。但执行完trimTosize()方法之后如果再添加元素,还是会进行扩容。所以除非知道在之后不会再添加元素,否则调用trimTosize()方法没有意义。

public void trimToSize() {
        modCount++;
        if (size < elementData.length) {
            elementData = (size == 0)
              ? EMPTY_ELEMENTDATA
              : Arrays.copyOf(elementData, size); //利用Arrays.copyOf()方法对数组进行复制重组
        }
    }

当前元素数量size( )

public int size() {
        return size;
    }

获取对象索引:indexOf( Object o )

返回对象第一次出现时的索引,如果没有该对象,返回-1。

 public int indexOf(Object o) {
        if (o == null) {
            for (int i = 0; i < size; i++)
                if (elementData[i]==null)
                    return i;
        } else {
            for (int i = 0; i < size; i++)
                if (o.equals(elementData[i]))
                    return i;
        }
        return -1;
    }

首先判断传入对象o是否为null,如果是null,for循环遍历elementData,如果找到某个元素为null,则返回该元素的索引值。如果o不为null,for循环遍历elementData,通过equals判断如果有某个元素等于指定对象o,则返回该元素的索引。如果找不到该对象,返回-1。
这里通过对象的equals方法判断元素是否为某个对象。

获取最后一次出现的索引 lastIndexOf( Object o )


/**
     * Returns the index of the last occurrence of the specified element
     * in this list, or -1 if this list does not contain the element.
     */

    public int lastIndexOf(Object o) {
        if (o == null) {
            for (int i = size-1; i >= 0; i--)
                if (elementData[i]==null)
                    return i;
        } else {
            for (int i = size-1; i >= 0; i--)
                if (o.equals(elementData[i]))
                    return i;
        }
        return -1;
    }

逻辑跟indexOf类似,只不过从数组末尾向前遍历。

是否包含某元素:contains(&nsbsp;Object o )

public boolean contains(Object o) {
        return indexOf(o) >= 0;
    }

通过判断对象索引是否>=0。

将list转换为对象数组 toArray()

新对象数组的长度为list的size

 public Object[] toArray() {
        return Arrays.copyOf(elementData, size);
    }

返回的是Object[],并不是对应的泛型数组,而且也不能强制转换为对应的泛型数组。如果需要转化为对于的泛型数组,可以参考 public < T > T[] toArray(T[] a) 方法。
举例说明:

 ArrayList<Integer> list = new ArrayList<Integer>();
        list.addAll(Arrays.asList(1,2,3,4,5,6,7,8,9));
        Object[] objects = list.toArray();
        Integer[] s = (Integer[])objects;  //抛出异常:
        //java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.Integer;

返回的数组是重现分配了内存的,跟原来的list已经脱离,相互不影响

将list转换为对应的泛型数组 toArray(T[] a)

/**    
     * @return an array containing the elements of the list
     * @throws ArrayStoreException if the runtime type of the specified array is
     *  not a supertype of the runtime type of every element in this list
//如果a的类型不是list中所有元素的超类,那么抛出ArrayStoreException异常。
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;
    }

从源码上看,当a.length < size时,创建一个新的数组并将elementData的数据复制到新数组中,返回新数组。这没有任何问题。

需要注意的是:当a.length>size时,将elementData的数据复制到a,将a[size]置为null。虽然不明白为什么要这么做,但需要注意。最终返回a。例如我写了下面一个测试例子,会导致一些预料之外的事情。

    List<Integer> list = new ArrayList<Integer>();
    list.addAll(Arrays.asList(1,2,3,4,5,6,7,8,9));
    Integer[] intArr = {1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5};  // intArr的长度大于list的size
    Integer[] result = list.toArray(intArr);
    for (int i = 0; i < intArr.length; i++) {
        System.out.print(intArr[i]+" ");  //输出1 2 3 4 5 6 7 8 9 null 4 4 5 5 5 . 
       //由于intArr[size] = null。 size为9的数据被置空了,这显然不是我们需要的。
    }
    //而且需要注意的是:intArr和返回值result指向了同一个数组对象,修改其中一个会影响另一个
    System.out.println();
    for (int i = 0; i < result.length; i++) {
        System.out.print(result [i]+" "); //输出1 2 3 4 5 6 7 8 9 null 4 4 5 5 5
    }
    System.out.println();
    result[0] = 0; result[1] = 0; //修改result,也会同步影响intArr。
    for (int i = 0; i < intArr.length; i++) {
        System.out.print(intArr[i]+" "); //输出0 0 3 4 5 6 7 8 9 null 4 4 5 5 5
    }

对此,我的建议是:传递给toArray(T[] a)的数组a为空数组(没有填充数据),只要指明相应的类型就可以。Integer[] intArr = list.toArray(new Integer[]{})

通过索引获取对象 E get(int index)

public E get(int index) {
     if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

     return (E)elementData(index);
    }

设置指定索引的值

 public E set(int index, E element) {
      if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

      E oldValue = elementData(index);
      elementData[index] = element;
      return oldValue;
   }

清空list: clear()

public void clear() {
        modCount++;

        // clear to let GC do its work
        for (int i = 0; i < size; i++)
            elementData[i] = null;

        size = 0;
    }

clear()只是将元素都置为null,将size设置为0。但不会修改容量。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

ArrayList源码解析(一) 的相关文章

  • 【计算机操作系统】第二章 进程管理

    1 进程的基本概念 1 1 程序的顺序执行和特征 程序顺序执行时的特征 顺序性 处理机的操作严格按照程序所规定的顺序执行 即每一操作必须在上一个操作结束之后开始 封闭性 程序是在封闭的环境下执行的 即程序运行时独占全机资源 资源的状态 除初
  • 大数据课程C5——ZooKeeper的应用组件

    文章作者邮箱 yugongshiye sina cn 地址 广东惠州 本章节目的 掌握Zookeeper的Canal消费组件 掌握Zookeeper的Dubbo分布式服务框架 掌握Zookeeper的Metamorphosis消息中间件 掌
  • 快速性分析 一阶、二阶系统响应

    自控笔记 3 3一阶系统的时间响应及动态性能 一 一阶系统的数学模型 凡是以一阶微分方程作为运动方程的控制系统 称为一阶系统 其数学模型为 时间常数T是表征系统响应的唯一参数 T越小 输出响应上升得越快 调节时间越小 二 一阶系统的典型响应
  • 华为OD机试 - 符合要求的元组的个数(Java & JS & Python)

    题目描述 给定一个整数数组 nums 一个数字k 一个整数目标值 target 请问nums中是否存在k个元素使得其相加结果为target 请输出所有符合条件且不重复的k元组的个数 数据范围 2 nums length 200 10 9 n
  • Java Thread.currentThread()方法具有什么功能呢?

    转自 Java Thread currentThread 方法具有什么功能呢 下文讲述Thread currentThread 方法的功能简介说明 如下所示 Thread currentThread 方法的功能 返回当前线程 注意事项 Th
  • Java实现CSV读写

    在开发过程中经常需要处理csv文件 我一般是实现一个CSVHelper 封装一些对csv文件的基本操作 代码中直接使用封装好的CSVHelper来读写csv文件就可以了 今天就来记录一下如何通过Java实现封装的csv文件的读写 对于C 实
  • 弱网测试

    来自 https www cnblogs com linxiu 0925 p 9412190 html 什么是弱网测试 弱网测试主要在宽带 丢包 延时的弱网环境中 验证客户端的展示 以及丢包 延时的处理机制 属于健壮性测试的内容 比如弱网下
  • STM32移植U8g2图形库——玩转OLED显示

    之前的文章 介绍过ESP8266在Arduino IDE环境中使用U8g2库 实现OLED上的各种图形显示 本篇 介绍一下U8g2库如何移植到STM32上 进行OLED的图形显示 本次的实验硬件为 STM32 型号为最常见的STM32F10
  • 前事不忘,后事之师——基于相似性进行剩余有效寿命预测的案例讲解

    在上一篇文章中我们讲到了三种机电产品算命方法 相似模型法 退化模型法和生存模型法 这一篇我们将使用相似模型法构建完整的剩余使用寿命 RUL 估计工作流程 该案例来自MATLAB的Similarity Based Remaining Usef
  • JavaScript设计模式(三)——单例模式、装饰器模式、适配器模式

    个人简介 个人主页 前端杂货铺 学习方向 主攻前端方向 正逐渐往全干发展 个人状态 研发工程师 现效力于中国工业软件事业 人生格言 积跬步至千里 积小流成江海 推荐学习 前端面试宝典 Vue2 Vue3 Vue2 3项目实战 Node js
  • SpringBoot快速入门

    SpringBoot快速入门 1 SpringBoot简介 SpringBoot概述 SpringBoot起步依赖 SpringBoot程序启动 入门案例 SpringBoot项目快速启动 2 基础配置 自动提示功能消失解决方案 yaml语
  • docker创建多个mysql集群_Docker在一台服务器上安装和配置Mysql集群

    1 从docker hub下载mysql5 6的镜像 docker pull mysql 5 6 2 使用mysql5 6镜像运行4台mysql服务 用端口号区分 前期准备工作 在本机创建四个目录 分别用了存储4台mysql服务的数据 日志
  • Windows服务器管理(运维)——cmd命令大全

    1 文件和目录操作命令 cd 更改当前目录 dir 列出当前目录中的文件和文件夹 mkdir 创建一个新的文件夹 rmdir 删除一个空的文件夹 copy 复制文件或文件夹 del 删除文件 ren 重命名文件或文件夹 move 移动文件或
  • angular html原理,Angular 4.x ngModel 双向绑定原理揭秘

    在 Angular 4 x 中对于使用 Template Driven 表单场景 如果需要实现表单数据绑定 我们就需要引入 ngModel 指令 该指令用于基于 domain 模型 创建 FormControl 实例 并将创建的实例绑定到表

随机推荐

  • java日志级别

    java中日志级别有7 个级别 severe Warning info config fine finer finest 默认情况只记录前三个级别 另外可以使用Level ALL开启所有的级别记录 或者使用Level OFF关闭所有的级别记
  • android动静态申请IMEI或其他特殊权限(适配11)

    报错原因 今天又是撸代码的一天 人生第一个项目上架闪退被打回 很难受 打开就闪退 后面才恍然大悟 打开APP默认申请获取手机IMEI 测试用的手机被我手动打开了权限 所以一直没有注意这个问题 果然 log报错 java lang Secur
  • pjsip的一个qt写的demo

    msvc版本编译的pjsip的demo 有源码 也有可直接运行的包 本程序解决了pjsip双方互相同时呼叫时会出现的问题 目前只是用来呼叫接听的demo 没有做流媒体传输 https download csdn net download q
  • 【C语言】使用C语言实现静态、动态的通讯录(简单易懂)

    我们在学习结构体之后 就可以尝试去实现通讯录的制作 如果您这边对于结构体还没有太多的认识的话 请先访问这一篇文章 会有利于接下来的学习 自定义类型 带你走进结构体 枚举 联合 小王学代码的博客 CSDN博客 目录 一 通讯录 二 静态通讯录
  • Java自增和自减运算符(++和--)

    在对一个变量做加 1 或减 1 处理时 可以使用自增运算符 或自减运算 或 是单目运算符 放在操作数的前面或后面都是允许的 与 的作用是使变量的值增 1 或减 1 操作数必须是一个整型或浮点型变量 自增 自减运算的含义及其使用实例如表 1
  • Flutter实现倒计时功能,秒数转时分秒,然后倒计时

    Flutter实现倒计时功能 发布时间 2023 05 12 本文实例为大家分享了Flutter实现倒计时功能的具体代码 供大家参考 具体内容如下 有一个需求 需要在页面进行显示倒计时 倒计时结束后 做相应的逻辑处理 实现思路 在Flutt
  • 牛客中等难度3

    HJ70 矩阵乘法计算量估算 描述 矩阵乘法的运算量与矩阵乘法的顺序强相关 例如 A是一个50 10的矩阵 B是10 20的矩阵 C是20 5的矩阵 计算A B C有两种顺序 AB C 或者 A BC 前者需要计算15000次乘法 后者只需
  • 异常处理包装技术

    异常大致可分为两种 受检查异常和非受检查异常 受检查异常是在编译期间就可以检查到的 非受检查异常又分为error和RuntimeException 非受检查异常是可控的 可以人为操作修改的 一般我们针对业务异常 非受检查异常 进行处理 会继
  • 鸿蒙系统是否可以用来做服务器,小米手机也能使用鸿蒙系统?国内厂商使用鸿蒙热情高涨...

    原标题 小米手机也能使用鸿蒙系统 国内厂商使用鸿蒙热情高涨 自从6月2日HarmonyOS 2正式发布以来 国内针对鸿蒙系统的热议一直不减 对于新买的华为手机用户来说 能第一批次使用上国产的手机系统 也确实过了一把瘾 本次手机系统更新 华为
  • Android蓝牙开发教程(三)——蓝牙设备相互通讯

    在上一篇中已经介绍如何连接我们搜索到的蓝牙设备 如果你还没阅读过 建议先看看上一篇文章Android蓝牙开发教程 二 连接蓝牙设备 在上一篇文章中 无论是自动连接还是被动连接 连接成功后 都是将获取到的BluetoothSocket交由连接
  • 根据眼动数据的模板作为KNN聚类的中心点并因此进行数据分类

    from scipy io import loadmat import numpy as np import matplotlib pyplot as plt 实验数据采集分为两个过程 第一个是眼动校准阶段 要求实验参与者依次观看界面上的数
  • VMWare 6.5.3 绿色精简版汉化 +VMware Workstation 6.5.3 Build 185404 汉化绿色精简版

    绿色精简版 参考网上6 5 X几个绿色精简版更新制作 bat不加密 不加入个人信息 喜欢研究的随便看 精简版一般使用够用了 高手估计会觉得缺少某些功能了 那就只能装完整版了 bridge 桥接 usb服务 host only都可以使用 VM
  • C++23新特性个人总结

    文章目录 1 关键字 1 1 consteval 1 2 auto 1 2 1 新增支持数组指针的引用类型 1 2 2 代替decay copy语义 1 3 volatile 1 4 constexpr 1 5 char8 t 1 6 wc
  • 【自动化风控建模系列1】最简洁的代码实现特征初步筛选

    金融信贷开发评分卡时 通常会准备好特征中间层供评分卡开发筛选使用 评分卡的特征选择余地越大 后期越是有可能开发出性能更高的评分卡 但特征变量的选择在此时就成为第一个问题 如何初步筛选出合适的变量 基于经验 我认为第一步的筛选只需要剔除那些极
  • Educoder---Java面向对象 - 集合框架(1)

    第一题 请仔细阅读右侧代码 根据方法内的提示 在Begin End区域内进行代码补充 创建ArrayList集合并且向集合中添加数据 具体要求如下 添加字符串类型数据 https www educoder net 添加double类型数据
  • 偏移注入payload构造技巧实战+Access注入

    url http 218 245 4 113 8888 web03 ca55022fa7ae5c29d179041883fe1556 index asp id 886 拿到url 虽然知道肯定是id是注入点 但还是写一下完整思路 1 拿到界
  • node环境实现console输出不同颜色

    一 输出规则分析 1 输出及打印如下 console log x1B 31m s x1B 0m 这是红色 console log x1B 36m s x1B 0m 这是青色 2 规则说明 x1B 31m 是一个转义序列 它将被您的终端拦截并
  • 【翻译】Dart和Flutter是什么?

    Dart是在Go之后从谷歌出现的 最近作为Flutter跨平台前端框架背后的语言 其受欢迎程度激增 这对那些对云原生基础设施感兴趣的人来说很重要 因为有一种对 全栈Dart 的推动 Flutter开发者可以使用相同的语言来构建他们应用程序背
  • python文件打开的合法模式组合wr_使用Python来操作你的路由器(TP_LINK WR885N)

    开始之前咱们先了解一下TPLINK WR885N这款设备 官方地址为 http www tp link com cn product 368 html 针对官方介绍 这里博主做个简短的讲解 首先看到的是官方的路由器图片 外观还是不错的 博主
  • ArrayList源码解析(一)

    以下分析均以jdk1 8为准 首先来看一下ArrayList的继承体系 ArrayList继承自AbstractList 实现了 List Cloneable Serializable RandomAccess接口 这一点从源码上也可以看到