ArrayList 与LinkedList 源码分析,比较
ArrayList , LinkedList都是 List接口的实现类, ArrayList 底层是一个Object数组, LinkedList是一个双向链表,他们都是线程不安全的
ArrayList
先看看变量吧:
elementData : ArrayList数据的存放位置, 一目了然,是一个Object数组, 修饰符 transtient 的作用是使得该变量不能再被序列化
DEFAULT_CAPACITY : ArrayList的默认容量,初始化时若没有显示指定容量, 默认为 10 , 在 ensureCapacityInternal 方法中设置默认容量
size : 当前该ArrayList的大小, 实际存储的数据量,注意与ellmentData数组的长度区别 , 是不一样的
modCount : 这是ArrayList的 父类AbstractList类中定义的变量,表示结构修改的次数, 当并发时, 若modCount不一致,触发快速失败
然后我们知道,java数组创建之后长度是固定的, 一直往数组里添加元素,数组下标会越界,这就需要对数组进行扩容
扩容时机: list的容量(Object数组的长度 ) < 需要存储的数据量。
下图代码中, minCapacity 指的是当前需要存储的数据量(size + 1),
扩容大小: 1.5倍, 如果新的数组大小大于 该ArrayList最大容量时,就更新为 MAX_ARRAY_SIZE。 下图关键代码 :
newCapacity = oldCapacity + (oldCapacity >> 1) , >> : 位运算,往右移动一位,
扩容方法: Arrays.copyOf() , 重新申请内存,再赋值给elementData,该方法使用 System类的arraycopy() (这是一个native方法)移动数据, 操作内存,整体移动数据,效率高,包括插入,删除操作的移动数据也是使用该arraycopy方法
LinkedList
底层是一个双向链表,节点 结构如下:next 指向下一个节点, prev指向前一个,
维护了两个引用,first 指向链表的第一个节点, last指向最后一个, 因为是链表 ,所以不存在扩容,直接插入即可
node(int index) 判断 index 靠近左边还是右边从更近的地方开始移动
ArrayList, LinkedList 查询效率比较:
ArrayList 是 Object数组实现的,可以直接通过下标随机查找, 时间复杂度 是O(1),
LinkedList 底层是双向链表, LinkedList 需要从 头或尾节点 移动到指定的节点,时间复杂度为 O (n), 所以 ArrayList的查询效率高
插入,删除效率比较:
具体需要分情况讨论: (请注意,这里的效率比较,都默认数据量很大的情况)
操作尾部, ArrayList, LinkedList 效率差不多,都可以直接插入
操作首部,LinkedList 效率高 ,可以直接插入,主要时间开销是,生成节点的时间, ArrayList (Object数组)需要移动操作位置后的元素,扩容也需要时间
操作非首尾部元素时, ArrayList效率高 , 因为 ArrayList 虽然需要移动数据,但,使用的是 System.arraycopy() (native方法)操作内存,整体移动,效率高, LinkedList 需要从 头或尾节点 移动到指定的节点再操作,效率低, 并且数据量越大,差距越明显
for, 增加for(forEach),迭代器遍历效率比较:
ArrayList: 三种遍历方式的效率都差不多 ,时间复杂度都是 O(N)
LinkedList : 迭代器 、forEach遍历方式,效率比普通 for循环效率高很多,因为普通for,每次获取元素(get方法)都需要从头部或尾部,移动到指定的元素,这会产生 O(N)的时间复杂度,所以说,for循环遍历,时间复杂度达到了 O(N * N),而 迭代器、forEach方式遍历时间复杂度为O(N)