解析集合 一 (重温ArrayList源码)
(#)背景:最近遇到了很多集合层面的问题,可能是自己对于集合本身只是简单了解内部的原理,忽略了其中的细节,所以最近来再重温一下各种集合的实现细节
(#)原理:对于ArrayList首先,应该知道就是这是一个底层用‘动态数组’实现的集合,此类继承的接口不多说了,这个没什么实际的意义,下面一起来看看内部实现代码
这是此类的成员变量,很容易理解,默认的数组大小10,定义控的数组实例,很显然elementData这个数据就是存储list中的元素的可变数组,注意此数组是不可序列化的,
size这个就是代表数组中实际存储的元素的数量.
(#)下面看看实例化一个List的时候,内部是怎么实现的----构造方法
ArrayList提供了三个构造方法,当不指定大小的时候,默认的情况会创建一个空的数组,当实际存在一个元素的时候,数组会进行一次扩容,大小会变成默认大小也就是10,
当指定大小的时候,会创建一个initialCapacity大小的数组来初始化,当传入一个集合的时候,如果集合空那么还是创建一个空的集合,否则会将传入集合的元素copy到ele
mentData中,此时完成了对与内部数组的初始化
(#)一直再说ArrayList是动态数组,那么这个动态是怎么体现的呢,下面来看一下
每次添加元素的时候,都会进行检查,来确定是否需要进行扩容,当存入的元素加上size已经大于elementData的时候,此时需要进行扩容了,可以中带关注一下grow
函数,此函数是进行扩容的核心代码,扩容的时候会先进行一个1.5倍的扩容,然后与实际需要的大小进行比较,进行一个最后大小的确认,之后剩下的就很简单了,将原有
数组的内容copy过去就可以了,现在elementData就变成了扩容之后的数组了
(#)下面来看看,增删改查了
查询代码也是很简单的,遍历集合来查找指定的元素,但是一个问题就是查询的效率不太高,看代码也看到了,最坏的情况需要遍历O(n)次,而这种get(i),速度就很快了
相当于直接elementData[i],所以是O(1)的,set方法与之类似,需要记得一点就是所有涉及到下表i的操作,都需要进行越界检查,越界检查只需要和size比较就可以了
而不是与elementData.size比较
对于add方法,可以看出来这是一个核心方法,添加元素的时候,首先需要做的就是判断是否需要进行扩容,所以将size加1进行此次判断,扩容这步过后才是存储数据,
对于删除方法的话,稍微复杂一点点,首先还是会进行越界检查,之后修改了modCount,这个字段非常有用,它记录了数组的修改次数,有一些集合在遍历的时候要进行
modCount与expectedModCount的比较,如果只修改了其中一个就会抛异常了ConcurrentModificationException,这个以后再说,剩下的删除就很简单了,直接把
index后的通过复制向前移动一位,然后将最后一个直接设置成null,整体上使用了一个比较巧妙的方法来进行删除,其实我觉得设置成null与// clear to let GC do its work
关系不大,因为如果你不设置成null的时候,那么末尾就会存在两个一样的元素........
(#)iterator的实现, 这个是比较有意思的,也是大师的设计,废话不多说,来一起看看
在调用iterator的时候,实际上等于每次都是new 了一个Itr类,下面看看这个内部类的代码
cursor就相当于一个游标,来标记elementData的下一个元素,你看这里就有一个问题来了,每次next的时候,都会比较modCount和expectedModCount。。。。
就像上面说的如果在正在遍历的时候修改了elementData那么modCount就会改变,这个时候就会抛异常了,而使用Itr自带的remove方法来删除的话,不会抛异常,
原因就是此remove方法同时维护了modCount和expectedModCount,还有一个问题需要关注的就是,每次调用iterator()方法的时候,每次都会新new一个迭代器
出来,这在业务上也是合理的,因为迭代器只能便利一次嘛,如果每次不new一个新的出来,那么只能迭代一次了,剩下的方法其实都是基本方法的变形,理解了所有基本
的操作之后,我想没有其他问题想不明白的了
(#结尾)当对于某部分代码有疑问,想不明白的时候,最简单的办法其实就是看看源码,不仅能学习别人的代码风格,设计思路,还能熟悉原理,这样才能写出来更优雅的代
码