天天看点

# 深入理解ArrayList集合的初始容量和初始容量为0的两种扩容机制(1.8版本)深入理解ArrayList集合的初始容量和初始容量为0的两种扩容机制(1.8版本)

深入理解ArrayList集合的初始容量和初始容量为0的两种扩容机制(1.8版本)

网络上对于ArrayList集合的空参构造是否为0,存在不同的的看法。对此,分析了源码,有以下见解:
1.空参构造,集合初始容量必定为0,添加一个元素,扩容为10。
2.有参构造,参数为0和集合长度为0时,初始容量为0,添加一个元素扩容为1,再添加一个元素扩容为2,再添加一个元素扩容
为3,再添加一个元素扩容为4,再添加一个元素扩容为6...
           
  • 版本不同
  • ArrayList的三种构造方法
  • ArrayList()的初始容量和扩容
  • ArrayList(int initialCapacity)的初始容量扩容
  • ArrayList(Collection( extends E) c)(尖括号显示不出来,用()代替)
  • ArrayList集合的扩容公式

版本不同

JAVA对于ArrayList集合进行了优化,目前所知,优化了空参构造的初始容量和扩容公式,至于是1.7还是1.8版本开始,
就不清楚了。在1.6版本之时,该集合的空参构造的初始容量,确实是10,源码自己找。
该文的源码以JDK1.8为主。
           

ArrayList的三种构造方法

public ArrayList(int initialCapacity)
public ArrayList()
public ArrayList(Collection<? extends E> c)
           

public ArrayList()的初始容量和扩容

首先,分析空参构造的初始容量。
在1.8里面定义了三个Object[]:
           
1.用于空实例的共享空数组实例。
private static final Object[] EMPTY_ELEMENTDATA = {}
           
2.用于默认大小的空实例的共享空数组实例。
   该数组与EMPTY_ELEMENTDATA区别,但都为空,以便知道当添加第一个元素时,该数组应该于何时扩容多少。
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}
           

而这两个数组,有什么关系呢?

第一个数组运用于有参构造方法的参数为0或者集合长度为0的时候,而第二个数组运用于空参构造的时候。

这两个数组一定要区别,对于以后的扩容造成影响。

ArrayList的底层是由(transient Object[] elementData)实现的; 该数组是存储ArrayList元素的数组缓冲区。ArrayList的容量是这个数组缓冲区的长度。
    任何的elementData=DEFAULTCAPACITY_EMPTY_ELEMENTDATA的空集合,当添加第一个元素时,将被扩展到DEFAULT_CAPACITY。
           
  • 初始容量
要知道空参构造的初始容量,要对空参构造的源码进行分析:
	public ArrayList() {
        this.elementData =
        DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
    这样看空参构造,初始容量就是0。
           
  • 扩容机制
那么当添加一个元素的时候,集合的容量会扩容到默认容量为10,也可能为大于10的值,视情况而定。一般默认一次只添加一个元素。
private int newCapacity(int minCapacity) {
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity <= 0) {
            if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
                return Math.max(DEFAULT_CAPACITY, minCapacity);
                //这里就定义了当
                //DEFAULTCAPACITY_EMPTY_ELEMENTDATA数组添加元素的情况,若是第一次添加:1.只添加一个元素,
                //那么容量就扩容为DEFAULT_CAPACITY=10;2.要是添加的元素大于该默认容量,则以该元素个数为
                //扩容后的容量。
            if (minCapacity < 0) // overflow
                throw new OutOfMemoryError();
            return minCapacity;
        }
        return (newCapacity - MAX_ARRAY_SIZE <= 0)
            ? newCapacity
            : hugeCapacity(minCapacity);
    }
           

public ArrayList(int initialCapacity)的初始容量和扩容

  • 初始容量

这里就讨论当参数为0的时候,该集合的初始容量也为0。

public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
            //当该参数为0,底层数组为EMPTY_ELEMENTDATA,那么该数组的长度为0,集合初始容量则为0。
        } else {
            throw new IllegalArgumentException("Illegal    Capacity: "+initialCapacity);
        }
    }
           
  • 扩容机制

    加进一个元素,扩容为1。

    源码分析:

//add源码  
public boolean add(E e) {
        modCount++;
        add(e, elementData, size);//调用add(E e, Object[] elementData, int s)方法
        return true;
    } 
//添加一个元素,调用此方法  
private void add(E e, Object[] elementData, int s) {
        if (s == elementData.length)
            elementData = grow();// 数组长度为0,继续调用grow()方法
        elementData[s] = e;
        size = s + 1;
    }
private Object[] grow() {
        return grow(size + 1);//grow()调用grow(1)
    }
private Object[] grow(int minCapacity) {
        return elementData = Arrays.copyOf(elementData,
                                           newCapacity(minCapacity));
                                           //扩容方法newCapacity(minCapacity)
    }
    //扩容
private int newCapacity(int minCapacity) {
        int oldCapacity = elementData.length;//oldCapacity=0
        int newCapacity = oldCapacity + (oldCapacity >> 1);//newCapacity=0
        if (newCapacity - minCapacity <= 0) {
            if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
                return Math.max(DEFAULT_CAPACITY, minCapacity);
                //DEFAULTCAPACITY_EMPTY_ELEMENTDATA空参构造的数组;
                //抱歉这是为了空参构造设计的,参数为0和集合长度为0对应的数组是EMPTY_ELEMENTDATA。
            if (minCapacity < 0) 
                throw new OutOfMemoryError();
            return minCapacity;//上述条件都不符合,那么就返回1.
        }
        return (newCapacity - MAX_ARRAY_SIZE <= 0)
            ? newCapacity//当newCapacity - minCapacity > 0,且小于最大的数组长度,就返回原数组的1.5倍
            : hugeCapacity(minCapacity);
    }
           

所以当添加一个元素进去时,该集合扩容为1。

public ArrayList(Collection(? extends E) c)

该构造方法与 ArrayList(int initialCapacity)方法有一样的地方:当参数集合长度为0时,该集合的初始容量也为0。

elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            // defend against c.toArray (incorrectly) not returning Object[] 
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // replace with empty array.
            this.elementData = EMPTY_ELEMENTDATA;
     //当该集合为空时,同 ArrayList(int initialCapacity)方法的参数为0情况,集合的初始容量为0。
           

扩容情况参照ArrayList(0)的情况,同样。

ArrayList集合的扩容公式

1.6版本的扩容公式:

数组原长度*3/2+1

.

1.6之后的扩容公式:

oldCapacity + (oldCapacity >> 1)

因为位运算快。

之所以写此博文,是因为,看见一个拿着1.8版本的源码,睁眼说瞎话,说空参构造的初始容量为10,这简直就是害人。不平之下
, 按照自己对于源码的见解,进行分析,望能帮助到需要之人。
若有不足之处,望批评指正。