引言
在前面的文章中, 我们对 ArrayList 做了较为详细的源码解读, 今天将在这篇文章中继续对 LinkedList 的源码作出解读, 本文中的 LinkedList 是基于 jdk1.8.
LinkedList 底层分析
LinkedList 数据结构
如上图所示 LinkedList
底层是基于 双向链表
实现的, 其中 LinkedList 的头结点不存放数据, 仅存放指针.
常用字段
transient int size = 0;// 当前 LinkedList 对象中存储的数据数目
transient Node first;// 起始节点
transient Node last;// 结束节点
以上的三个字段全都使用了transient
关键字进行修饰, 因为该三个字段可能为空, 以免在自动序列化中引起错误使得序列化失败
常用方法
add(E e)
将元素插入 链表末尾
public boolean add(E e) { linkLast(e);// 将元素插入 ` 链表末尾 ` return true; } void linkLast(E e) { final Node<E> l = last;// 获取结束节点 final Node<E> newNode = new Node<>(l, e, null); last = newNode; if (l == null) first = newNode;// 若链表为空链表, 将新节点赋值给给 first 节点 else l.next = newNode; size++; modCount++; }
由源码可见每次插入数据都是移动指针,相比 ArrayList
的拷贝数组来说效率要高上不少。时间复杂度为 o(1)
add(int index, E element)
将元素插入 链表指定位置
public void add(int index, E element) { checkPositionIndex(index); if (index == size) linkLast(element); else linkBefore(element, node(index)); } Node<E> node(int index) { // assert isElementIndex(index); //判断index是否大于1/2的size,若小于1/size则从first结点开始向后遍历,否则从last节点开始向前遍历 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; } }
上述代码,利用了双向链表的结构特点 (可双向遍历),如果 index
离链表头比较近,就从节点头部遍历。否则就从节点尾部开始遍历。使用空间(双向链表的每个节点的前后指针所占用空间)来换取时间。
node()
会以O(n/2)
的时间复杂度去获取一个结点- 如果索引值大于链表大小的一半,那么将从尾结点开始遍历
这样的效率是非常低的,特别是当 index 越接近 size 的中间值时。
get(int index)
获取指定索引的元素
public E get(int index) { checkElementIndex(index); return node(index).item; } 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; } }
get()
方法和前面的 add(int index, E element)
一样, 需要调用到 node(int index)
方法进行查找.
peek()和 poll()
peek()
方法和 poll()
是如果 list 中的 开始节点
, 但 peek()
方法不会将节点从链表中移除, 而 poll()
方法会.
public E peek() { final Node<E> f = first; return (f == null) ? null : f.item; } public E poll() { final Node<E> f = first; return (f == null) ? null : unlinkFirst(f); }
其余方法如 writeObject(),``readObject()
方法与 ArrayList
中的方法类似, 此处不做过多介绍.
最后的思考题
现有一个LinkedList类型的list,其中含有顺序[1....100000]个元素(10w),现在要打印后10000个元素,如下操作方式会有什么方面的问题? for(int i = 90000; i<list.size(); i++){ System.out.println(list.get(i)); }
具体答案可以参考 LinkedList 下的错误操作, 这篇文章就这么结束了, 似乎写的有点水啊! 读者觉得有什么需要补充的欢迎在下面留言.
ps:由于之前不小心部署了两个博客系统,在这里出现了问题,所以我重新上传了一下这篇文章
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于