首先看下两个类继承关系上的差异,都实现了List接口,这也是我们平时常用到的,都实现了Cloneable接口,说明两个类都是可以调用clone()方法的,否则会抛出一个CloneNotSupportedException异常,以及都实现了Serializable接口,可以实现序列化。
不同的地方在于,ArrayList继承AbstractList抽象类,而LinkedList继承的是AbstarctSequentialList抽象类,查看AbstarctSequentialList抽象类可以知道,AbstarctSequentialList也是继承了AbstractList抽象类的抽象类,只是对AbstractList的几个抽象方法做了些具体的默认实现。
然后,ArrayList实现了一个RandomAccess接口,这是一个标志性接口,没有任何的方法定义,具体在Collections.binarySearch()方法中体现,当一个集合实现类实现了RandomAccess接口,就会利用索引进行二分查找,否则就使用迭代器。
网上有人做了实验表明,ArrayList用for循环遍历比iterator迭代器遍历快,LinkedList用iterator迭代器遍历比for循环遍历快。
而LinkedList实现了一个Deque的接口,该接口定义了一些双向列表的方法,比如在头尾加入元素,出队,入队等方法。
AbstarctList为两个类提供了一个modCount的int类型的属性,初始值为0,来记录对list(统称两个类都为list)的修改操作的次数,并提供了一种快速失败机制,当list迭代循环的时候,检测到modCount变化,就会抛出一个ConcurrentModificationException异常。如下图代码所示:
AbstarctList代码逻辑都比较简单,值得注意的一点是,我们常用的subList(int formIndex, int toIndex)方法,其返回的不是一个新的list,而是当前list的一个视图。
从上面代码段中可以看出来,SubList这个内部类只不过维护了当前AbstractList的一个引用,和记录了formIndex作为视图的偏移值,和toIndex-formIndex作为视图的长度,用于检查数组越界问题。所以,subList方法生成的新List,不过是原来List的一个视图,当对新List进行元素添加或者删除时,会对原有的List也产生影响。
ArrayList本质上是一个可变的数组,其存储元素的容器是类内部的一个Object数组elementData。
有一个细节
当elementData没有元素是个空数组的时候,会将elementData指向不可变的静态变量EMPTY_ELEMENTDATA属性,以免每个空List都维护自己的空数组,节约内存空间。下面的DEFAULTCAPACITY_EMPTY_ELEMENTDATA是无参的构造方法时候对elementData属性进行的初始化赋值。
LinkedList内部维护了头尾元素的指针,因为是链表,所以不需要扩容。
每个Node节点中维护了上一个元素的指针和下一个元素的指针。构造方法参数为上一个节点,新元素、下一个节点。
LinkedList根据索引获取元素的时候会根据该索引值是否大于链表长度的一半来区分是从头节点还是从尾节点开始遍历查找,从而提高查询效率。