ArrayList源码剖析
ArrayList是基于数组实现的,是一个动态数组,其容量能自动增长,类似于C语言中的动态申请内存,动态增长内存。
ArrayList不是线程安全的,只能用在单线程环境下,多线程环境下可以考虑用Collections.synchronizedList(List l)函数返回一个线程安全的ArrayList类,也可以使用concurrent并发包下的CopyOnWriteArrayList类。
ArrayList实现了Serializable接口,因此它支持序列化,能够通过序列化传输,实现了RandomAccess接口,支持快速随机访问,实际上就是通过下标序号进行快速访问,实现了Cloneable接口,能被克隆。
序列化、序列化传输。所谓的序列化就是实现 Serializable 接口,使用 ObjectInputStream 和 ObjectOutputStream进行对象的读写。它是一种为了保存内存中各种对象的状态,并且可以把保存的对象状态再读出来。比如在你的机子上java进程中的对象怎么传递远程java进程呢?或者在jvm停止运行的时候,怎么恢复之前运行的对象信息呢?这些通过序列化都能做到。
1 | package java.util; |
fail-fast机制:它是Java集合的一种错误检测机制,当多个线程对集合进行结构上的改变的操作时,有可能会产生fail-fast机制。记住是有可能,而不是一定。例如:假设存在两个线程(线程1、线程2),线程1通过Iterator在遍历集合A中的元素,在某个时候线程2修改了集合A的结构(是结构上面的修改,而不是简单的修改集合元素的内容:比如删除了某个数据),那么这个时候程序就会抛出异常。集合类通过记录修改集合结构的次数来确保这个机制的生效
注意其三个不同的构造方法,无参数构造的ArrayList的容量默认是10,带有Collection参数的构造方法,将Collection转化为数组赋给ArrayList的实现数组elementData
注意扩充容量的方法:ensureCapacity。ArrayList在每次增加元素,哪怕是一个,都会调用该方法来确保容足够的容量。当容量不足时,就设置当前的容量为旧容量的1.5倍加1。如果设置后的新容量还是不够,就设置为当前所需的容量。然后调用Arrays.copyof()方法讲元素拷贝到新的数组。也就是说每当容量不够时,都会讲原来的元素拷贝到一个新的数组,这个操作是非常耗时。建议在使用ArrayList的时候给定一个预估的长度。如果不确定长度,建议使用LinkedList
实现中大量的调用了Arrays.copyof和System.arraycopy(),首先来看一下copyof
1 | public static <T> T[] copyOf(T[] original, int newLength) { |
实际就是创建了一个新数组,把就旧数组的数据拷贝过去。调用的是System.arraycopy函数进行的赋值操作。那么System.arraycopy是如何实现的呢?该方法被标记了native,也就是调用了系统内部的C语言的memmove函数。它可以保证同一个数组内元素的正确复制和移动,比一般的复制方法的实现效率要高很多,很适合用来批量处理数组。Java强烈推荐在复制大量数组元素时用该方法,以取得更高的效率。
memmove函数:和memcpy功能类似,memmove函数是可以拷贝即使原数组和目标数组地址重叠了的情况。
- 在查找给定元素索引值等的方法中,源码都将该元素的值分为null和不为null两种情况处理,ArrayList中允许元素为null。
- ArrayList基于数组实现,可以通过下标索引直接查找到指定位置的元素,因此查找效率高,但每次插入或删除元素,就要大量地移动元素,插入删除元素的效率低