前言:
个人觉得看源代码会增进不少知识,一直都是看别人写的博客中的代码片段,没自己静下心来去看过,接下来体验体验源码的快乐。
对 ArrayList 相应关系有了大致的了解后,咱由外到里依次深入。
ArrayList 利用 MIXIN 的思想,实现了多继承。
说明其实现的接口:
Serializable
接口:使得ArrayList 支持序列化和反序列化。RandomAccess
接口:该接口是一个标记接口,意在标明ArrayList 具备快速随机访问的能力,使用for 会比使用 迭代器(iterator)访问速度要快。LinkedList 那边即不具备这种能力,所以不存在这种标记(原因是因为LinkedList 底层是链表,访问都是从根到尾的,所以它使用迭代器会更快)。Cloneable
接口:也是一个标记接口,实现了它便可以去重写Object 下的clone 方法,利于对象进行创建副本。(注意:是ArrayList 实现了Cloneable接口,当利用多态向上转型为List 时,是不可以使用的。)下面是 ArrayList 内部重写的 clone 方法的代码。
/*** Returns a shallow copy of this {@code ArrayList} instance. (The* elements themselves are not copied.)** @return a clone of this {@code ArrayList} instance*/public Object clone() {try {ArrayList> v = (ArrayList>) super.clone();v.elementData = Arrays.copyOf(elementData, size);v.modCount = 0;return v;} catch (CloneNotSupportedException e) {// this shouldn't happen, since we are Cloneablethrow new InternalError(e);}}
emmmm…这里我不知道返回值的数据类型不直接为 ArrayList
,一般我去重写的话就会直接。。。
ArrayList 有三个构造方法,俩有参,一无参的。
第三个描述的那个构造方法如何使用呢 ?
由于Arrays.asList()
得到的集合固定大小的,是由Arrays内部类所实例化的,是没有 clone(),add(),remove(),clear()
等这样的动态操作的,但如果想要使用这种动态操作怎么办呢?
那咱就可以利用这个构造方法,看代码更清晰:
String[] s = {"xx","yy"};//List xxx = Arrays.asList(s);//xxx.add("mm"); /*这里运行最后是会报异常的,这是因为Arrays 里面有个ArrayList 的内部类,它没有重写add和remove等方法。但没运行是不会爆红的,虽然没有重写那些方法,但该内部类继承了AbstractList,这个类里面这些方法不是抽象的...*/List list = new ArrayList<>(Arrays.asList(s));list.add("mm");list.forEach(System.out::println);// 正常输出
先不说方法,先说说类里面实现的Iterator
接口和它的子接口,ListIterator
接口。(虽然ArrayList 实例对象用迭代器的话比较少,但是量变质不变,LinkedList 可以用啊)。
Itr
内部吧:ListIterator
接口实现类 ListItr
(继承了Itr
) 的内部吧:
这里继承,所以forEachRemaining
方法也是可以使用的。
有了 listIterator()
, 不会还有人用 iterator()
吧。注意ListIterator 只在List 集合中出现了哦,也就是 Vector、ArrayList、LinkedList、Stack这样的。
也可以看看 JAVA中ListIterator和Iterator详解与辨析 这篇博客对其的一些解释。
add 对外提供的方法有俩,
add(E e)
和add(int index, E e)
。它俩有公共的点是,判断类内定义的elementData
数组长度是否和元素size(个数)
相匹配,是否需要扩容。
也就是说它们俩有一个共同的方法,让elementData
扩容。咋括滴勒,咱来看看这个grow
方法内部。
咱看别人博客都说是1.5倍扩容的,但是是怎么个扩容呢?下面看源码分析分析。
通过调用 ArraysSupport
类中的newLength
方法,我们获得newCapacity
后,通过Arrays.copyOf
完成扩容。
ArraysSupport
类中的newLength
方法。(1.5倍的由来)Arrays.copyOf
方法调用吧:
它内部用的是System.arraycopy
这个方法。这是核心啊,我觉得这也是ArrayList 性能要比较好的关键。这玩意将数据复制给一个副本然后返回给elementData数组。arraycopy 是本地(native)方法。它强在它是浅拷贝,它是直接拷贝所存放的地址,而不是像深拷贝那样重新分配内存。
有关浅拷贝和深拷贝,可以看看这篇《浅拷贝和深拷贝》,或者我写的这篇《clone方法》。
了解完如何扩容之后,再来看看俩方法的具体实现:
remove 方法也有两种对外提供,remove(int index),remove(Object obj)
。
它们都是根据索引去调用内部的 fastRemove(Object[] es, int i)
方法去进行移除。也是调用System
下面的arraycopy
方法,然后把那个设置为空(null),让GC
回收。
咱先来看看 fastRemove
方法的实现吧:
有关 mark
标记的使用可以看这篇博客《mark标号》。
从代码可以看出,不管传入的参数是空还是非空,都会对 elementData
进行操作进行删除。
从jdk1.8
后,List接口就引入了sort
这个默认方法。
及其舒适简单。
可以直接用LIst集合中的sort了,其实调用Collections 工具类中的sort 方法,最后还是会回到List 集合中,还不如直接使用呢。
还有一些常用的 方法,比如:
trimToSize()
还是通过Arrays.copyOf 将数组长度变成和数组元素个数(size)一致,可以减少空间的损失。理解了 ArrayList 的本质,这些方法的实现其实自己想也想的出来。
可以看看这篇博客写的几点线程不安全的情况以及其测试。
《ArrayList为什么线程不安全 线程不安全体现在哪些方面 源码角度分析其具体原因》
ArrayList
它很多地方是分步进行操作的,**而且内部元素也是可以为 null 的。**它不是线程安全的。ArrayList
删除元素时,只会改变元素的个数(size
),而不会改变容量(elementData.length
),如果想要更改,可以调用trimToSize()
方法,它会给你改成和元素个数一样的值。