写点什么

Java 集合篇之深入解析 LinkedList

作者:EquatorCoco
  • 2024-02-18
    福建
  • 本文字数:2823 字

    阅读完需:约 9 分钟

写在开头


作为 ArrayList 的同门师兄弟,LinkedList 的师门地位逊色不少,除了在做算法题的时候我们会用到它之外,在实际的开发工作中我们极少使用它,就连它的创造者都说:“I wrote it,and I never use it”,想想颇有点好笑,但这并不影响我们去学习它,个人认为它底层的链表逻辑对于我们代码思想的培养还是挺有帮助的。



源码解析


看过 build 哥文章的同学应该都知道,俺喜欢通过源码去学习和分析对象或代码逻辑,因此,话不多说,直接上源码!


public class LinkedList<E>    extends AbstractSequentialList<E>    implements List<E>, Deque<E>, Cloneable, java.io.Serializable{  //...}
复制代码


如上为 JDK8 中 LinkedList 的继承实现关系,通过这些关系我们可以大致分析出它所具备的特性:


  1. 实现 List 接口 表明它是一个列表,支持添加、删除、查找等操作,并且可以通过下标进行访问;

  2. Deque 继承自 Queue 接口,具有双端队列的特性,支持从两端插入和删除元素,方便实现栈和队列等数据结构;

  3. Cloneable :表明它具有拷贝能力,可以进行深拷贝或浅拷贝操作;

  4. Serializable : 表明它可以进行序列化操作,也就是可以将对象转换为字节流进行持久化存储或网络传输,非常方便。


LinkedList 提供了非常多的方法供我们使用,继续阅读源码可以看到


// 在链表尾部插入元素public boolean add(E e) {    linkLast(e);    return true;} // 在链表指定位置插入元素public void add(int index, E element) {    // 下标越界检查    checkPositionIndex(index);     // 判断 index 是不是链表尾部位置    if (index == size)        // 如果是就直接调用 linkLast 方法将元素节点插入链表尾部即可        linkLast(element);    else        // 如果不是则调用 linkBefore 方法将其插入指定元素之前        linkBefore(element, node(index));} // 将元素节点插入到链表尾部void linkLast(E e) {    // 将最后一个元素赋值(引用传递)给节点 l    final Node<E> l = last;    // 创建节点,并指定节点前驱为链表尾节点 last,后继引用为空    final Node<E> newNode = new Node<>(l, e, null);    // 将 last 引用指向新节点    last = newNode;    // 判断尾节点是否为空    // 如果 l 是null 意味着这是第一次添加元素    if (l == null)        // 如果是第一次添加,将first赋值为新节点,此时链表只有一个元素        first = newNode;    else        // 如果不是第一次添加,将新节点赋值给l(添加前的最后一个元素)的next        l.next = newNode;    size++;    modCount++;} // 在指定元素之前插入元素void linkBefore(E e, Node<E> succ) {    // assert succ != null;断言 succ不为 null    // 定义一个节点元素保存 succ 的 prev 引用,也就是它的前一节点信息    final Node<E> pred = succ.prev;    // 初始化节点,并指明前驱和后继节点    final Node<E> newNode = new Node<>(pred, e, succ);    // 将 succ 节点前驱引用 prev 指向新节点    succ.prev = newNode;    // 判断尾节点是否为空,为空表示当前链表还没有节点    if (pred == null)        first = newNode;    else        // succ 节点前驱的后继引用指向新节点        pred.next = newNode;    size++;    modCount++;}// 获取链表的第一个元素public E getFirst() {    final Node<E> f = first;    if (f == null)        throw new NoSuchElementException();    return f.item;} // 获取链表的最后一个元素public E getLast() {    final Node<E> l = last;    if (l == null)        throw new NoSuchElementException();    return l.item;} // 获取链表指定位置的元素public E get(int index) {  // 下标越界检查,如果越界就抛异常  checkElementIndex(index);  // 返回链表中对应下标的元素  return node(index).item;}
复制代码



更多的 API 方法可以参考:LinkedList全量方法


使用 LinkedList


在 Java 中我们写一个小测试代码来用一下 LinkedList 的增删改查


【代码示例 1】


  // 创建LinkedList集合  LinkedList link = new LinkedList();  // 1、添加元素  link.add("happy");  link.add("new");  link.offer("year"); // 向集合尾部追加元素  link.push("javabuild"); // 向集合头部添加元素  System.out.println(link); // 输出集合中的元素  // 2、获取元素  Object object = link.peek(); //获取集合第一个元素  System.out.println(object); // 输出集合中的元素  // 3、删除元素  link.removeFirst(); // 删除集合第一个元素  link.pollLast(); // 删除集合最后一个元素  System.out.println(link);
复制代码


输出:


[javabuild, happy, new, year]javabuild[happy, new]
复制代码


对比 ArrayList


  1. ArrayList 和 LinkedList 都是不同步的,也就是不保证线程安全;

  2. ArrayList 底层使用的是 Object 数组;LinkedList 底层使用的是双向链表数据结构;

  3. LinkedList 不支持高效的随机元素访问,而 ArrayList(实现了 RandomAccess 接口) 支持。

  4. ArrayList 存在扩容问题,LinkedList 不存在,直接放在集合尾部,修改指针即可;


提问:为什么 LinkedList 不支持高效的随机访问,或者说为什么不去实现 RandomAccess 接口?


我们看过 RandomAccess 接口的底层的同学知道,这个接口也是个标识性接口,只要实现了这个接口就意味着支持通过索引访问元素。由于 LinkedList 底层数据结构是链表,内存地址不连续,只能通过指针来定位,不支持随机快速访问,所以不能实现 RandomAccess 接口。但是!在 LinkedList 中依旧提供了 get(int index):获取链表指定位置的元素。


// 获取链表指定位置的元素public E get(int index) {  // 下标越界检查,如果越界就抛异常  checkElementIndex(index);  // 返回链表中对应下标的元素  return node(index).item;}
复制代码


源码中 get 方法实现通过位置获取元素的核心是 node(index)方法,我们跟进去继续看一下!


// 返回指定下标的非空节点Node<E> node(int index) {    // 断言下标未越界    // assert isElementIndex(index);    // 如果index小于size的二分之一  从前开始查找(向后查找)  反之向前查找    if (index < (size >> 1)) {        Node<E> x = first;        // 遍历,循环向后查找,直至 i == index        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 参数和 size 的 1/2 进行比较,小于则从链表头向后查找,否则从链表尾向前遍历查找,这与 ArrayList 中的 get(index)方法还是有本质上的区别!


文章转载自:https://www.cnblogs.com/JavaBuild/p/18018733

原文链接:JavaBuild

体验地址:http://www.jnpfsoft.com/?from=001

用户头像

EquatorCoco

关注

还未添加个人签名 2023-06-19 加入

还未添加个人简介

评论

发布
暂无评论
Java集合篇之深入解析LinkedList_Java_EquatorCoco_InfoQ写作社区