ArrayList源码个人笔记

本文主要解析了ArrayList的源码。它底层由数组实现,查询快增删慢,可动态增长。介绍了其基本参数、构造方法等,重点讲解add和grow方法,强调扩容影响性能。总结指出其核心是扩容机制,建议知晓数据长度时设置初始容量,还提及数组去重方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

简介
  • ArrayList底层是数组实现的,所以特性也是查询快增删慢。但和数组不同的是它可以实现动态增长,在知晓数据的大概容量时可使用ensureCapacity方法进行手动扩容。

  • ArrayList继承自AbstractList,实现了List,RandomAccess,Cloneable,Serializable接口

  • ArrayList继承AbstractList,实现LIst,实现了增加,删除,修改,遍历等功能。

  • RandomAccess,Cloneable,Serializable都是标志接口,接口内没有任何内容。其中RandomAccess标志着ArrayList实现了随机访问,Cloneable标志着ArrayList重写了Object类的clone方法实现拷贝,而Serializable则标志ArrayList支持序列化。

核心源码
基本参数
public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    private static final long serialVersionUID = 8683452581122892189L;

    /**
     * 默认初始容量
     */
    private static final int DEFAULT_CAPACITY = 10;

    /**
     * 空数组.
     */
    private static final Object[] EMPTY_ELEMENTDATA = {};

    /**
     * 
     */
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    /**
     * 真正用于保存数据的数组
     */
    transient Object[] elementData; 

    /**
     * ArrayList包含的元素数量
     */
    private int size;
构造方法
	// 无参构造
	public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
	
	// 有参构造,指定初始容量
	public ArrayList(int initialCapacity) {
		// 参数的合法性校验
        if (initialCapacity > 0) {
        	// 创建长度为initialCapacity的数组
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
        	// 数组为EMPTY_ELEMENTDATA(空数组)
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }
	// 传入参数(集合),构造为ArrayList
	public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        
        if ((size = elementData.length) != 0) {
        	// 数组的长度不为0
            // c.toArray可能返回的不是object类型数组
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // 如果传入的集合长度为0,替换为空数组.
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }
trimToSize

修剪此ArrayList实例的容量为最合适值,节约空间

	public void trimToSize() {
        modCount++;
        if (size < elementData.length) {
        	// 下边可能刚看有点迷,可以看我注掉的这个,会直观一些
	        // elementData = ((size == 0) ? EMPTY_ELEMENTDATA : Arrays.copyOf(elementData, size));
            elementData = (size == 0)
              ? EMPTY_ELEMENTDATA
              : Arrays.copyOf(elementData, size);
        }
    }
add方法和grow方法(重要)

扩容方法是ArrayList中最核心方法,它最影响性能,所以在知晓数据存储量的情况下,应设置ArrayList的初始容量来避免扩容

	public boolean add(E e) {
		// 内部容量担保
        ensureCapacityInternal(size + 1);
        // 将元素放入底层数组的size++位置,就是数组赋值
        elementData[size++] = e;
        return true;
    }
    // 内部容量担保
	private void ensureCapacityInternal(int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        	// 若底层数组和默认容量数组是一样的,则将最小容量指定为10
        	// 这个判断一般只在数组为指定容量第一次添加元素时进入
            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;
        // 新容量赋值为旧容量的1.5倍
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        // 判断新容量是否大于最小容量
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        // 判断新容量是否大于数组最大容量
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // 将旧数组的数据转移到新数组中
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
add的重载方法
	// index:插入的位置,element:插入的元素
	// 慎用,每一次写入都会造成数组的复制和迁移,如果大数据量的情况下非常影响性能
	public void add(int index, E element) {
		// 索引检查
        rangeCheckForAdd(index);
		
		// 内部容量担保
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        // 将elementData数组的index位置及其后的所有元素,插入到elementData数组的inde+1位置
        // 其实就是把index位置空出来,把原来index及其后的元素集合向后错一位
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;
        size++;
    }
	
	// 将集合填入到ArrayList中
	public boolean addAll(Collection<? extends E> c) {
        Object[] a = c.toArray();
        int numNew = a.length;
        // 内部容量担保
        ensureCapacityInternal(size + numNew);  // Increments modCount
        // 将集合数组插入到ArrayList的size开始的位置,插入数量为numNew
        // 从ArrayList的尾部插入
        System.arraycopy(a, 0, elementData, size, numNew);
        // size的改变
        size += numNew;
        return numNew != 0;
    }
	
	// 将集合从指定位置填入ArrayList中
	// 慎用
	public boolean addAll(int index, Collection<? extends E> c) {
        rangeCheckForAdd(index);

        Object[] a = c.toArray();
        int numNew = a.length;
        // 内部容量担保
        ensureCapacityInternal(size + numNew);  // Increments modCount
		
		// 取出偏移量
        int numMoved = size - index;
        if (numMoved > 0)
        	// 进行偏移
            System.arraycopy(elementData, index, elementData, index + numNew,
                             numMoved);
		// 将新传入的集合从指定位置开始插入
        System.arraycopy(a, 0, elementData, index, numNew);
        size += numNew;
        return numNew != 0;
    }
总结

ArrayList查询快,增删慢(数组扩容、移位导致),线程不安全
核心是扩容机制

tips:如果知晓需要存入的数据的长度,应在构造时设置好ArrayList的容量,减少扩容提高效率


记得看到过一个面试题,如何让一个数组快速实现元素去重?
把元素数组转为List,然后把List放入Set中就可以快速实现啦


下一篇:LinkedList源码个人笔记

参考:https://snailclimb.gitee.io/javaguide/#/java/collection/ArrayList?id=arraylist简介

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值