• 1、ArrayList源码解析


    1 概述

    1. ArrayList的元素:有序、可重复、允许null
    2. ArrayList没有实现同步(synchronized),因此线程不安全的。(vector线程安全)
    3. ArrayList底层数据结构为数组,容量(capacity):表示底层数组长度。容量不足则触发扩容,创建一个更长的数组,并将元素迁移到新数组
    4. 关于数组:数组一旦被创建,长度不可变
    5. 支持泛型

    2 底层数据结构

    几个重要的成员变量

        transient Object[] elementData; //存储列表元素的数组,容量(capacity)就是elementData的长度
        private int size;//元素的数量
        protected transient int modCount = 0; //list的修改次数
        private static final int DEFAULT_CAPACITY = 10; //初始化没有指定容量时,容量达到10就会触发扩容
    

    ps:size跟capacity不一样,capacity>=size

    3 构造函数

    三个构造函数

    1. 指定初始容量大小,创建Object类型数组,且长度等于参数值
    2. 未指定初始容量大小,数据数组赋值为一个无限容量的空数组
    3. 造参数为集合类型,将参数转换成Object类型数组,并赋值给数据数组

    注意,只有当第一次add元素时,才会指定数组的长度

    源码:

        // 1、指定初始容量大小时,创建Object类型数组,且长度等于参数值
        public ArrayList(int initialCapacity) {
            if (initialCapacity > 0) {
                this.elementData = new Object[initialCapacity];
            } else if (initialCapacity == 0) {
                this.elementData = EMPTY_ELEMENTDATA; //参数值为0,数据数组赋值为一个无限容量的空数组
            } else {
                throw new IllegalArgumentException("Illegal Capacity: "+
                                                   initialCapacity);
            }
        }
    // 2 不指定初始容量大小时,数据数组赋值为一个无限容量的空数组
        public ArrayList() {
            this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
        }
    // 3 构造参数为集合类型,将参数转换成Object类型数组,并赋值给数据数组
        public ArrayList(Collection c) {
            elementData = c.toArray();
            if ((size = elementData.length) != 0) {
                // c.toArray might (incorrectly) not return Object[] (see 6260652)
                if (elementData.getClass() != Object[].class)
                    elementData = Arrays.copyOf(elementData, size, Object[].class);
            } else {
                // replace with empty array.
                this.elementData = EMPTY_ELEMENTDATA;
            }
        }
    
    

    4 自动扩容

    1. 添加元素时,会判断添加后是否超出当前数组长度,超出则会执行数组扩容;
    2. 数组扩容时,会将老数组中的元素拷贝到新数组
    3. 如果未指定初始容量,那么容量达到10就会触发扩容
    4. 数组扩容后的容量为原来的1.5倍
    5. 尽可能评估所需要容量的大小,避免扩容。(因为扩容占用更多的内存)

    参考源码:

    重点!!!!!:添加前,都判断是否需要扩容:(如果size+1后,超过elementData的长度,则执行扩容,扩容为原来的1.5倍

    
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // size 初始值为0,
        elementData[size++] = e;
        return true;
    }
    
    private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }
    
    //计算所需容量:初始化未指定容量,第一次添加元素时,扩容阈值为10;反则,容量为当前长度+1
    private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);//DEFAULT_CAPACITY = 10,即第一次添加时,数组长度变为10
        }
        return minCapacity;//如果数组不为空时,最小长度是当前长度+1
    }
    
    //再次计算数组所需容量
    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;//列表修改次数递增
        //所需容量大于数组的长度,则执行扩容
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
    //扩容,数组的内存状态已经发生变化了
    private void grow(int minCapacity) {
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);//  新的长度为旧长度的1.5倍  【右移1位(除以2)】
        if (newCapacity - minCapacity < 0)  // 如果扩容后的长度小于所需要的最小长度,则使用最小长度(基本不会发生)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)  //这里是极限的情况,即逼近数组分配的最大内存空间
            newCapacity = hugeCapacity(minCapacity);
        elementData = Arrays.copyOf(elementData, newCapacity);  // 执行数组拷贝
    }
    

    5 set() get() remove()

    • set()方法也就变得非常简单,直接对数组的指定位置赋值即可。
    public E set(int index, E element) {
        rangeCheck(index);//下标越界检查
        E oldValue = elementData(index);
        elementData[index] = element;//赋值到指定位置,复制的仅仅是引用
        return oldValue;//返回原先位置上的元素
    }
    
    • get()方法同样很简单,唯一要注意的是由于底层数组是Object[],得到元素后需要进行类型转换。
    public E get(int index) {
        rangeCheck(index);
        return (E) elementData[index];//注意类型转换
    }
    
    • remove()方法也有两个版本,一个是remove(int index)删除指定位置的元素,另一个是remove(Object o)删除第一个满足o.equals(elementData[index])的元素。删除操作是add()操作的逆过程,需要将删除点之后的元素向前移动一个位置。需要注意的是为了让GC起作用,必须显式的为最后一个位置赋null值
    public E remove(int index) {
        rangeCheck(index);
        modCount++;
        E oldValue = elementData(index);
        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index, numMoved);
        elementData[--size] = null; //清除该位置的引用,让GC起作用
        return oldValue;
    }
    
    

    6 Fail-Fast机制

    ArrayList也采用了快速失败的机制,通过记录 modCount 参数来实现。在面对并发的修改时,迭代器很快就会完全失败,而不是冒着在将来某个不确定时间发生任意不确定行为的风险。

  • 相关阅读:
    【深度学习 AIGC 绘画】Robert001/UniControl-Demo docker
    YOLO目标检测——卫星遥感舰船检测数据集下载分享【含对应voc、coco和yolo三种格式标签】
    LLM大模型4位量化实战【GPTQ】
    深入理解MySQL索引的数据结构和事务的四大特性、隔离性的四种级别
    开发技术-前后端(vue+java)加密传输数据
    C语言日志库zlog基本使用
    k8s环境生成自签名证书配置secret tls并配置到浏览器
    《确保安全:PostgreSQL安全配置与最佳实践》
    web网页设计期末课程大作业:家乡旅游主题网站设计——河北8页HTML+CSS+JavaScript
    acwing.台阶-nim游戏
  • 原文地址:https://www.cnblogs.com/knowledgeispower/p/16915407.html