ArrayList底层数据结构+源码分析

数组与ArrayList

首先我们要知道ArrayList的底层是一个数组,也可以说本质上ArrayList就是一个普通的类,对数组进行的封装,扩展其功能。

对于数组,我们知道数组一旦确定就不能再被改变,而ArrayList可以实现自动扩容,ArrayList所谓的自动扩容其实也是新创建一个数组而已,所以我们首先了解一下数组拷贝,再来看看ArrayList是怎么扩容的。

数组拷贝
1. Arrays.copyof

我们先看看代码,看如何使用它

public static void main(String[] args) {
    int[] arr = new int[]{1, 2};
    for (int a : arr) {
        System.out.println(a);
    }
    System.out.println("-------------copyOf------------");
    int[] arr1 = Arrays.copyOf(arr, 10);
    for (int b : arr1) {
        System.out.println(b);
    }
}

运行结果

1
2
-------------copyOf------------
1
2
0
0
0
0
0
0
0
0

应该不难理解,将原数组arr扩大成长度为10的数组,int[] arr1 = Arrays.copyOf(arr, 10);

2. System.arraycopy()
public static void main(String[] args) {
    int[] src = new int[]{1, 2, 3, 4, 5};
    int[] dest = new int[10];
    System.arraycopy(src, 1, dest, 0, 3);
    for (int num :
            dest) {
        System.out.println(num);
    }
}

运行结果:

2
3
4
0
0
0
0
0
0
0

我们去看看System.arraycopy()是啥

public static native void arraycopy(Object src,  int  srcPos,
                                    Object dest, int destPos,
                                    int length);
src:要拷贝的数组
srcPos:要拷贝的数组的起始位置
dest:目标数组
destPos:目标数组的起始位置
length:你要拷贝多少个数据

以上两个方法都是进行数组拷贝的,这个对理解数组扩容技术很重要,而且在ArrayList中也有应用,我们等会会详细说。

现在主角ArrayList正式登场!

我们先来看看它的无参构造函数

ArrayList arrayList = new ArrayList();

我们点进去康康:

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

这里出现了两个不认识的东西:

1. elementData
2. DEFAULTCAPACITY_EMPTY_ELEMENTDATA

我们分别来看看

elementData

transient Object[] elementData; // non-private to simplify nested class access

原来就是一个Object数组的声明呀~ 前面有个transient,它修饰序列化的时候会被忽略掉,这里不展开讲

DEFAULTCAPACITY_EMPTY_ELEMENTDATA

private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

这不也是个Object数组嘛?这是一个空数组,所以

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

也就是将一个空的Object数组赋给elementData。

我们之前也提到了,ArrayList其实底层就是数组,所以这里new AarrayList()后就给我们整了一个空数组出来。

但是我们要往里面塞数据呀,所以空数组肯定不行,那我们再去看看它的add()方法看看如何进行数据的添加

ArrayList的add()

public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}

怎么又有我们不认识的东西?!

1. ensureCapacityInternal()
2. size

ensureCapacityInternal()

翻译一下这个方法名:确保内部容量?????

啥玩意儿,我们不知道,先看看size是什么

size

 private int size;

原来就是一个变量

再来看看这一句

elementData[size++] = e;

elementData我们上面有提到过,就是new一个ArrayList时候创建的一个空数组,那这一句的意思就是在这个数组中某一个元素进行赋值呗,但是空数组怎么赋值?所以我们知道上面那一句【ensureCapacityInternal(size + 1);】是干嘛的了吧~

这一步应该就是把数组的容量给确定下来的,赶紧进去看看

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

OMG,怎么又多了一堆不认识的东西?!没关系,看源码就得耐心,一步一步慢慢得剖开它

1. calculateCapacity()
2. minCapacity
3. ensureExplicitCapacity()

我们先来看看calculateCapacity()

private static int calculateCapacity(Object[] elementData, int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    return minCapacity;
}

我们来看这个if语句

if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }

这个语句其实就是先判断下现在这个ArrayList的底层数组elementData 是不是刚创建的的空数组,现在这个情况当然是啦对吧。

我们进了这个if语句后,看这一句

Math.max(DEFAULT_CAPACITY, minCapacity);

首先我们要清楚minCapacity是啥?在这个方法【calculateCapacity】里面是一个形参吧?然后返回看

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

那它也是【ensureCapacityInternal】的形参呀,所以我们要看调用【ensureCapacityInternal】传进来的参数是什么,往上一找发现是:

ensureCapacityInternal(size + 1);

所以这个minCapacity就是size + 1啦。那现在这个情况也就是minCapacity=1,也就是说将要向底层数组添加第一个元素

所以这个语句

return Math.max(DEFAULT_CAPACITY, minCapacity);

我们就要来看看DEFAULT_CAPACITY是多大啦

private static final int DEFAULT_CAPACITY = 10;

这就是ArrayList的底层数组elementData初始化容量啊,是10。

我们再看看这个if语句

if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }

不难发现,这个语句必定只执行一次,为什么呢?这个elementData为空的时候,不就是刚创建ArrayList的时候吗?只有第一次添加数据才会执行这步,这一步就是为了指定默认数组长度(10)的,指定一次就ok了

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

这里我们已经分析好了calculateCapacity()方法咯,现在返回的就是默认数组长度10,所以现在来探究一下ensureExplicitCapacity

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

    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

这里形参minCapacity = 10

此时elementData还是一个空数组呀,所以肯定会进去执行grow

grow方法

  private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

我们来康康第一句

 int oldCapacity = elementData.length;

这句得到了原来数组的长度

int newCapacity = oldCapacity + (oldCapacity >> 1);

得到新容量的,后面的这oldCapacity >> 1就相当于oldCapacity /2,是移位运算,也就是扩容为原来的1.5倍

if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;

目前数组长度为0,所以这个新的容量newCapacity也是0,而minCapacity 则是10,所以会执行方法体内的赋值操作,也就是现在的新容量成了10。

elementData = Arrays.copyOf(elementData, newCapacity);

我们继续往下瞅瞅发现了Arrays.copyOf,上面特意讲过的数组拷贝呀,那这个elementData就是原来的数组,newCapacity就是新数组的容量。

这里饶了一大圈,就是为了创建一个默认长度为10的底层数组,苍天啊。。。

简单来说grow方法就是拿来扩容滴

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

这不就是直接创建嘛?!

我们再来康康这个

else if (initialCapacity == 0) {
        this.elementData = EMPTY_ELEMENTDATA;
    } 

EMPTY_ELEMENTDATA

private static final Object[] EMPTY_ELEMENTDATA = {};

那么EMPTY_ELEMENTDATADEFAULTCAPACITY_EMPTY_ELEMENTDATA有什么区别呢?

这两个都是空的Object数组

但是DEFAULTCAPACITY_EMPTY_ELEMENTDATA

是在无参构造的时候elementData被赋值为DEFAULTCAPACITY_EMPTY_ELEMENTDATA。而有参构造的时候,elementData被赋值为EMPTY_ELEMENTDATA。虽然可能会认为两个都是空数组,没什么区别,但是我们前面分析了add方法,就应该知道这两个的区别是什么。

private static int calculateCapacity(Object[] elementData, int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    return minCapacity;
}

当我们添加第一个元素的时候,minCapacity肯定都是等于1的,但是如果我们用的是无参构造,return的就会是DEFAULT_CAPACITY,也就是默认数组容量10,而我们用有参构造的话,return的就是minCapacity,是1

所以我们扩容的时候也会有区别,无参构造扩容是基于默认初始化大小10进行扩容,依次是10,15,22,33这种1.5倍扩容

而如果用

ArrayList arrayList = new ArrayList(0);

就是基于我们自己设置的大小0开始扩容,一次是0,1,2,3,4,6这种1.5倍扩容

参考:
https://blog.csdn.net/sinat_33921105/article/details/103773539

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值