集合框架 -------- ArrayList源码分析

2018-07-29  本文已影响4人  快乐的小码农呀

一、简介

这篇文章主要介绍一下ArrayList的相关细节,它的底层是基于数组实现的,数组是在内存中划分出一块连续的地址空间用来进行元素的存储,它存在一个致命的缺陷,就是在初始化时必须要指定大小,并且不能改变,而ArrayList则解决了这个问题,实现了一个动态的数组(自动扩容机制),与此同时,它也具备了数组的一些特性:查询快,增删慢。下面我们来看一下类图:

ArrayList继承结构

二、底层实现

先来看一下它的几个字段,可以发现它的内部维护了一个Object数组用于存储元素,也就是说它可以存储任何类型的元素(最好使用泛型约束存储的类型),当创建一个新的实例时,可以指定初始大小,若不指定则不会分配内存空间而是使用空的对象数组,在添加第一个元素时再进行内存分配。

//默认初始容量
private static final int DEFAULT_CAPACITY = 10;
//空对象数组
private static final Object[] EMPTY_ELEMENTDATA = {};
//默认容量空对象数组(即添加第一个元素时会自动扩容至默认容量)
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//存储ArrayList的元素,它的大小即size大小
transient Object[] elementData;
//此集合的大小(即集合中元素的数量)
private int size;
//集合最大长度,若依然不够会进一步扩容到Integer.MAX_VALUE
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

接下来我们再来看一下它的增删改查方法,emmm。。。基于数组的操作,也没什么可写的。

//在末尾增加一个元素
public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}
//在指定位置增加一个元素
public void add(int index, E element) {
    rangeCheckForAdd(index);
    ensureCapacityInternal(size + 1);
    System.arraycopy(elementData, index, elementData, index + 1, size - index);
    elementData[index] = element;
    size++;
}
//移除此列表中首次出现的指定元素(如果存在)。
public boolean remove(Object o) {
    if (o == null) {
        for (int index = 0; index < size; index++)
            if (elementData[index] == null) {
                fastRemove(index);
                return true;
            }
    } else {
        for (int index = 0; index < size; index++)
            if (o.equals(elementData[index])) {
                fastRemove(index);
                return true;
            }
    }
    return false;
}
//用指定的元素替代此列表中指定位置上的元素。
public E set(int index, E element) {
    //下标合法性检查 
    rangeCheck(index);
    E oldValue = elementData(index);
    elementData[index] = element;
    return oldValue;
}
//获得指定位置元素
public E get(int index) {
    //下标合法性检查
    rangeCheck(index);
    return elementData(index);
}
扩容机制及本人的思考
    private void grow(int minCapacity) {
        int oldCapacity = elementData.length;
        //扩容为原长度的1.5倍
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        //扩容后的长度若大于MAX_ARRAY_SIZE,进一步扩容到Integer.MAX_VALUE
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

从源码中可以看到,ArrayList的最大长度为Integer.MAX_VALUE - 8,而数组对象相比标准java对象多了一个用于存储大小的元数据,即 2^31 -8 (for storing size ),若试图分配更大空间可能会导致OutOfMemoryError,下面贴出源码注释:

    /**
     * The maximum size of array to allocate.
     * Some VMs reserve some header words in an array.
     * Attempts to allocate larger arrays may result in
     * OutOfMemoryError: Requested array size exceeds VM limit
     */
     private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

虽然注释上是这么说的,但是仔细看源码还有这么一行:

    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

当MAX_ARRAY_SIZE长度的集合也不足够的时候,则会扩容至Integer.MAX_VALUE,那么问题来了,这样操作占用了本用来存储header words的空间,按注释所言可能会报OutOfMemoryError,那这里又是如何处理的呢???

三、总结

又到了文章的最后,我们来总结一下ArrayList的特点:

上一篇下一篇

猜你喜欢

热点阅读