LinkedList总结

86 阅读5分钟

LinkedList框架图

img

LInkedList的结构

1.Collection 接口: Collection接口是所有集合类的根节点,Collection表示一种规则,所有实现了Collection接口的类遵循这种规则

2.List 接口: List是Collection的子接口,它是一个元素有序(按照插入的顺序维护元素顺序)、可重复、可以为null的集合

3.AbstractCollection 类: Collection接口的骨架实现类,最小化实现了Collection接口所需要实现的工作量

4.AbstractList 类: List接口的骨架实现类,最小化实现了List接口所需要实现的工作量

5.Cloneable 接口: 实现了该接口的类可以显示的调用Object.clone()方法,合法的对该类实例进行字段复制,如果没有实现Cloneable接口的实例上调用Obejct.clone()方法,会抛出CloneNotSupportException异常。正常情况下,实现了Cloneable接口的类会以公共方法重写Object.clone()

6.Serializable 接口: 实现了该接口标示了类可以被序列化和反序列化,具体的 查询序列化详解

7.Deque 接口: Deque定义了一个线性Collection,支持在两端插入和删除元素,Deque实际是“double ended queue(双端队列)”的简称,大多数Deque接口的实现都不会限制元素的数量,但是这个队列既支持有容量限制的实现,也支持没有容量限制的实现,比如LinkedList就是有容量限制的实现,其最大的容量为Integer.MAX_VALUE

8.AbstractSequentialList 类: 提供了List接口的骨干实现,最大限度地减少了实现受“连续访问”数据存储(如链表)支持的此接口所需的工作,对于随机访问数据(如数组),应该优先使用 AbstractList,而不是使用AbstractSequentailList类

基础属性及构造方法

基础属性

 public class LinkedList<E>
     extends AbstractSequentialList<E>
     implements List<E>, Deque<E>, Cloneable, java.io.Serializable
 {
     //长度
     transient int size = 0;
     //指向头结点
     transient Node<E> first;
     //指向尾结点
     transient Node<E> last;
 }
 ​

如上源码中为LinkedList中的基本属性,其中size为LinkedList的长度,first为指向头结点,last指向尾结点,Node为LinkedList的一个私有内部类,其定义如下,即定义了item(元素),next(指向后一个元素的指针),prev(指向前一个元素的指针)

 private static class Node<E> {
     //元素
     E item;
     //指向后一个元素的指针
     Node<E> next;
     //指向前一个元素的指针
     Node<E> prev;
 ​
     Node(Node<E> prev, E element, Node<E> next) {
         this.item = element;
         this.next = next;
         this.prev = prev;
     }
 }

那么假如LinkedList中的元素为["A","B","C"],其内部的结构如下图所示

image-20221109154503955

可以看出一个节点中包含三个属性,也就是上面源码中定义的属性,可以清晰的看出LinkedList底层是双向链表的实现

构造方法

在源码中,LinkedList主要提供了两个构造方法,

1.public LinkedList() :空的构造方法,啥事情都没有做

2.public LinkedList(Collection<? extends E> c) : 将一个元素集合添加到LinkedList中

底层实现

添加节点

 //在链表的最后添加元素
 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;
     else
         l.next = newNode;
     size++;
     modCount++;
 }

其实通过源码可以看出添加的过程如下:

  1. 记录当前末尾节点,通过构造另外一个指向末尾节点的指针l

  2. 产生新的节点:注意的是由于是添加在链表的末尾,next是为null的

  3. last指向新的节点

  4. 这里有个判断,我的理解是判断是否为第一个元素(当l==null时,表示链表中是没有节点的), 那么就很好理解这个判断了,如果是第一节点,则使用first指向这个节点,若不是则当前节点的next指向新增的节点

  5. size增加 例如,在上面提到的LinkedList["A","B","C"]中添加元素“D”,过程大致如图所示

    image-20221109154950372

删除节点

 //方法1.删除指定索引上的节点
 public E remove(int index) {
     //检查索引是否正确
     checkElementIndex(index);
     //这里分为两步,第一通过索引定位到节点,第二删除节点
     return unlink(node(index));
 }
 ​
 //方法2.删除指定值的节点
 public boolean remove(Object o) {
     //判断删除的元素是否为null
     if (o == null) {
         //若是null遍历删除
         for (Node<E> x = first; x != null; x = x.next) {
             if (x.item == null) {
                 unlink(x);
                 return true;
             }
         }
     } else {
         //若不是遍历删除 
         for (Node<E> x = first; x != null; x = x.next) {
             if (o.equals(x.item)) {
                 unlink(x);
                 return true;
             }
         }
     }
     return false;
 }

通过源码可以看出两个方法都是通过unlink()删除,在方法一种有个方法要介绍下,就是node(index)该方法的作用就是根据下标找到对应的节点,要是本人去写这个方法肯定是遍历到index找到对应的节点,而JDK提供的方法如下所示

首先确定index的位置,是靠近first还是靠近last

若靠近first则从头开始查询,否则从尾部开始查询,可以看出这样避免极端情况的发生,也更好的利用了LinkedList双向链表的特征

 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;
     }
 }

下面会详细介绍unlink()方法的源码,这是删除节点最核心的方法

 E unlink(Node<E> x) {
     // assert x != null;
     final E element = x.item;
     final Node<E> next = x.next;
     final Node<E> prev = x.prev;
     
     //删除的是第一个节点,first向后移动
     if (prev == null) {
         first = next;
     } else {
         prev.next = next;
         x.prev = null;
     }
     
     //删除的是最后一个节点,last向前移
     if (next == null) {
         last = prev;
     } else {
         next.prev = prev;
         x.next = null;
     }
 ​
     x.item = null;
     size--;
     modCount++;
     return element;
 }
  1. 获取到需要删除元素当前的值,指向它前一个节点的引用,以及指向它后一个节点的引用。
  2. 判断删除的是否为第一个节点,若是则first向后移动,若不是则将当前节点的前一个节点next指向当前节点的后一个节点
  3. 判断删除的是否为最后一个节点,若是则last向前移动,若不是则将当前节点的后一个节点的prev指向当前节点的前一个节点
  4. 将当前节点的值置为null
  5. size减少并返回删除节点的值

linkedList

  • LinkedList底层的数据结构哦是基于双向循环链表
  • 有序、可重复、非同步的
  • LinkedList经常使用的两种数据结构:栈、队列

push和add的区别

push:将元素添加到栈顶,等同于addFirst

add:将元素添加到队尾,等同于addLast

poppollpeek三者的区别:

  • pop和poll,删除并返回栈顶(队首)元素,而peek,仅返回栈顶元素
  • pop栈结构,后进先出,删除并返回第一个元素(栈顶),栈顶为null值时抛出异常
  • poll是队列结构,删除并返回第一个元素(队首),队首为null时,返回null

原文链接