Java集合类:ArrayList集合

       从源码层面重新认识ArrayList结构


前言

阅读源码可以提升我们编码的能力,同时可以让我们更好的理解原理和架构设计。OK,废话不说, 让我们一起来看下ArrayList底层属性和构造方法以及扩容策略吧!

一、ArrayList 属性+构造

package java.util;

import ...

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable{
    
    
    //默认初始容量
    private static final int DEFAULT_CAPACITY = 10;
    
    
    //用于空实例的共享空数组实例。  
    private static final Object[] EMPTY_ELEMENTDATA = {};
    
    
    //共享空数组实例,用于默认大小的空实例。 我们将其与EMPTY_ELEMENTDATA区分开来,以便知道在添加第一个元素时膨胀多少。 
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    
    
    //非私有以简化嵌套类访问
    transient Object[] elementData;
    
    
    //ArrayList集合的大小
    private int size;
    
    
    //构造具有指定初始容量的空列表。  
    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);
        }
    }
    
     
     //构造一个初始容量为10的空列表。  
     public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
    
}

二、底层扩容操作

        涉及多个方法调用,建议顺序观看

1.add方法

代码如下(示例):

    // 将指定的元素追加到此列表的末尾。 
    public boolean add(E e) {
        // 方法调用
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        //下标后移一位
        elementData[size++] = e;
      
        return true;
    }

add方法是我们常用的添加成员方法,内部先是调用ensureCapacityInternal()方法,传入扩容长度,并将参数值存入数组下标后移一位的成员中。

2.ensureCapacityInternal方法

代码如下(示例):

    private void ensureCapacityInternal(int minCapacity) {
        // 1                        // 2                                
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }

该方法有两个方法调用分别是calculateCapacity()传入数组和扩容长度,将返回值传入ensureExplicitCapacity方法中

3.calculateCapacity方法

作用:判断数组是否为空,不为空则可以正常扩容

代码如下(示例):

   private static int calculateCapacity(Object[] elementData, int minCapacity) {
      //判断传入的数组是否是空数组, DEFAULTCAPACITY_EMPTY_ELEMENTDATA是提前定义好的空数组实例
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            
            //Math.max方法作用:返回两个参数中的最大值
            return Math.max(DEFAULT_CAPACITY, minCapacity);
            
        }
       
        //如果判断这不是一个空数组,表示可以扩容,则正常返回minCapacity
        return minCapacity;
    }

4.ensureExplicitCapacity方法

作用:modCount++;判断传入的容量长度是否大于数组的长度,大于则调用grow方法进行扩容,否则不做处理

代码如下(示例):

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

        //如果传入的容量值当前数组的长度,则调用grow方法进行扩容,否则不做处理
        if (minCapacity - elementData.length > 0)
            //调用grow,增量容量
            grow(minCapacity);
    }

5.grow方法

作用:具体的扩容策略,正常扩容的话,新数组长度等于原数组长度的1.5倍,判断扩容长度和根据扩容机制扩容后的新数组长度的关系,谁大用谁,对扩容长度超过最大限定长度做特殊判断,调用hugeCapacity方法。

代码如下(示例):

     //增加容量,以确保它至少可以容纳最小容量参数指定的元素数量。  
     private void grow(int minCapacity) {
        
        /*  以下是具体的扩容机制  */
        // overflow-conscious code
         
        // 获取原数组的长度
        int oldCapacity = elementData.length;
         
        // 新数组长度等于原数组长度的1.5倍   oldCapacity右移一位(相当于除2)
        int newCapacity = oldCapacity + (oldCapacity >> 1);

        // 判断如果新数组长度小于扩容后的长度  将传入扩容长度作为新数组长度 
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;

        // 如果扩容长度超过限定的最大扩容长度
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);

        // 执行Arrays工具类的copyOf方法  
        // 把旧数组的值赋值给新数组,新数组赋值给原引用,旧数组等待GC回收
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

6.hugeCapacity方法

作用:对扩容长度超过限定的最大扩容长度做处理

代码如下(示例):

   //限定的最大数组长度。
   private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;    

   private static int calculateCapacity(Object[] elementData, int minCapacity) {
      //判断传入的数组是否是空数组, DEFAULTCAPACITY_EMPTY_ELEMENTDATA是提前定义好的空数组实例
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            
            //Math.max方法作用:返回两个参数中的最大值
            return Math.max(DEFAULT_CAPACITY, minCapacity);
            
        }
       
        //如果判断这不是一个空数组,表示可以扩容,则正常返回minCapacity
        return minCapacity;
    }

6.copyof方法

作用:完成具体的动态申请内存空间,将旧数组内容复制到新数组中

代码如下(示例):

    public static <T> T[] copyOf(T[] original, int newLength) {
        return (T[]) copyOf(original, newLength, original.getClass());
    }

    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;
    }    


    //从指定的位置开始,将数组从指定的源数组复制到目标数组的指定位置。 
    //将数组组件的子序列从src引用的源数组复制到dest引用的目标数组。
    //复制的组件数量等于参数length。 
    //将源阵列中位置为srcPos到srcPos+length-1的组件分别复制到目标阵列位置为destPos到destPos+length-1的组件。 
    public static native void arraycopy(Object src,  int  srcPos,
                                        Object dest, int destPos,
                                        int length);

到此为止就算是到java的底层了,再接下来就是C++来实现具体的copy功能了


总结

文章中可能会有部分纰漏,欢迎留言指出。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

To Do.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值