java集合之ArrayList
相信大家在日常生活开发中,接触的集合当中,ArrayList是大家经常会用到的, 但是想要在码界立足,只是单纯的应用已经满足不了现在的需求了,谁不怀念当初大学在校园操场上牵着好多小妹妹的手在遛弯,好了,不多说了,泪水已经打湿了我的眼眶…(要说装逼这方面,我们谁不会,哈哈…,傻瓜,好好学技术),进入正题。
一:什么是ArrayList
大家应该都知道,他的底层其实就是一个数组结构的,当我们在使用list的时候相比大家都是直接类似new ArrayList<>(), 这种方式来进行创建,我们在new的时候会发生什么呢?(这里只针对JDK1.8来说)
大家都明白ArrayList的初始容量是10, 但是这个10是什么时候给的呢?我们明明new的空的list,咋会是10呢?难道new初始化的时候会直接给容量吗?我们继续看
显然不是new的时候给的,那么当我add的时候呢?这里我先直接给你上add时的源码图
这里面的size属性,是成员变量,大家都知道new对象的时候,有一步是对变量赋默认值,暖心的我在这里帮你们回忆一下,new对象的时候会发生什么。首先第一步会判断对应的类是否已经加载、连接、初始化(这里是当虚拟机遇到new指令的时候,他会去metaSpace找对应的类的符号引用,检查这个符号引用是否已经加载、连接、初始化);第二步会在堆中给他开辟一个内存空间(注意这里会处理并发安全问题,一般会采用cas失败重试机制,区域加锁保证更新原子性和每个线程预先分配一块TLAB,默认下TLAB一般占eden的1%;第三步会去默认初始化;第四步设置对象头;第五步执行init方法初始化。
好了我们继续,所以此时size=0。 然后我们进入ensureCapacityInternal方法,
这个方法里有calculateCapacity方法,我们再进去
到这里我们是不是很明白了,elementData是空,进入if,DEFAULT_CAPACITY是10,在类成员中定义的。接着我们看ensureExplicitCapacity方法,此时入参是10
modCount++,这个有得很关键,下面我回说到;if里会有一个grow操作,我们继续看一下这个方法如下:
相信大家看到这知道这个是在干嘛了吧,minCapacity是10,然后我们通过把oldCapacity数组扩大为原来的1.5倍,这里采用的右移位。如果newCapacity的容量此时与minCapacity做差,小于0就是赋值minCapacity给newCapacity。
Arrays.copyOf(elementData, newCapacity);这个操作是将elementData值放到一个新数组里,新数组的长度大小是newCapacity。然后我们再回到刚才的add方法,elementData[size++] = e;是将你此时add的元素放在数组中,此时add操作完成。这就是给集合添加元素的流程。
二:异常问题(ConcurrentModificationException)
由于公司代码保密原因,这里我只是用案例模拟下:
这是用iterator迭代器来遍历集合,如果发现值是2,就对list进行一个remove操作。这里大家感觉逻辑没问题,但是会报并发修改异常,我们来揭秘下。
首先list.iterator()方法里面就是直接 new Itr(),我们看下Itr()方法
里面有三个参数cursor(遍历当前元素的下标值),lastRet(遍历当前元素的下标的前一个值),expectedModCount = modCount(我前面说过modCount很重要…,在这里边表现,代表修改的次数,比如上面add就会对他+1,每对集合修改一次,就会+1,注意此时上面我们add了两次,所以modCount为2,expectedModCount为2),当我们调用hasNext方法时候,cursor != size()这个判断意思是,cursor集合当前下标如果不等于集合长度,证明集合中会有数据,我们就允许去获取值。然后就是 iterator.next()方法我们在来剖析
checkForComodification这个方法我们先不去看,看下面的,将cursor复制给i,然后获取数据返回,然后我们判断如果这个值是2,我们上面就会进行remove操作。我们看下这个操作源码
最终会起执行fastRemove方法,移除元素,可不是直接remove的值啊,最终还是根据值去寻找的下标然后我们去remove的下标。我们再进去fastRemove方法。
这里modCount又进行了加++操作,此时modCount为3,我们继续往下看
int numMoved = size - index - 1,这里其实就是寻找的你删除的元素的下标之后的元素个数,如果numMoved 是大于0的,我们会进行 System.arraycopy操作,这个操作不用我多解释吧?其实就是对数组进行了一个复制操作。这里最后
elementData[–size] = null这个操作也许你看到以后会说,这是什么傻逼操作,为啥还给弄成null(泪水再一次打湿我的眼眶…),是的,这里为null,是有意义的,是为了让他及时进行垃圾回收,释放空间。你想,删除操作其实就是对后面的元素进行复制到原数组,那我们数组里最后一个元素的空间是不是还会被一直占用啊?这就是典型的内存泄漏,举个例子就是占着茅坑不拉屎,你想憋死他们啊…。
好了,你吹了这么多牛逼,那这个异常发生的点在哪呢?不装逼了,往下看。大家还记得我说的checkForComodification方法吗?我们进去看一下
卧槽!!! 一目了然,这里循环的时候再进行下一次next的时候,会判断这两个数相不相等,不相等就会抛出异常。上面modCount是3,expectedModCount是2,显然不等,所以报错。
写到这里大家估计能了解到这个ArrayList东西了吧,哪里有不对的地方欢迎大家指导,虚心求学,我经常有一句话,不奋斗的人生是没有任何意义的,我们共同加油。