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

ArrayList 源码浅析

类的关系

ArrayList 继承了 AbstractList,并实现了 List、RandomAccess、Cloneable 和 Serializable 接口,List 是 Collection 的子接口,RandomAccess 是标识性接口,代表 ArrayList 具有快速随机访问的能力;Cloneable 表示 ArrayList 实现了 clone 方法,是浅拷贝;Serializable 代表 ArrayList 可以进行 序列化 / 反序列化 操作。

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

几个成员变量

    /**
     * 序列化 ID
     */
	private 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.
     * 
     * 空数组的实例,只有我们调用 ArrayList 的无参构造器时才会使用,目的是为了与另外两个构造器的为空的情况区分开来
     */
    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.
     * 
     * ArrayList 中真正存储元素的底层数组,即 elementData,Object 数组,ArrayList 的容量就是这个数组的长度
     * 空的 ArrayList 为空数组,只有第一次添加元素时,才会扩容至默认大小10
     */
    transient Object[] elementData; // non-private to simplify nested class access

    /**
     * The size of the ArrayList (the number of elements it contains).
     * 
     * ArrayList 中所存储的元素数量
     */
    private int size;

三个构造方法

	/**
     * Constructs an empty list with the specified initial capacity.
     *
     * 可指定初始容量的构造方法,如果初始容量为0,则使用 EMPTY_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);
        }
    }

    /**
     * Constructs an empty list with an initial capacity of ten.
     * 
     * 无参构造器,使用 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 空数组
     */
    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.
     *
     * 可传入集合构造器,如果集合为空,则使用 EMPTY_ELEMENTDATA 空数组
     */
    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;
        }
    }

为什么要使用两个不同的空数组呢?

这里需要看一下 add 方法的源码

	public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

add 方法执行时,先会判断一下容量,通过 ensureCapacityInternal 方法确保 Object 数组 可以装下 size + 1 个元素

跟一下 ensureCapacityInternal 方法,这个方法会调用 calculateCapacity 方法,计算需要最终需要扩容的大小,而 calculateCapacity 会检查 elementData 是否是 空参构造器 的默认容量的空数组,如果为真,则返回 默认容量 10 与 最小容量 的最大值,否则直接返回最小容量。

可以看出,这两个空数组主要是在扩容时用到了,用于区分 ArrayList 是我们指定初始容量为0 或 传入空集合,还是调用了空参构造器的默认容量 10

	private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }
    
    private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }

ArrayList 扩容机制
每一次 add 新元素 ArrayList 都会确保容量的大小,如果需要扩容,即 ensureExplicitCapacity 方法中,最小容量 小于 当前数组的长度,则需要进行底层数组扩容,调用 grow 方法。

	private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

在 grow 方法中,先拿到了 oldCap,并进行了 1.5 倍扩容,通过 oldCap + oldCap >> 1,右移一位相当于除以 2,并且效率更高;

我们还能注意到,grow 中有一行注释 overflow-conscious code,表示这部分代码考虑到了溢出的情况。我们来看看:

newCap 为扩容了之后的容量,如果 扩容了之后的 newCap 依然小于 minCap,那么就直接将 minCap 赋值给 newCap,不过这种情况应该很少出现。
如果 newCap 大于了 最大数组容量限制,那么就代表 newCap 发生了 int 溢出,说明 1.5 的扩容太大了,调用 hugeCapacity 来对 minCap 进行尽可能的扩容,hugeCapacity 先检查 minCap 是否发生溢出,然后在 最大数组容量限制上尽可能的扩容。

    /**
     * The maximum size of array to allocate.
     * Some VMs reserve some header words in an array.
     * Attempts to allocate larger arrays may result in
     * OutOfMemoryError: Requested array size exceeds VM limit
     * 
     * 最大数组长度,防止某些 VM 会 OOM
     */
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

    /**
     * Increases the capacity to ensure that it can hold at least the
     * number of elements specified by the minimum capacity argument.
     *
     * @param minCapacity the desired minimum capacity
     */
    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

Arrays.copyOf 与 System.arraycopy

源码如下,Arrays.copyOf 底层调用了 System.arraycopy,而 System.arraycopy 是一个 native 方法;
从功能上看,Arrays.copyOf 是对数组进行扩容的复制,System.arraycopy 是将一个数组复制到另外一个数组上。

	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;
    }
    
	public static native void arraycopy(Object src,  int  srcPos,
                                        Object dest, int destPos,
                                        int length);

为什么是 1.5 倍扩容呢?

个人理解原因应该是:为了取得性能与内存的折衷,如果为 2 倍扩容,可能会有更大的空间浪费,如果为更小的扩容可能会导致扩容更加频繁!而且 1.5 倍可以充分利用位运算的优势!

为什么不能使用 foreach 对集合进行 remove / add 操作呢?

foreach 是 增强 for 循环,Java 中的一种语法糖,实际上底层还是调用了 iterator 迭代器,但是非 fail-safe的集合具有 fail-fast 机制即快速失败机制,我们在对 ArrayList 做修改时,会记录一个 modCount 变量,而 iterator 也会有一个变量 expectedModCount,Iterator.next 会调用Iterator.checkForComodification 方法检查 modCount 与 expectedModCount 是否相等,不相等会抛出 并发修改异常 ConcurrentModificationException。

对于 expectedModCount,我们在通过 list.iterator() 获取构造器时,会将 modCount 赋值给 expectedModCount,但是!只有采用 iterator 的 remove / add,才会触发 expectedModCount 的修改,否则 expectedModCount 没有改变,这就导致了与 modCount 的不一致!

也就是说,在 foreach 循环中,集合遍历是通过 Iterator 进行的,但是元素的 add/remove 却是直接使用集合类自己的方法,这就导致 Iterator 在遍历的时候会发现有一个元素在自己不知不觉的情况下就被添加/删除了,就会抛出一个异常,用来提示用户可能发生了并发修改

相关文章:

  • 毕业设计 基于单片机的智能音响设计与实现 -物联网 嵌入式 stm32
  • 【区块链】从社区平台MOJOR看,为何Web3需要原生?
  • 2022年 研究生数学建模题目
  • C# 算数运算符
  • TC8:TCP_HEADER_01-11
  • Mysql出现问题:慢查询日志失效解决方案
  • Unity接入TopOn聚合广告平台SDK【聚合了穿山甲,优量汇(腾讯广告),快手,Mintegral,sigmob等各大广告平台SDK】
  • 【leetcode】和最小的 k 个数对
  • Java 程序控制结构(4)
  • C++动态空间申请
  • WEB安全之javascript基础(一):js的引入方法注释变量数据类型
  • 【node进阶】深度解析Express框架--路由、中间件
  • 硬件设计基础----通信协议UART
  • Python的基础语法
  • 详解CAN总线:CAN总线报文格式—过载帧
  • 【Leetcode】101. 对称二叉树
  • 时间复杂度分析经典问题——最大子序列和
  • 【5+】跨webview多页面 触发事件(二)
  • 〔开发系列〕一次关于小程序开发的深度总结
  • Apache的基本使用
  • CentOS 7 修改主机名
  • CentOS7 安装JDK
  • JavaScript类型识别
  • Traffic-Sign Detection and Classification in the Wild 论文笔记
  • vue.js框架原理浅析
  • 阿里研究院入选中国企业智库系统影响力榜
  • 今年的LC3大会没了?
  • 聊一聊前端的监控
  • 深度解析利用ES6进行Promise封装总结
  • 腾讯大梁:DevOps最后一棒,有效构建海量运营的持续反馈能力
  • 做一名精致的JavaScripter 01:JavaScript简介
  • linux 淘宝开源监控工具tsar
  • 继 XDL 之后,阿里妈妈开源大规模分布式图表征学习框架 Euler ...
  • 数据库巡检项
  • #NOIP 2014# day.2 T2 寻找道路
  • (1综述)从零开始的嵌入式图像图像处理(PI+QT+OpenCV)实战演练
  • (20050108)又读《平凡的世界》
  • (Java)【深基9.例1】选举学生会
  • (ZT)一个美国文科博士的YardLife
  • (分享)一个图片添加水印的小demo的页面,可自定义样式
  • (附源码)springboot宠物医疗服务网站 毕业设计688413
  • (附源码)springboot社区居家养老互助服务管理平台 毕业设计 062027
  • (附源码)ssm高校实验室 毕业设计 800008
  • (附源码)ssm户外用品商城 毕业设计 112346
  • (离散数学)逻辑连接词
  • (论文阅读40-45)图像描述1
  • (原創) 未来三学期想要修的课 (日記)
  • (转)自己动手搭建Nginx+memcache+xdebug+php运行环境绿色版 For windows版
  • .net core Swagger 过滤部分Api
  • .NET4.0并行计算技术基础(1)
  • .NET单元测试
  • .NET简谈互操作(五:基础知识之Dynamic平台调用)
  • @transaction 提交事务_【读源码】剖析TCCTransaction事务提交实现细节
  • @软考考生,这份软考高分攻略你须知道
  • [ 第一章] JavaScript 简史