好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

Java源码解析ArrayList及ConcurrentModificationException

本文基于jdk1.8来分析arraylist的源码

首先是主要的成员变量。

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

/**

  * default initial capacity.

  **/

private static final int default_capacity = 10 ;

/**

  * shared empty array instance used for empty instances.

  **/

private static final object[] empty_elementdata = {};

/**

  * shared empty array instance used for default sized empty instances. we

  * distinguish this from empty_elementdata to know how much to inflate when

  * first element is added.

  **/

private static final object[] defaultcapacity_empty_elementdata = {};

/**

  * the array buffer into which the elements of the arraylist are stored.

  * the capacity of the arraylist is the length of this array buffer. any

  * empty arraylist with elementdata == defaultcapacity_empty_elementdata

  * will be expanded to default_capacity when the first element is added.

  **/

transient object[] elementdata; // non-private to simplify nested class access

/**

  * the size of the arraylist (the number of elements it contains).

  *

  * @serial

  **/

private int size;

其中初始大小为10,size表示集合中元素的个数。此外,还有两个空数组empty_elementdata,和defaultcapacity_empty_elementdata。通过defaultcapacity_empty_elementdata的注释,我们可以了解到,这个变量区别于empty_elementdata,主要是为了决定第一个元素插入时,扩容多大的问题。从这里的描述可以理解到,arraylist创建好后,其实并没有真正分配数组空间,而是在第一个元素插入时,才分配的空间。这一点是区别于jdk1.6的。在jdk1.6中,arraylist一创建,数据空间就默认分配好了,10个或指定的空间。jdk1.8这么做,可以做到空间延迟分配,提高程序性能。

接下来看一下构造函数。

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

/**

    * constructs an empty list with an initial capacity of ten.

    **/

   public arraylist() {

     this .elementdata = defaultcapacity_empty_elementdata;

   }

/**

    * constructs an empty list with the specified initial capacity.

    *

    * @param initialcapacity the initial capacity of the list

    * @throws illegalargumentexception if the specified initial capacity

    *     is negative

    **/

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

     }

   }

无参构造函数,将创建一个长度为0的空数组。

有参构造函数,参数大于0时正常创建数组,参数为0时,也是创建长度为0的数组。但它和无参构造函数创建的空数组是可以区别开的,它们使用了不同的对象。

接下来是插入元素add。

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

/**

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

    *

    * @param e element to be appended to this list

    * @return <tt>true</tt> (as specified by {@link collection#add})

    **/

   public boolean add(e e) {

     ensurecapacityinternal(size + 1 ); // increments modcount!!

     elementdata[size++] = e;

     return true ;

   }

  private void ensurecapacityinternal( int mincapacity) {

     ensureexplicitcapacity(calculatecapacity(elementdata, mincapacity));

   }

  private static int calculatecapacity(object[] elementdata, int mincapacity) {

     if (elementdata == defaultcapacity_empty_elementdata) {

       return math.max(default_capacity, mincapacity);

     }

     return mincapacity;

   }

  private void ensureexplicitcapacity( int mincapacity) {

     modcount++;

     // overflow-conscious code

     if (mincapacity - elementdata.length > 0 )

       grow(mincapacity);

   }

通过calculatecapacity函数,我们可以知道,如果是用new arraylist()创建的list,第一次add元素,计算得mincapacity = 1。如果是new arraylist(0)创建的list,计算得mincapacity = 10. 然后再根据mincapacity去grow。

get方法比较简单,这里不再分析。

arraylist的一个常见问题是concurrentmodificationexception,同步修改异常,也称为快速失败,fast-fail。当我们以foreach方式遍历arraylist时,如果在遍历过程中删除arraylist的元素,或者别的线程往arraylist中添加元素,就会抛出该异常。这里需要注意,以for(int i = 0; i < list.size(); i++)的方式遍历arraylist时,是不会抛出同步修改异常的,但用这种方式遍历,需要处理好i的前进速度。

那么,用foreach方式遍历arraylist为什么会抛出同步修改异常呢?

foreach代码的底层实现,是用iterator对arraylist进行遍历,在遍历过程中,会持续调用next获取下一个元素。next方法中,会首先checkforcomodification(),它的作用是检查modcount和expectedmodcount是否相等。不相等时,则抛出同步修改异常。那么什么情况下修改次数和期望修改次数不相等呢?这里需要首先弄明白,modcount和expectedmodcount是什么东西?modcount是arraylist从它的父类继承来的属性,记录了集合的修改次数,add,remove时都会给modcount加1. expectedmodcount是迭代器的成员变量,它是在创建迭代器时,取的modcount的值,并且,在遍历过程中不再改变。那么就清楚了,expectedmodcount其实是开始遍历时modcount的值,如果在遍历过程中,arraylist进行了add或remove操作,那么必然导致expectedmodcount和modcount不相等,于是就抛出了同步修改异常。

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

public e next() {

   checkforcomodification();

   int i = cursor;

   if (i >= size)

     throw new nosuchelementexception();

   object[] elementdata = arraylist. this .elementdata;

   if (i >= elementdata.length)

     throw new concurrentmodificationexception();

   cursor = i + 1 ;

   return (e) elementdata[lastret = i];

}

final void checkforcomodification() {

   if (modcount != expectedmodcount)

     throw new concurrentmodificationexception();

}

那么,同步修改异常如何避免呢?或者说,我们如何遍历集合并把其中的某些元素删除呢?

答案是使用迭代器的remove方法删除元素。在迭代器的remove方法中,删除元素后,会重新把modcount赋值给expectedmodcount,所以,它不会抛出同步修改异常。

?

1

2

3

4

5

6

7

8

9

10

11

12

13

public void remove() {

      if (lastret < 0 )

        throw new illegalstateexception();

      checkforcomodification();

      try {

        arraylist. this .remove(lastret);

        cursor = lastret;

        lastret = - 1 ;

        expectedmodcount = modcount;

      } catch (indexoutofboundsexception ex) {

        throw new concurrentmodificationexception();

      }

    }

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对的支持。如果你想了解更多相关内容请查看下面相关链接

原文链接:https://blog.csdn.net/li_canhui/article/details/85001591

查看更多关于Java源码解析ArrayList及ConcurrentModificationException的详细内容...

  阅读:11次