• ArrayList 源码解析和设计思路学习记录


    整体架构

    ArrayList 整体架构比较简单,就是一个数组结构,比较简单.

    DEFAULT_CAPACITY 表示数组的初始大小,默认是 10
    size 表示当前数组的大小,类型 int,没有使用 volatile 修饰,非线程安全的;
    modCount 统计当前数组被修改的版本次数,数组结构有变动,就会 +1。

    类注释

    允许 put null 值,会自动扩容;
    size、isEmpty、get、set、add 等方法时间复杂度都是 O (1);

    是非线程安全的,多线程情况下,推荐使用线程安全类:

    Collections#synchronizedList;

    增强 for 循环,或者使用迭代器迭代过程中,如果数组大小被改变,会快速失败,抛出异常。

    初始化方法

    无参数直接初始化、指定大小初始化、指定初始数据初始化

    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    
    //无参数直接初始化,数组大小为空
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
    
    //指定初始数据初始化
    public ArrayList(Collection<? extends E> c) {
        //elementData 是保存数组的容器,默认为 null
        elementData = c.toArray();
        //如果给定的集合(c)数据有值
        if ((size = elementData.length) != 0) {
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            //如果集合元素类型不是 Object 类型,我们会转成 Object
            if (elementData.getClass() != Object[].class) {
                elementData = Arrays.copyOf(elementData, size, Object[].class);
            }
        } else {
            // 给定集合(c)无值,则默认空数组
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }
    

    ArrayList 无参构造器初始化时,默认大小是空数组,并不是大家常说的 10,10 是在第一次 add 的时候扩容的数组值。

    指定初始数据初始化时,我们发现一个这样子的注释 see 6260652,这是 Java 的一个 bug,意思是当给定集合内的元素不是 Object 类型时,我们会转化成 Object 的类型。一般情况下都不会触发此 bug,只有在下列场景下才会触发:ArrayList 初始化之后(ArrayList 元素非 Object 类型),再次调用 toArray 方法,得到 Object 数组,并且往 Object 数组赋值时,才会触发此 bug。不过该问题已在java9中被解决。

    新增和扩容实现

    新增就是往数组中添加元素,主要分成两步:

    判断是否需要扩容,如果需要执行扩容操作。
    直接赋值。

    public boolean add(E e) {
      //确保数组大小是否足够,不够执行扩容,size 为当前数组的大小
      ensureCapacityInternal(size + 1);  // Increments modCount!!
      //直接赋值,线程不安全的
      elementData[size++] = e;
      return true;
    }
    

    扩容(ensureCapacityInternal)的源码

    private void ensureCapacityInternal(int minCapacity) {
      //如果初始化数组大小时,有给定初始值,以给定的大小为准,不走 if 逻辑
      if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
      }
      //确保容积足够
      ensureExplicitCapacity(minCapacity);
    }
    private void ensureExplicitCapacity(int minCapacity) {
      //记录数组被修改
      modCount++;
      // 如果我们期望的最小容量大于目前数组的长度,那么就扩容
      if (minCapacity - elementData.length > 0)
        grow(minCapacity);
    }
    //扩容,并把现有数据拷贝到新的数组里面去
    private void grow(int minCapacity) {
      int oldCapacity = elementData.length;
      // oldCapacity >> 1 是把 oldCapacity 除以 2 的意思
      int newCapacity = oldCapacity + (oldCapacity >> 1);
    
      // 如果扩容后的值 < 我们的期望值,扩容后的值就等于我们的期望值
      if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    
      // 如果扩容后的值 > jvm 所能分配的数组的最大值,那么就用 Integer 的最大值
      if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
     
      // 通过复制进行扩容
      elementData = Arrays.copyOf(elementData, newCapacity);
    }
    

    扩容的规则并不是翻倍,是原来容量大小 + 容量大小的一半,直白来说,扩容后的大小是原来容量的 1.5 倍;

    ArrayList 中的数组的最大值是 Integer.MAX_VALUE,超过这个值,JVM 就不会给数组分配内存空间了。

    新增时,并没有对值进行严格的校验,所以 ArrayList 是允许 null 值的。

  • 相关阅读:
    PHP常见问题总结
    Java常见问题总结(二)
    C语言常见问题总结
    C#常见问题总结(三)
    C#常见问题总结(二)
    Android常见问题总结(二)
    日期和时间类函数
    Eclipse开发工具介绍
    JavaScript中逻辑运算符的使用
    多路开关模式的switch语句
  • 原文地址:https://www.cnblogs.com/AmosAlbert/p/12832183.html
Copyright © 2020-2023  润新知