Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

2019-05-28:谈一谈ArrayList的扩容? #64

Open
MoJieBlog opened this issue May 28, 2019 · 5 comments
Open

2019-05-28:谈一谈ArrayList的扩容? #64

MoJieBlog opened this issue May 28, 2019 · 5 comments
Labels

Comments

@MoJieBlog
Copy link
Collaborator

No description provided.

@MoJieBlog
Copy link
Collaborator Author

MoJieBlog commented May 28, 2019

学习目标:

  • 初始化时元素个数是多少
  • 如何扩容

先来看看构造方法

无参数构造方法

    /**
     * Constructs an empty list with an initial capacity of ten.
     */
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

这是无参数的构造方法,是对elementData(元素的数组)进行赋值DEFAULTCAPACITY_EMPTY_ELEMENTDATA(空数组,我们暂时叫做默认空数组),明明是空数组,但是注释确实创建容量是10。好吧继续往下看。

再来看带参数的构造方法

    public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            //如果initialCapacity>0,创建initialCapacity个元素的数组
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            //等于0时,创建空数组
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            //否则直接抛出异常
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }

再看另一个构造方法

    public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // replace with empty array.
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

就是直接将集合转为数组赋值给elementData,同时对size赋值,并且如果size不等于0时,c.toArray might (incorrectly) not return Object[] (see 6260652)。我也是一脸懵逼。等于0时,直接赋值空的数组。

我们接着看add方法,先看一个参数的

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

就是ensureCapacityInternal(size + 1) 并且将e添加到size++的位置,我们来看ensureCapacityInternal(size + 1)方法。

    private void ensureCapacityInternal(int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
    }

看这里

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

还记得空参数的构造方法吗?DEFAULT_CAPACITY = 10。也就是当空参数时创建ArrayList时,minCapacity与DEFAULT_CAPACITY的最大值,显然创建无参数构造方法时minCapacity = 0.这时结果 = 10,然后看ensureExplicitCapacity(minCapacity)

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

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

显然调用无参数构造方法时minCapacity - elementData.length > 0是成立的,我们再看grow(minCapacity)

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

oldCapacity是元素个数newCapacity是oldCapacity+oldCapacity/2,即oldCapacity的1.5倍。当1.5倍的元素个数小于minCapacity时newCapacity = minCapacity。显然创建无参数ArrayList就是这种情况,这就解决了开头的第一个问题。调用无参数构造方法创建的是10个元素的长度。

我们再看当1.5倍元素个数大于 MAX_ARRAY_SIZE时newCapacity = hugeCapacity(minCapacity);

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

到这里我们可以总结一下了:
无参构造方法调用add之后创建的是10个长度的数组。有参数则直接创建指定长度的数组。扩容时,小于MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8一次扩容1.5倍,超过则直接Integer.MAX_VALUE。

附加一个问题

有没有哪位大神解释下为什么是int最大值减8?网上有人说是因为数组对象用这8位来存储数组长度,锁对象,描述对象状态的标志集合。不是很理解

@yangfanggang
Copy link

我这里简单说一下
首先ArrayList底层实现是数组,且数组有默认的容量,使用ArrayList存储元素时,必须先构造ArrayList对象,对象的长度即应该对应数组的容量

那么为什么需要扩容呢?对象的长度比存储他的数组容量大
怎么扩容?每次增加原数组容量的一半,即1.5的倍数扩大

这时候需要分两种情况
1.初始操作即需要扩容
2.进行新增操作即需要扩容
但要注意:扩容是有范围的,即数组不容量是有一定范围的
详细源码分析

@LvKang-insist
Copy link

初始容量为10 ,当添加第11个元素的时候,会扩容1.5倍,当添加到16个元素的时候扩容为15*1.5=22,以此类推。

@Petterpx
Copy link

Petterpx commented May 28, 2019

ArrayList的扩容,又称自动扩容机制,就像一个动态数组一样。内部是用数组实现的,而且数组默认长度为10。当它插入元素的时候,如果当前插入元素后长度大于默认长度,这时候就会扩容。扩容是指每次扩容数组长度为1.5倍,如果扩容后还不够,那么会直接扩容到需求长度。然后使用System.arraycopy()方法将原来数组里的内容复制到新数组。所以我们在使用的时候,最好能给出预估值的大小。
核心源码如下

private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
// 扩展为原来的1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
// 如果扩为1.5倍还不满足需求,直接扩为需求值
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);
}

@Moosphan Moosphan added the Java label Aug 2, 2019
@houqingfeng
Copy link

houqingfeng commented Sep 1, 2022

    /**
     * Increases the capacity to ensure that it can hold at least the
     * number of elements specified by the minimum capacity argument.
     *
     * @param minCapacity the desired minimum capacity
     */
    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);
    }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

6 participants