集合之ArrayList的源码分析https://blog.csdn.net/kkfd1002/article/details/79906208

一、介绍

   对于ArrayList,可以说诸位绝不陌生,可以说是在诸多集合中运用的最多一个类之一,那么它是怎样构成,怎样实现的呢,相信很多人都知道数组构成的,没毛病,如果遇到面试的时候,估计还会问,它的默认大小是多少?它是怎样扩容的?它的一个属性modCount有啥作用?线程安全性怎么样?。。。。等待一系列问题。下面就围绕着对该集合的运用来展开,如增删改查,这四个方面。在介绍之前,先来看看该集合的性质

1、里面的元素是有序的(指的是添加的顺序和排列的顺序是一致的)

2、可以添加重复元素

3、增删慢,查询快

4、内部采用了Object数组来存储元素,size为元素个数

5、有线程安全问题

二、增加

先来贴下增加元素的源码

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

添加某个元素,在使用默认构造函数的时候,且是第一次添加元素,第三行代码会触发初始化操作,即对数组扩容到默认大小10,点进去看,如下

复制代码
 1 private void ensureCapacityInternal(int minCapacity) {
 2         //若使用的默认构造函数,且是第一次添加,则返回true,此时开始初始化话
 3         if (elementData == EMPTY_ELEMENTDATA) {
 4             minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);//10
 5         }
 6      
 7         ensureExplicitCapacity(minCapacity);
 8     }
 9 
10 
11 private void ensureExplicitCapacity(int minCapacity) {
12         modCount++;
13 
14         // overflow-conscious code
15         if (minCapacity - elementData.length > 0)
16             grow(minCapacity);
17     }
18 
19 
20 private void grow(int minCapacity) {
21         // overflow-conscious code
22         int oldCapacity = elementData.length;
23         int newCapacity = oldCapacity + (oldCapacity >> 1);
24         if (newCapacity - minCapacity < 0)
25             newCapacity = minCapacity;
26         if (newCapacity - MAX_ARRAY_SIZE > 0)
27             newCapacity = hugeCapacity(minCapacity);
28         // minCapacity is usually close to size, so this is a win:
29         elementData = Arrays.copyOf(elementData, newCapacity);
30     }
复制代码

 如上可以看出扩容步骤:

1、先判断有没有初始化,没有则初始化,有则跳过这步骤

2、检查要扩容的大小和数组的长度哪个大,如果数组长度大,则还没到需要扩容的时候即跳过,进行添加元素同时size+1,反之,则进行下一步grow方法(这里说明下关于size和数组的大小,这两个是不同的概念,这两者关系必满足size<=length,size指的是数组里的元素个数,length指的是数组长度)

3、grow(),该方法是ArrayList扩容的关键方法,在它里面有这几个逻辑判断(注意此时意味着minCapacity超过了数组的长度length)

                  1、获取数组长度,为扩容大小提供值

     2、默认扩大为原数组长度的1.5倍(这里面有精度运算,不一定真的恰好是1.5倍),你扩容后的长度newCapacity它的值为原数组长度+该长度向左移1个长度

                       (即除以2),如,原来是10,则扩容后为10+10>>2=15。

       3、扩容后长度再去和传进来的参数minCapacity去比较大小,取最大值,将最大值赋值为扩容后的长度。

                  4、扩容后的长度再去和MAX_ARRAY_SIZE做上一步操作,该值为Integer.MAX_VALUE - 8,可以知道ArrayList里数组最大的长度就是

                       Integer.MAX_VALUE,这步在hugeCapacity里

       5、利用工具类Arrays去扩容(实际是先new了一个数组,再利用System类的元素复制将元素移动到新数组里,再返回,至于为啥不用for循环,因为这个类的

                       效率比for循环高,是一个本地方法)

以上就是扩容步骤,

扩容完之后,就开始添加元素了elementData[size++] = e;同时size增一。

对于ArrayList方法其它add重载方法,就不一一介绍了,扩容步骤都是一样的,

 

三、移除

 同样,先贴下代码

复制代码
 1 public E remove(int index) {
 2         rangeCheck(index);//当index>=size会抛异常
 3 
 4         modCount++;
 5         E oldValue = elementData(index);
 6 
 7         int numMoved = size - index - 1;
 8         if (numMoved > 0)
 9             System.arraycopy(elementData, index+1, elementData, index,
10                              numMoved);
11         elementData[--size] = null; // clear to let GC do its work
12 
13         return oldValue;
14     }
复制代码

 

先去检查移除的位置的正确性,或许会有人问,怎么不去检查index是否大于0?其实继续往下看就知道了,在第五行代码,若为负数的话,则会直接抛异常ArrayIndexOutOfBoundsException。

若index在正常范围内,则去判断要不要移动元素,即index后面的所有元素都往前移动一步,最后将size-1这个位置的元素设为null,来帮助垃圾回收,返回旧值。。

对于它的重载函数,remove(Object o),代码如下,要注意的是,它移除的是从数组下标为0开始往后找的第一个元素,再利用fastRemove来进行移除(和上一个remove(int)比较就是少了index判断),最后返回true,移除成功,fasle移除失败

复制代码
 1 public boolean remove(Object o) {
 2         if (o == null) {
 3             for (int index = 0; index < size; index++)
 4                 if (elementData[index] == null) {
 5                     fastRemove(index);
 6                     return true;
 7                 }
 8         } else {
 9             for (int index = 0; index < size; index++)
10                 if (o.equals(elementData[index])) {
11                     fastRemove(index);
12                     return true;
13                 }
14         }
15         return false;
16     }
复制代码

 

该集合还提供大规模的移除,不过用的不多(提供给子类和同包类用的)。贴下代码

复制代码
 1 protected void removeRange(int fromIndex, int toIndex) {
 2         modCount++;
 3         int numMoved = size - toIndex;
 4         System.arraycopy(elementData, toIndex, elementData, fromIndex,
 5                          numMoved);
 6 
 7         // clear to let GC do its work
 8         int newSize = size - (toIndex-fromIndex);
 9         for (int i = newSize; i < size; i++) {
10             elementData[i] = null;
11         }
12         size = newSize;
13     }
复制代码

 

具体原理和之前移除一个元素一样,不过多叙述

 

四、查找

 前面的介绍说该集合查找快,让我们,它比增删快在哪?

1 public E get(int index) {
2         rangeCheck(index);
3 
4         return elementData(index);
5     }

 

代码非常精简,检查了下index的正确性,然后直接返回,因为它是一个数组,可以直接定位下标。相比增删,少了移动元素,少了新创建数组。必然快,而对于LinkedList这个集合,却和ArrayList相反,关于LinkedList这个集合,在后面的博文中会出现,到时候会做一个比较。

 

五、修改

 

复制代码
1 public E set(int index, E element) {
2         rangeCheck(index);
3 
4         E oldValue = elementData(index);
5         elementData[index] = element;
6         return oldValue;
7     }
复制代码

 同样先检查index的正确性,然后直接改变原index位置的值,返回旧值。

六、modCount的作用

 细心的人会发现,每一次的增删都有modCount这个变量的出现,并且每次都是+1,至于为什么进行“改查”modCount却不改变,这是因为,该集合是一个数组,如果进行增加或删除,则size必然改变,那么在遍历过程中,肯定找不到正确的结果或者直接数组越界异常,这都是我们不想要的,改查,只是改变元素值,而不是元素个数。在了解这个变量之前,先来了解下,快速失败(fail-fast)和安全失败(fail-safe)的两个概念.

    fail-fast 快速失败,产生于遍历集合过程中,如某个线程对集合集合遍历时,有另外线程对该集合做修改操作,则会抛出ConcurrentModificationException异常,其作用是用来检测错误,并不一定发生。

 fail-safe 安全失败,在遍历集合,并不是直接在原集合中操作,而是先复制一个集合,在复制的集合中操作,这样就不会产生ConcurrentModificationException异常。缺点是改变后不能看到改变后的结果。

其中安全失败里提到的复制集合,并不是直接将原来的集合赋给新集合,如list_1=list_2,这样只是把内存中的引用地址给了list_1,如果list_1也进行增删操作,那么list_2也能得到相应的结果,即两个集合操作都是对同一个堆内存的地址里的数据进行操作,前面说的复制是另开辟一个堆内存。(切记)。

在了解这两个概念后,应该可以猜的出来,modCount的作用就是用来fail-fast的,具体体现在如下

查看ArrayList源码可知,它有一个内部类Itr(实现了Iterator),这个类的作用就是使ArrayList集合可以迭代操作,该类有这几个方法,如下图

 

复制代码
 1 private class Itr implements Iterator<E> {
 2         int cursor;       // index of next element to return
 3         int lastRet = -1; // index of last element returned; -1 if no such
 4         int expectedModCount = modCount;
 5 
 6         public boolean hasNext() {
 7             return cursor != size;
 8         }
 9 
10         @SuppressWarnings("unchecked")
11         public E next() {
12             checkForComodification();
13             int i = cursor;
14             if (i >= size)
15                 throw new NoSuchElementException();
16             Object[] elementData = ArrayList.this.elementData;
17             if (i >= elementData.length)
18                 throw new ConcurrentModificationException();
19             cursor = i + 1;
20             return (E) elementData[lastRet = i];
21         }
22 
23         public void remove() {
24             if (lastRet < 0)
25                 throw new IllegalStateException();
26             checkForComodification();
27 
28             try {
29                 ArrayList.this.remove(lastRet);
30                 cursor = lastRet;
31                 lastRet = -1;
32                 expectedModCount = modCount;
33             } catch (IndexOutOfBoundsException ex) {
34                 throw new ConcurrentModificationException();
35             }
36         }
37 
38         final void checkForComodification() {
39             if (modCount != expectedModCount)
40                 throw new ConcurrentModificationException();
41         }
42     }
复制代码

 

在建立该类的实例,首先会初始化几个成员变量,cursor和、astRet、expectedModCount,值依次为0,-1,modCount。

hasNext(),是用来判断集合有没有下一个元素,该代码是直接判断当前指针cursor和元素个数size是否相等。

next(),先会去检查modCount的值和expectedModCount值是否相等,如果不等,则直接抛出异常,看到这,或许大家就明白了modCount的作用了吧,modCount值要改变必然要对集合进行增或删操作。之后就取当前指针cursor位置的值。

remove(),该方法也是首先会去判断集合有没有被改变,之后进行相应操作。

七、最后

 对于ArrayList的线程安全问题,可以采用工具类Collections,该类有一个方法synchronizedList(List),可保证线程安全,但效率可能有点低,

还有一个方法就是采用CopyOnWriteArrayList类来操作数据。

http://www.dxq9350.top/
http://www.xom4602.top/
http://www.dcj6843.top/
http://www.rew9011.top/
http://www.vrc3443.top/
http://www.xzg8926.top/
http://www.hrd7175.top/
http://www.njn2935.top/
http://www.gmn7922.top/
http://www.gdi2417.top/
http://www.dpz7007.top/
http://www.slg0631.top/
http://www.pzq0064.top/
http://www.jqg9208.top/
http://www.smp7329.top/
http://www.sqd7023.top/
http://www.bmh5849.top/
http://www.lwg3929.top/
http://www.wgt9662.top/
http://www.bux1348.top/
http://www.ukr4854.top/
http://www.cfs8763.top/
http://www.psd1092.top/
http://www.xck1603.top/
http://www.fgm4024.top/
http://www.zoj1707.top/
http://www.oiv1998.top/
http://www.ftw8814.top/
http://www.jfs6888.top/
http://www.kdx4817.top/
http://www.sbx6519.top/
http://www.rrq5611.top/
http://www.pxk9336.top/
http://www.vik6796.top/
http://www.kod8371.top/
http://www.nuq3623.top/
http://www.vfv3740.top/
http://www.tbt7039.top/
http://www.wky3695.top/
http://www.kcs3342.top/
http://www.gum4900.top/
http://www.mrw5927.top/
http://www.wnu1861.top/
http://www.vlc4617.top/
http://www.idv6045.top/
http://www.jmk5203.top/
http://www.mug5965.top/
http://www.gtt6107.top/
http://www.cnp6436.top/
http://www.sdx1013.top/
http://www.jwd3113.top/
http://www.qeu2095.top/
http://www.tux4376.top/
http://www.tay3928.top/
http://www.tgq6935.top/
http://www.win4778.top/
http://www.ngh4321.top/
http://www.cqq1459.top/
http://www.fxm1291.top/
http://www.wyz5825.top/
http://www.kbx3827.top/
http://www.tqt8862.top/
http://www.yma5505.top/
http://www.rye6349.top/
http://www.xqs4260.top/
http://www.fjv3790.top/
http://www.wqv1289.top/
http://www.mxz6626.top/
http://www.npl2536.top/
http://www.vme0237.top/
http://www.edr0603.top/
http://www.kft8502.top/
http://www.kwb2561.top/
http://www.dqv1869.top/
http://www.iai9521.top/
http://www.jla2696.top/
http://www.vip1477.top/
http://www.ryc2010.top/
http://www.tpz5308.top/
http://www.gzi6562.top/
http://www.dwf8840.top/
http://www.soq5741.top/
http://www.pud6117.top/
http://www.zrv3166.top/
http://www.ylg5948.top/
http://www.evm5267.top/
http://www.mmc0244.top/
http://www.auk8793.top/
http://www.nbw1542.top/
http://www.oka2713.top/
http://www.txz2284.top/
http://www.amv5754.top/
http://www.ubb2737.top/
http://www.oim9564.top/
http://www.jhg1176.top/
http://www.ehe5445.top/
http://www.vkp0211.top/
http://www.uzy8206.top/
http://www.cnn5986.top/
http://www.nwz4782.top/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值