ArrayList 是一个用数组实现的集合,支持随机访问,元素有序且可以重复。ArrayList继承AbstractList 并且实现了List和RandomAccess,Cloneable, Serializable接口。
ArrayList继承关系:

1 | public class ArrayList<E> extends AbstractList<E> |
RandomAccess:

Linked并没有实现RandomAccess接口,RandomAccess接口是一个标志接口。
只要List集合实现这个接口,就能支持快速随机访问。随机访问就是表示我们可以在常量时间复杂度内访问数据,也就是时间复杂度是O(1)。而链表是不可以随机访问的,比如说我们想通过下标访问链表当中的某个数据,需要从头结点或者尾节点开始遍历,直到遍历到下标对应的数据,时间复杂度为O(n)。
当一个List拥有快速访问功能时,其遍历方法采用for循环最快速。而没有快速访问功能的List,遍历的时候采用Iterator迭代器最快速。
在这个类的JavaDoc中,描述了ArrayList的一些特征,主要如下:
允许 put null 值,会自动扩容;
size、isEmpty、get、set、add 等方法时间复杂度都是 O(1);
是非线程安全的,多线程情况下,推荐使用线程安全类:Collections#synchronizedList;
增强 for 循环,或者使用迭代器迭代过程中,如果数组大小被改变,会快速失败,抛出异常。
JavaDoc中还提到了fail-fast机制,这个会在下面将迭代器时提到。
实现这个接口的一些类:
ArrayList,Vector,CopyOnWriteArrayList,RandomAccessSubList,UnmodifiableArrayList
Serializable:
这个接口主要用于序列化,所谓序列化就是能将对象写入磁盘,反序列化就是能够将对象从磁盘当中读取出来,如果想序列化和反序列化ArrayList的实例对象就必须实现这个接口,如果没有实现这个接口,在实例化的时候程序执行会报错。
Cloneable:
和serializable一样是标志接口。实现Cloneable接口那么实现Cloneable的类就能够调用clone这个方法,如果没有实现Cloneable接口就调用方法,则会抛出异常java.lang.CloneNotSupportedException。
(1)浅克隆(shallow clone),在拷贝一个对象时,对对象的基本数据类型的成员变量进行拷贝,但对引用类型的成员变量只进行引用的传递,并没有创建一个新的对象,当对引用类型的内容修改会影响被拷贝的对象。
(2)深克隆(deep clone),在拷贝一个对象时,除了对基本数据类型的成员变量进行拷贝,对引用类型的成员变量进行拷贝时,创建一个新的对象来保存引用类型的成员变量。
1.为什么Object类中的clone方法定义为protected,而不是public?
因为不是每个对象都可以被克隆的。Java的设计者故意强制子类在其对象可克隆的情况下重写表方法。
2.为什么clone方法不是定义在Cloneable接口中呢?
因为Java提供了一个本地方法来执行一个浅复制以克隆一个对象。由于接口中的方法是抽象的,该本地方法不能在接口中实现。因此,Java的设计者决定在Object类中定义和实现本地clone方法。
List:
这个接口主要定义了一些集合常用的方法让ArrayList进行实现,比如add,addAll,contains,remove,set,size,indexOf等等方法。
AbstractList,这个抽象类也实现了List接口里面的方法,并且为其提供了默认代码实现。
ArrayList主要方法分析:
(一)字段
1 | /* |
private static final Object[] EMPTY_ELEMENTDATA = {};
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
这两个是用来共享给空数组的,无参构造函数的空数组会用DEFAULTCAPACITY_EMPTY_ELEMENTDATA赋值,有参构造函数的空数组会用EMPTY_ELEMENTDATA赋值。
(二)构造方法
1 | /* |
(三)常用方法
add:
放置新元素的时候没有进行任何的判断,所以ArrayList是允许null值的,且放置是没有加锁,使得ArrayList是线程不安全的。
1 | //直接将待添加的元素放在数组末尾 |

ensureCapacityInternal(扩容部分的核心实现):

1 | private static int calculateCapacity(Object[] elementData, int minCapacity) { |
remove:
1 | //根据下标删除元素 |
get:
1 | public E get(int index) { |
相关问题:
(1)JDK1.7和JDK1.8下ArrayList()底层数组的默认长度?
jdk1.7时使用ArrayList的无参构造,初始化后的长度是10,jdk1.8时使用无参构造,构造一个空数组,初始长度是0。
(2) 如何复制某个ArrayList到另一个ArrayList中去?
使用clone()方法
使用ArrayList构造方法
使用addAll方法
(3)arraylist怎么保证线程安全?
<1>、使用Vector
<2>、使用Collections.synchronizedList()
<3>、使用CopyOnWriteArrayList,涉及线程安全的部分,是通过写时复制的方式来实现。它内部有个volatile数组来保持数据。在“添加/修改/删除”数据时,会先获取互斥锁,再新建一个数组,并将更新后的数据拷贝到新建的数组中,最后再将该数组赋值给volatile数组,然后再释放互斥锁。
1.在做修改操作的时候加锁
2.每次修改都是将元素copy到一个新的数组中,并且将数组赋值到成员变量array中。
3.利用volatile关键字修饰成员变量array,这样就可以保证array的引用的可见性,每次修改之前都能够拿到最新的array引用。
迭代器的弱一致性:弱一致性是指返回迭代器后,其他线程对list的增删改对迭代器是不可见的。
(4)ArrayList频繁扩容导致添加性能急剧下降,如何处理?
使用ArrayList时,可以使用有参构造方法根据业务实际指定集合大小,以减少扩容的次数,提高写入效率。
(5)Java集合的快速失败机制 “fail-fast”和安全失败机制“fail-safe”?
“fail-fast”是java集合的一种错误检测机制,当多个线程对集合进行结构上的改变的操作时,有可能会产生fail-fast机制。fail-fast机制并不保证在不同步的修改下一定会抛出异常,它只是尽最大努力去抛出,所以这种机制一般仅用于检测bug。
原理:迭代器在遍历时直接访问集合中的内容,并且在遍历过程中使用一个 modCount 变量。集合在被遍历期间如果内容发生变化,就会改变 modCount 的值。每当迭代器使用hashNext()/next()遍历下一个元素之前,都会检测 modCount 变量是否为 expectedmodCount 值,是的话就返回遍历;否则抛出异常,终止遍历。
“fail-safe”采用安全失败机制的集合容器,在遍历时不是直接在集合内容上访问的,而是先复制原有的集合内容,在拷贝的集合上进行遍历。
原理:基于拷贝内容的优点是避免了ConcurrentModificationException,但同样地,迭代器并不能访问到修改后的内容,即:迭代器遍历的是开始遍历那一刻拿到的集合拷贝,在遍历期间原集合发生的修改迭代器是不知道的。
场景:java.util.concurrent包下的容器都是安全失败,可以在多线程下并发使用,并发修改。
(6)扩容机制?
首先判断当前数组是不是空数组,如果是空数组,那么将数据由0扩容到需求长度minCapacity,此时有参构造生成的空数组扩容到1,无参构造扩容到10,因为在计算minCapacity时,如果是无参构造,取默认长度和需求长度 minCapacity 中比较大的值,返回10。如果是有参构造的空数组,minCapacity返回1。
如果不是空数组,创建一个长度为原来数组长度1.5倍的新数组,当新数组长度大于ArrayList数组定义的最大值后会调用hugeCapacity来进行判断。如果minCapacity已经大于Integer的最大值,也就是溢出为负数,那么抛出内存溢出异常。否则的话根据与MAX_ARRAY_SIZE的比较情况确定返回Integer的最大值还是MAX_ARRAY_SIZE。最后将旧数组的值用Arrays.copyOf()方法拷贝到新数组。
(6)Iterator 和 ListIterator 有什么区别?
(1)Iterator可以在所有集合中使用,而ListIterator只能在List类型和其子类型中使用
(2)ListIterator和Iterator都有hasnext()和next()方法可以实现顺序向后遍历,但是ListIterator有hasPrevious()方法和previous()方法,可以实现逆向遍历,Iterator不可以。
(3)ListIterator有add()方法,可以向List中添加对象,而Iterator不能。
(4)ListIterator可以定位当前索引的位置,next Index()和previous Index()可以实现,Iterator没用此功能。
(5)两个都可以实现删除操作,但是ListIterator可以实现对象的修改,set()方法可以实现,Iterator不能修改。
(7)迭代器Iterator是什么?
Iterator是可以遍历集合的对象,为各种容器提供了公共的操作接口,隔离对容器的遍历操作和底层实现(解耦)。
迭代器是java23种设计模式之一,用于顺序访问集合对象的元素,无需知道集合对象的底层实现。
(8)Array和ArrayList的区别?
Array可以包含基本类型和对象类型,ArrayList只能包含对象类型。
Array大小是固定的,所以需要事前确定合适的空间大小。ArrayList的大小是动态变化的,在每次添加新的元素的时候都会检查内部数组空间是否足够。
ArrayList提供了更多的方法和特性,比如:all All(),removeAll(),iterator()等。
对于基本数据类型,ArrayList使用自动装箱来减少编码工作量;而当处理固定大小的基本数据类型的时候,这种方式相对比较慢,这时候应该使用Array。




