复习一下list集合知识,记录一下。
参考文章:https://blog.csdn.net/qq_36520235/article/details/82535044
ArrayList实现原理:
1、ArrayList是List接口的可变数组非同步实现,并允许包括null在内的所有元素。
2、底层使用数组实现。(默认初始化长度为10)
3、该集合是可变长度数组,数组扩容时,会将原数组中的元素copy一份到新的数组中,每次数组容量增长大约是其容量的1.5倍,这种操作代价很高。
4、采用Fail-Fash机制,通过记录modCount参数来实现,面对并发的修改时,迭代器很快就会完全失败,而不是冒着将来某个不确定时间发生任意不确定性为的风险。
5、remove方法会让下标到数组末尾的元素向前移动一个单位,并把最后一位的值置空,方便GC。
(关于ArrayList:https://www.iteye.com/blog/zhangshixi-674856)
在ArrayList和Vector中addAll()方法都使用到了System.arraycopy(a, 0, elementData, size, numNew)方法,此方法为native方法。
(关于native关键字:https://blog.csdn.net/zw6161080123/article/details/80628069)
LinkedList实现原理:
1、LinkedList是List接口的双向链表非同步实现,并允许包括null在内的所有元素。
2、底层的数据结构是基于双向链表的,该数据结构称为节点。
3、双向链表节点对应的类Node的实例,Node中包含成员变量:prev,next,item。prev代表上一个节点,next反之,item是该节点所包含的值。
4、它的查找是分两半查找,先判断index是在链表的哪一半,然后再去对应区域查找,这样最多只要遍历链表的一半节点即可找到,不是折半查找,只是缩小了一半查找范围。用node(int index)方法:
Node<E> node(int index) {
// assert isElementIndex(index);
// 判断插入的位置在链表前半段或者是后半段
if (index < (size >> 1)) { // 插入位置在前半段
Node<E> x = first;
for (int i = 0; i < index; i++) // 从头结点开始正向遍历
x = x.next;
return x; // 返回该节点
} else { // 插入位置在后半段
Node<E> x = last;
for (int i = size - 1; i > index; i--) // 从尾结点开始逆向遍历
x = x.prev;
return x; // 返回该节点
}
}
说明:在根据索引查找结点时,会有一个小优化,结点在前半段则从头开始遍历,在后半段则从尾开始遍历,这样就保证了只需要遍历最多一半结点就可以找到指定索引的结点。
(关于LinkedList:https://www.cnblogs.com/leesf456/p/5308843.html)
区别:
1、ArrayList底层数据结构为数组,LinkedList底层数据结构为链表,且是双向链表,即既有next也有previous。
2、对于随机访问,ArrayList优于LinkedList,因为LinkedList要移动指针。
3、对于新增和删除操作add和remove,LinkedList占优,因为ArrayList要移动数据。
区别解析:
1、对于二者,在列表末尾增加一个元素的开销是固定的。对于ArrayList而言,主要是在内部数组增加一项,指向所添加的元素,偶尔可能会导致对数组重新进行分配;对LinkedList而言,这个开销是统一的,分配一个内部Entry对象。
2、在ArrayList的中间插入或删除一个元素意味着该列表剩余的其他元素都将被移动;而在LinkedList的中间增删一个元素的开销固定。
3、LinkedList不支持高效的随即元素访问。
4、ArrayList的空间浪费主要体现在list列表的结尾预留一定的容量空间,而LinkedList的空间花费则体现在它的每个元素都需要消耗相当的空间。
===========================================================
如何遍历,使用for循环遍历LinkedList为什么效率低,LinkedList能使用索引访问么
1、通过查看源码发现,ArrayList实现了RandomAccess接口,而LinkedList没有实现。RandomAccess接口是空的,为标志接口(Maker)。只要实现该接口,list集合就能支持快速随机访问。
ArrayList实现了RandomAccess接口
LinkedList没有实现RandomAccess接口
RandomAccess接口是空的
2、通过查看Collections类中的binarySearch()方法,其中可以看到有instanceof来判断list是否实现了RandomAccess接口,进而判断执行indexedBinarySearch(list,key)或者iteratorBinarySearch(list,key)方法。
3、通过查看源代码,发现实现RandomAccess接口的List集合采用一般的for循环遍历,而未实现这接口则采用迭代器。
查看下indexedBinarySerach(list,key)方法源码:
查看下iteratorBinarySerach(list,key)方法源码:
ArrayList用for循环遍历比iterator迭代器遍历快,LinkedList用iterator迭代器遍历比for遍历快
总结:RandomAccess接口这个空架子的存在,是为了能够更好地判断集合是否ArrayList或者LinkedList,从而能够更好选择更优的遍历方式,提高性能!
LinkedList不能使用索引直接访问,因为LinkedList集合中索引为隐式的,查找效率低于ArrayList。
=================================================================================
Vector和ArrayList的对比
(关于Vector:https://blog.csdn.net/ljcitworld/article/details/78317273)
Vector的实现原理:
Vector支持有序可重复地存放单列数据。它底层实现和ArrayList类似,都是数组实现。
Vector的所有方法都是有synchronized关键字的,即每一个方法都是同步的,所以在使用起来效率会非常低,但是保证了线程安全;
区别:
(1) Vector几乎所有方法都是有synchronized关键字的,即每一个方法都是同步的;而ArrayList的全部方法都是非同步的,所以相对比Vector的效率会更高,且它是线程不安全的。
(2) ArrayList在每次扩容时都是增加当前容量的1.5倍,而Vector在扩容时都是增加当前容量的2倍。
Vector在增删改查等API上的实现都是和ArrayList类似甚至是相同的。通常在不用考虑线程安全问题时,java官方推荐使用ArrayList,因为它的效率更高;当线程不安全时,其实Collections类中提供了同步ArrayList的方法
public static <T> List<T> synchronizedList(List<T> list),它可以帮助我们同步ArrayList。当我查看这个方法实现如下
可知道,是Collections中提供了一个新的类SynchronizedList,它其实只是给ArrayList的每一个方法都添加了synchronized关键字,因此该方法其实与Vector的效率一致。