ArrayList解读

说明:本文(Android中的ArrayList, 跟java中的ArrayList相差无几)主要是针对不了解ArrayList原理的开发者,也欢迎大神指导指正。

相信很多java或者android开发者,ArrayList是使用的最频繁的一个集合,但是你们是否真的了解ArrayList的真谛?

问题

1.ArrayList的内部采用什么数据结构?

2.ArrayList最大容量是多少?

3.每次ArrayList扩容,默认自动扩容多大?

4.ArrayList会抛出什么异常?什么情况下会抛?

5.ArrayList如何安全的删除item?

6.ArrayList是线程安全吗?如果不是如何避免?

7.使用ArrayList有什么优化点?

大佬,如果你觉得这些是小儿科,走好不送~也欢迎各位大佬指导指正

好,我们言归正传,我们要全面了解ArrayList,带着问题去看源码,下面我们通过一一解答上面的问题来一一解读ArrayList的真谛:

问题1:ArrayList的内部采用什么数据结构

ArrayList内部采用的是数组数据结构,我们看它内部代码:


transient Object[]elementData;

问题2:ArrayList最大容量是多少?

最大容量是Integer.MAX_VALUE,但部分虚拟机上是Integer.MAX_VALUE -8。我们来看下最大容量变量定义:


/**

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

注释说的很明确,ArrayList在有些虚拟机上保留几个字节(8个字节),所以在这些虚拟机上最大容量是Integer.MAX_VALUE -8。

也许你会说,大哥你不是说最大容量是Integer.MAX_VALUE吗?但是代码定义是Integer.MAX_VALUE -8呀,哪来的Integer.MAX_VALUE呀?大佬,稍安勿躁,来我们看下ArrayList中这段代码


private static int hugeCapacity(int minCapacity) {

if (minCapacity <0)// overflow

        throw new OutOfMemoryError();

    return (minCapacity >MAX_ARRAY_SIZE) ?

Integer.MAX_VALUE :

MAX_ARRAY_SIZE;

}

看到了吧?小于0会报oom, 如果大于MAX_ARRAY_SIZE返回的是Integer.MAX_VALUE,但是在部分虚拟机上会报oom

好,我们总结下:ArrayList最大的容量是Integer.MAX_VALUE,在部分虚拟机上实质是Integer.MAX_VALUE -8,超过了Integer.MAX_VALUE -8就会报oom

问题3:每次ArrayList扩容,默认自动扩容多大

这个问题的正确答案有5个答案,看到部分网上面试题讲解时大多都只说了一种,其实不准确。

所谓扩容就是往ArrayList添加元素时,如果当前ArrayList的容量不足以容纳新增的元素时,此时就需要扩大它自身的容量添加新的元素。那么问题来了这个扩大自身容量,每次扩大多少才比较合适呢?新增的元素有多少个就扩大多少个?no,no....如果频繁的添加那么就会造成频繁的动态扩容,这样的开销是挺大的,那多少合适呢?我们来看下源码:


/**

* Appends the specified element to the end of this list.

*

* @param e element to be appended to this list

* @return true (as specified by {@link Collection#add})

*/

public boolean add(E e) {

    //这个方法主要是用于容量不足时,自动扩容

    ensureCapacityInternal(size +1);  // Increments modCount!!

    elementData[size++] = e;

    return true;

}

上面是ArrayList添加一个元素的源码,通过这个函数我们知道ArrayList的add方法添加元素默认是添加到最后一个的。add方法中调用了ensureCapacityInternal扩容方法,我们看下ensureCapacityInternal方法:


// minCapacity = 原容量大小+新增元素的个数

private void ensureCapacityInternal(int minCapacity) {

    //如果ArrayList是通过new ArrayList()产生的,那么最小扩容取DEFAULT_CAPACITY, minCapacity中的

    // 最大值

    if (elementData ==DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {

        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);

    }

    ensureExplicitCapacity(minCapacity);

}

ensureCapacityInternal方法内部有调用了ensureExplicitCapacity方法,我们看下ensureExplicitCapacity方法:


 // minCapacity = 原容量大小+新增元素的个数

private void ensureExplicitCapacity(int minCapacity) {

    // ArrayList修改次数加1

    modCount++;

    // overflow-conscious code

    // 如果当前ArrayList的当前容量比新的要扩展到的容量大,则不需要扩容

    // 如果当前ArrayList的当前容量比新的要扩展到的容量小,则通过grow方法进行扩容

    if (minCapacity -elementData.length >0)

        grow(minCapacity);

}

接下来我们看下grow方法:


/**

* 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;// 当前ArrayList的容量

    int newCapacity = oldCapacity + (oldCapacity >>1); // 当前ArrayList的容量增大到原来的1半

    if (newCapacity - minCapacity <0)// 如果ArrayList的容量增大到原来的1半还不足以容量要新增的元素容量,则扩容容量采用minCapacity

        newCapacity = minCapacity;

    if (newCapacity -MAX_ARRAY_SIZE >0)// 如果新的容量大于最大容量的定义,则通过hugeCapacity方法返回最新的容量值

        newCapacity =hugeCapacity(minCapacity);

    // minCapacity is usually close to size, so this is a win:

    elementData = Arrays.copyOf(elementData, newCapacity);

}

hugeCapacity方法上面已经讲过,这里就不讲了

总结下这个问题的答案:

答案1:DEFAULT_CAPACITY(值是10),说明:如果通过new ArrayList()来生成,第一次扩容默认采用DEFAULT_CAPACITY

答案2:原容量的一半:oldCapacity + (oldCapacity >>1),说明:默认扩容的容量是原容量的一半newCapacity,条件是newCapacity比minCapacity大且小于最大容量的定义

答案3:原容量+新增的元素的个数,说明:默认扩容的容量是原容量的一半newCapacity,但是如果newCapacity比minCapacity小,再扩容的大小为minCapacity

答案4:Integer.MAX_VALUE,说明:newCapacity > Integer.MAX_VALUE - 8,则扩容的大小是Integer.MAX_VALUE

答案5: Integer.MAX_VALUE - 8

问题4:ArrayList会抛出什么异常?什么情况下会抛?
  • IndexOutOfBoundsException: 当读写操作的下标越界时

  • ConcurrentModificationException: 发生在多线程同时增、删的操作

  • IllegalArgumentException: new ArrayList如果指定的大小为负数时;或者subList的fromindex > toIndex时

  • NullPointerException: new ArrayList如果传入的集合为空时;或者添加的集合为空时;或者删除的集合为空时;或者将ArrayList给定的数组为空时;

  • OutOfMemoryError: 扩容的大小为负数或者超出了虚拟机的容许的ArrayList容量时

  • NoSuchElementException:发生在迭代器next或者previous操作越界时

  • IllegalStateException: 发生在迭代器设置、删除操作未先next操作的情况下

最常见的莫过于IndexOutOfBoundsException、ConcurrentModificationException、NullPointerException

问题5:ArrayList如何安全的删除item

也许你会说,直接remove不就可以了。大佬,估计你撸的码不够。。。

为什么会问这个问题呢?因为开发过程中这个问题还是时常遇到的。

非安全情况下删除item会出现什么现象呢?

情况1:单线程中在for循环中删除,就会抛出IndexOutOfBoundsException

情况2:多线程中同时增、删页会抛出IndexOutOfBoundsException

情况3:多线程迭代器中同时增、删页会抛出ConcurrentModificationException

我们针对情况1,我们的解决方案是:

使用迭代器进行删除,参考如下代码:


ArrayList list =new ArrayList(10);

Iterator it = list.iterator();

Integer item =null;

while(it.hasNext()){

item = it.next();

    if(item ==null){

it.remove();

    }

if(item ==10){

it.remove();

    }

}

对于情况2和情况3在问题6中作答

问题6:ArrayList是线程安全吗?如果不是如何避免?

答案肯定是非线程安全,如果一个线程在删除;一个线程在添加;一个线程在读取,那么就造成ArrayList的size变化,具体变成多少是不确定的,所以很容易出现IndexOutOfBoundsException。

既然是非线程安全,那有什么方法可以避免吗?

方案一:如果能确定在单线程中实现相应的业务逻辑,直接采用单线程就可以

方案二:使用CopyOnWriteArrayList

方案三:ArrayList加锁:synchronized或者lock

方案四:直接在初始化时用 Collections.synchronizedList 方法进行包装


List list = Collections.synchronizedList(new ArrayList(20));

问题7:使用ArrayList有什么优化点?

通过上面的讲解结合源码,最大的优化点在于添加元素和初始化操作;如果你确定你的ArrayList的容量或者大概的容量,那么在new ArrayList时指定容量,比如你的某个页面ArrayList的容量大概在在10个左右,那么你就通new ArrayList(10)来初始化。

初始化指定容量的好处在于如果容量范围确定,可以避免添加元素时因为扩容而增加的开销

到此ArrayList的解读、常见问题及解决方案、优化均已经讲解完,恭喜你又成长了(为你点赞!)

以上均是个人的见解,如果有不同异议或者不正确的,欢迎指正

你可能感兴趣的:(ArrayList解读)