按照从构造方法->常用API(增、删、改、查)的顺序来阅读源码,并会讲解阅读方法中涉及的一些变量的意义。
- ArrayList与LinkedList
ArrayList与LinkedList是List接口的两种不同的实现,ArrayList的增删效率低,但是改查效率高。
而LinkedList正好相反,增删由于不需要移动底层数组数据,其底层是链表实现的,只需要修改链表节点指针,所以效率较高。
而改和查,都需要先定位到目标节点,所以效率较低。 - Collection.toArray(); 。
这个方法很重要,不管是ArrayList、LinkedList 在批量add的时候,都会先转化成数组去做。 因为数组可以用for循环直接花式遍历。比较方便 高效
一. 定义
1 | public class LinkedList<E> |
- 实现 List 接口
能对它进行队列操作 - 实现 Deque 接口
即能将LinkedList当作双端队列使用 - 实现了Cloneable接口
即覆盖了函数clone(),能克隆 - 实现java.io.Serializable接口
支持序列化,能通过序列化去传输
二. 属性
主要有三个:
- size:当前有多少个节点
- first:第一个节点
- last:最后一个节点
1 | transient int size = 0; |
Node
双向链表。
1 | private static class Node<E> { |
三. 构造方法
1)无参构造方法
1 | public LinkedList() { |
什么都没做,表示初始化的时候size为0,first和last的节点都为空:
2)Collection对象作为入参
1 | public LinkedList(Collection<? extends E> c) { |
四. 增加—add addAll
添加一个元素(在末尾)–add(E e)
在尾部插入一个节点: add
add(E e)
Appends the specified element to the end of this list.
1 | public boolean add(E e) { |
linkLast(E e)
生成新节点 并插入到 链表尾部, 更新 last/first 节点。
只用维护前面的链子
1 | void linkLast(E e) { |
在指定位置添加一个元素–add(int index, E element)
1 | public void add(int index, E element) { |
linkBefore(E e, Node succ)
在succ节点前,插入一个新节点e
重新维护前后两条链子
1 | void linkBefore(E e, Node<E> succ) { |
在尾部批量增加—addAll(Collection<? extends E> c)
Appends all of the elements in the specified collection to the end of this list
1 | public boolean addAll(Collection<? extends E> c) { |
在尾部批量增加—addAll(int index, Collection<? extends E> c)
以size为插入下标,插入集合c中所有元素
- 判断加在队尾还是中间(为了找到插入后的前驱后继节点,在插入前的位置)
- 循环加入元素
2.1从前驱节点进行后接
2.2设置为前驱的后继
2.3当前节点设置为下一节点的前驱- - 判断加在队尾还是中间(为了判断last位置,和设置新的前驱后继)
1 | public boolean addAll(int index, Collection<? extends E> c) { |
根据index 查询出Node—node(int index)
Returns the (non-null) Node at the specified element index.
1 | Node<E> node(int index) { |
范围判定
1 | private void checkPositionIndex(int index) { |
1 | private boolean isPositionIndex(int index) { |
五. 删除—remove
- 按下标删,是先根据index找到Node,然后去链表上unlink掉这个Node
- 按元素删,会先去遍历链表寻找是否有该Node,考虑到允许null值,所以会遍历两遍,然后再去unlink它
删除头–remove()
1 | public E remove() { |
removeFirst()
1 | public E removeFirst() { |
unlinkFirst(f)
1 | private E unlinkFirst(Node<E> f) { |
根据索引位置删除元素–remove(int index)
Removes the element at the specified position in this list
1 | public E remove(int index) { |
unlink(Node x)
1 | E unlink(Node<E> x) { |
根据元素内容删除,只删除匹配的第一个
remove(Object o)
Removes the first occurrence of the specified element from this list, if it is present.
- null值要用==比较
1 | public boolean remove(Object o) { |
六. 查找—get
查询节点—get(int index)
Returns the element at the specified position in this list.
1 | public E get(int index) { |
查询下标—indexOf(Object o)
1 | public int indexOf(Object o) { |
7. 更新—set
将指定位置的元素更新为新元素
Replaces the element at the specified position in this list with the specified element.
- 注意返回值是oldValue
1 | public E set(int index, E element) { |
9. toArray()
new 一个新数组 然后遍历链表,将每个元素存在数组里,返回
Returns an array containing all of the elements in this list in proper sequence (from first to last element).
1 | public Object[] toArray() { |
10. LinkedList和 ArrayList的区别
- 增删效率比ArrayList高
底层数据结构是链表,增删只需要移动指针即可故时间效率较高。不需要批量扩容,也不需要预留空间,所以空间效率比ArrayList高。 - 查效率比ArrayList低
缺点就是需要随机访问元素时,时间效率很低,虽然底层在根据下标查询Node的时候,会根据index判断目标Node在前半段还是后半段,然后决定是顺序还是逆序查询,以提升时间效率。总体时间效率依然O(n)
11. 小总结
它的CRUD操作里,都涉及到根据index去找到Node的操作
- 链表批量增加,是靠for循环遍历原数组,依次执行插入节点操作
对比ArrayList是通过System.arraycopy完成批量增加的 - 通过下标获取某个node 的时候,会根据index处于前半段还是后半段进行一个折半,以提升查询效率
- 按下标删是先根据index找到Node,然后去链表上unlink掉这个Node
按元素删,会先去遍历链表寻找是否有该Node,如果有,去链表上unlink掉这个Node - 改也是先根据index找到Node,然后替换值。改不修改modCount。
- 查本身就是根据index找到Node。
参考文章
Java集合干货系列-(二)LinkedList源码解析
Java官方文档
LinkedList源码解析(JDK8)