Java源码阅读笔记-ArrayList

继承与实现
public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
ArrayList继承自AbstractList,实现了List,RandomAccess,Cloneable,Serializable。前两者都可以追溯到Collection接口,即要求实现size(),isEmpty()等集合类通用的方法。父类AbstractList事先实现了部分方法如indexOf(),clear(),还有一个很重要的ListIterator:这是一个专门用于List的遍历器,并且可以实现反向便利。
后三个接口的作用为:
1.RandomAccess标明ArrayList是支持随机访问的,可以使算法能够在随机和顺序访问的list中表现的更加高效。
2.Cloneable标明了ArrayList是支持clone方法的,虽然protected clone()是Object类中定义的,但是如果尝试调用一个未实现Cloneable的方法,会抛出ClassNotSupportException。ArrayList的clone实现为{}大意为新建一个ArrayList,并调用工具类Arrays将核心数组拷贝给新ArrayList,并初始化修改计数器mobCount为0,最后返回。
3.Serializable标明ArrayList可被序列化,后者实现了writeObject和readObject方法,主要是对自身数据进行for循环遍历逐个转化。

构造器
    public ArrayList(int initialCapacity) {}
首当其冲的是带参构造器public ArrayList(int i) {} 通过参数预设好了内部数组elementData的长度。此构造器适合在实现知道数据集的长度或者最大长度时调用,当然这并不意味着数组的长度被定死了。详情稍后解析。

public ArrayList(){}
第二位是无参构造器public ArrayList() {}直接讲elementData赋值为默认长度数组(10位)。

    public ArrayList(Collection<? extends E> c) {}
第三位构造器以集合为构造器,要求集合元素与自身所存元素对应,实现逻辑为:直接调用数据源的toArray()获取存储数据的数组并赋值给elementData,并做了检验,当toArray()返回的不是对应类型数组时,重新调用Arrays.copyOf进行拷贝。

如果ArrayList本质是用数组来存储数据的,那么如何保证能无限的存储数据而不发生溢出indexoutofboundexception呢?
原因就在elementData数组并非一成不变,当ArrayList发现当前elementData已满时,便会进行一系列操作以拓展数组长度,核心在与grow(int minCapacity){}基本逻辑就是计算出一个合适的新范围newCapacity,然后将数据转移到一个新数组并赋值给elementData,新数组的长度自然就是newCapacity。程序逻辑复杂之处并不在拓展而在于计算新范围,默认拓展为原来的三倍,当要求拓展的范围更大时使用指定的范围,这样不可避免会出现数组的长度比int的最大值还要大,所以对此特殊情况也会特殊处理。
数组拓展的操作并非ArrayList内部专享,ArrayList还提供了一个public void ensureCapacity(int minCapacity){}供外部使用者调用以拓展。

一系列的ArrayList状态量:size、isEmpty、contains、indexOf、lastIndexOf
都是非常简单的实现,值得注意的是indexOf是用for循环逐个用equals进行比较而不是使用==,这就意味一旦equals匹配成功,即使地址不同也会返回对应下标。

一系列操作方法get、set、add、remove等
get、set在操作之前都要对index进行检查,逻辑很好理解。
add方法在添加元素之前先对当前容量进行检查,而进行插入操作的add(int index,E element)使用了一个System类的静态方法arrayCopy将elementData空出一个位置,再插入数据。
addAll方法用于直接添加一整个数据集,ArrayList会尝试拓展一下长度,再去拷贝数据。
两个remove方法,一个移除对应下标的元素,另一个移除指定的对象。前者自己实现了自己的移除逻辑,后者调用了内部方法fastRemove()进行移除,问题在于两者逻辑都是一模一样的,代码都没变过一个字,为什么在这种地方会出现重复代码,实在是令人困惑。
removeAll方法用于批量删除数据,但要求参数中的数据集不能包含Null元素。再调用batchRemove方法,核心逻辑为遍历,保留需要留下的元素,在finally代码块中对尾部数据进行了剔除,返回的boolean量表示是否有元素发生了改变,不一定代表是否操作完全成功。
removeIf方法是java1.8新增的,用于函数式编程,需要注意的是结果返回的true并不代表完全操作成功,而是指是否有任何一个元素发生了更改。同理的还有replaceAll
clear方法直接遍历所有元素并设为null,交给GC回收。
sort方法,使用传入的比较器进行排序,由于排序为耗时操作而ArrayList又不是线程安全的,有可能在排序时发生元素的更改,所以在排序完成后检查一下期间是否发生了更改,一旦更改直接报错

内部类Itr和ListItr
这两个都是ArrayList交给使用者的遍历器,区别在于Ltr实现了向后遍历next()和删除当前遍历到的元素的remove()方法,还有一个可用于函数式编程的批量操作函数forEachRemaining。而ListItr在继承Ltr之后额外实现了向前遍历previous,修改set,添加add方法,可以更加方便地操作ArrayList。

内部类subList
这是一个类似于视图的存在,相当于截取一段数据供操作,如果对父list进行了结构性修改(对长度进行了改变)将会直接导致当前subList失效,而两者的非结构性操作都会影响彼此。令人吃惊的是,JDK甚至在subList内部专门实现了一个ListIterator供使用,基本逻辑与ArrayList的遍历器无差,只是对实际操作的数据下标进行了适配。

内部类Spliterator
这是java1.8新增的并行遍历迭代器,对其独立进行分析又可以独立写一篇笔记了,就先略tou过lan。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值