【ArrayList】白话文源码分析

打好基础,再说其他的,不要整天好高骛远。

希望我的分享能帮到一样迷茫但努力的同学。

ArrayList类解析

首先我想对一些基础不好同学说一句。ArrayList是一个,不是你们认为的就直接操作内存了。
既然ArrayList是一个类,那么类就有类的规则,有规则我们就能学习。我们来看看这个类的结构:

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

1.全局变/常量
    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; 

    private int size;
===================================================================================
2. 构造方法
    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);
        }
    }

    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

    public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }
===================================================================================
3. 其他方法
。。。。。。。

}

整个ArrayList类我们就分为三个模块来解释。

  1. 全局变/常量
  2. 构造方法
  3. 其他方法(继承自父类和接口的具体的集合操作方法,如.add()等)

全局变/常量

ArrayList操作和这些变/常量是息息相关的,我们必须要非常清楚得知道每一个变/常量的含义是什么。

定义默认值含义
serialVersionUID8683452581122892189L可以看到这是一个静态常量,这个常量主要是用于对象序列化时的唯一标识,这里我们不去了解它。
DEFAULT_CAPACITY10可以看到这是一个静态常量,默认为10,此字段用作表示ArrayList集合的初始化长度
EMPTY_ELEMENTDATA{}可以看到这是一个静态常量,用于表示空数组
DEFAULTCAPACITY_EMPTY_ELEMENTDATA{}可以看到这是一个静态常量,用于表示空数组,但和EMPTY_ELEMENTDATA含义不同,DEFAULTCAPACITY_EMPTY_ELEMENTDATA用在初始化集合的时候。
elementData{}这是一个变量,用于表示当前的集合对象增删改查都是操作的它。
size0这是一个变量,用于表示当前集合的长度。

构造方法

要了解上面的变/常量的含义,最好就是通过读代码一行一行得慢慢熟悉。
构造方法就是我们了解一个类的第一步。

我们发现ArrayList有三个构造方法,我们从易到难依次分析一下。

无参构造:

当我们平时写List myList = new ArrayList()的时候就会调用下面的这个无参构造方法创建一个ArrayList对象。
我们看看构造方法做了什么事情:

    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
===================================================================================    
    1.this.elementData。this指的是当前的ArrayList对象,elementData指的是当前对象的数组对象。
    2.DEFAULTCAPACITY_EMPTY_ELEMENTDATA。指初始化时的空数组常量。
    3.整个构造方法就是把一个空数组赋值给我们new的ArrayList对象。

因此,调用无参构造创建集合的时候,集合为空,长度为size=0;(int值默认为0)

有参构造(参数为int)

当我们平时写List myList = new ArrayList(5)的时候就会调用下面的这个有参构造方法创建一个ArrayList对象。
我们看看构造方法做了什么事情:

   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);
        }
    }
===================================================================================    
1.我们从参数名就知道,这个参数是用来指定集合创建时的集合初始长度的。
2.如果参数>0,初始化并赋值给集合对象一个该参数长度的数组。
3.如果参数=0,赋值给集合对象一个空数组。
4.else,说明参数无效或非法,报错。

因此,我们可以利用这个构造方法在创建集合的时候就指定集合的长度

有参构造(参数为Collection集合)

Collection是List的父类,List是ArrayList的父类。
所以,当我们平时写List myList = new ArrayList(Collection子类集合)的时候就会调用下面的这个有参构造方法创建一个ArrayList对象。
比如我们现在要把一个Set集合赋值给ArrayList集合,就要调用这个构造方法。
我们看看构造方法做了什么事情:

    public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            this.elementData = EMPTY_ELEMENTDATA;
===================================================================================
1.把Collection集合参数.toArray()转成数组,并赋值给当前集合对象中的elementData变量。
2.把elementData的长度赋值给变量size。
3.如果size!=0,并且如果elementData的类模板对象不是Object[]的类模板对象
4.就强转为Object对象并赋值给elementData (这里主要是防止数组类型不同做的预防)
5.如果size为0,赋值给集合对象一个空数组。

看文章的时候记得反复回去看看全局变/常量那个表格。

其他方法

这里呢,就分析几个最常用的方法,主要是理解,不要死记硬背。

add()

当我们要给集合添加元素的时候会调用这个.add()方法。这也是要了解ArrayList的最重要的一个方法。
我们开始:

    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  
        elementData[size++] = e;
        return true;
    }
===================================================================================    
1.e就是我们要添加的元素
2.方法进来第一行就去调用了一个ensureCapacityInternal方法,传入了当前size+1最为参数,
	作为期望最小容量去判断是否需要扩容。
3. ensureCapacityInternal方法调用完成(成功)之后,把元素赋值到数组的最后一位。
4. 返回true,添加完成。

很明显,重点就是在ensureCapacityInternal(方法里面了),我们点过去看看:

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

    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
===================================================================================
1.第一行就是ensureCapacityInternal方法,我们看到这个方法先后调用了calculateCapacity和
	ensureExplicitCapacity方法。
2.我们先看calculateCapacity方法,两个参数
	elementData:当前数组对象,长度为10。
	minCapacity:传过来的size+1,后面统称为最小容量。
3.判断当前数组对象是否为空,如果为空就在默认容量(10)和最小容量选一个最大的返回。
4.如果数组不为空,直接返回最小容量。
5.我们拿到这个int值能干嘛呢。
6.带着这个值来到ensureExplicitCapacity方法,
	第一步modCount++;我们不管它,主要是记录集合的操作数。
	如果最小需要空间比elementData的内存空间要大,则需要扩容,来到grow(minCapacity)方法。

因此,我们知道了,集合在添加第一个元素的时候才会初始化一个长度为10的集合。

接下来就是grow()方法了,顾名思义,grow,我们要开始扩容了。

扩容机制:
    private void grow(int minCapacity) {
        int oldCapacity = elementData.length;
        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);
    }
===================================================================================
1. 参数minCapacity是之前我们计算出来当前集合可以接受的最小长度。
2. 先得到原数组的长度   oldCapacity 
3. 再根据算法算出新长度  newCapacity (老长度的1.5倍)
4.  再判断一下新数组的容量够不够,够了就直接使用这个长度创建新数组,
    不够就将数组长度设置为需要的长度。
5. 检查长度是否超出最大值。
6. 调用Arrays.copyOf方法将elementData数组指向新的内存空间时newCapacity的连续空间
 	并将elementData的数据复制到新的内存空间    

remove()

面试常问,为什么ArrayList对集合中元素的增删效率很低。我们来看看为什么:
下图是更具下标删除元素。假如我们现在有3个元素,我要删除下标是1的元素,底层会怎么做呢?

    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;  // clear to let GC do its work
        return oldValue;
    }
===================================================================================
1. 第一步,检查传入的下标是否合法。
2. modCount++;操作数+13. 得到要删除的元素的值。
4. numMoved,如果numMoved =0,说明被删除的元素是第一个或最后一个,不需要整体拷贝。
	numMoved为需要移动的元素个数。
5. 通过判断,System.arraycopy方法是native修饰的,直接操作内存,整个数组下标移动,很慢。
6. 因为删除了一个元素,size需要-1。并把数组最后一个元素置为null,等待GC删除。
7. 返回被删除的值

其他的方法可以自己一步一步去看。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

易柏州Innovation

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

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

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

打赏作者

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

抵扣说明:

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

余额充值