信息发布→ 登录 注册 退出

Java基础之容器LinkedList

发布时间:2026-01-11

点击量:
目录
  • 一、LinkedList的整体结构
    • 1.1、LinkedList的继承关系
    • 1.2、LinkedList的结构
      • 1.2.1 Node的结构
  •  二、源码分析
    • 2.1、添加元素
      • 2.2、删除元素
        • 2.3、迭代器
        • 三、总结

          一、LinkedList的整体结构

          1.1、LinkedList的继承关系

          • public class LinkedList<E> extends AbstractSequentialList <E> implements List<E>, Deque<E>
          • LinkedList具备AbstractSequentialList的特点:AbstractSequentialList 只支持按次序访问,而不像 AbstractList 那样支持随机访问
          • LinkedList具备List的特点
          • LinkedList具备Deque的特点:Deque是一个线性collection,支持在两端插入和移除元素

          1.2、LinkedList的结构

          //元素个数
          transient int size = 0;
          //第一个元素指针
          transient Node<E> first;
          //最后一个元素指针
          transient Node<E> last;
          //Node节点的结构
          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;
              }
          }
          

          1.2.1 Node的结构

          LinkedList结构


          LinkedList特点

          1.LinkedList是通过双链表去实现的。

          2.LinkedList不存在容量不足的问题,因为是链表。

          3.LinkedList实现了Deque,而Deque接口定义了在双端队列两端访问元素的方法,所以LinkedList可以作为FIFO(先进先出)的队列;LinkedList可以作为LIFO(后进先出)的栈

           二、源码分析

          2.1、添加元素

          //添加元素
          public boolean add(E e) {
              //默认调用,尾部添加元素的方法
              linkLast(e);
              return true;
          }
          //尾部添加元素
          void linkLast(E e) {
              //记录当前尾部元素
              final Node<E> l = last;
              //创建一个新的Node节点 ,prev是当前的尾节点,next指向null
              final Node<E> newNode = new Node<>(l, e, null);
              //将last设置为新节点
              last = newNode;
              //判断当前尾部节点是否为null
              if (l == null)
                  //当前尾部节点为null,就挂到头结点上
                  first = newNode;
              else
                  //当前尾部节点不为null,就将新建的Node挂到当前last节点的next指针上
                  l.next = newNode;
              //元素的个数+1
              size++;
              //LinkedList修改记录+1
              modCount++;
          }
          

          新增元素add()方法默认是尾部追加,核心就是将新建的Node节点追加到当前last节点的next指针上 ,伪代码:

          Node newNode=new Node();
          newNode.prev=last;
          last.next=newNode;
          last=newNode;
          last.next=null;

          addFirst:首部追加

          public void addFirst(E e) {
              linkFirst(e);
          }
          //头部追加
          private void linkFirst(E e) {
              //记录当前首部元素
              final Node<E> f = first;
              //新建一个Node节点
              final Node<E> newNode = new Node<>(null, e, f);
              //首部元素指向新建的节点
              first = newNode;
              //判断当前首部指针是否为null
              if (f == null)
                  //当前首部指针为null,就把新建的节点挂到last指针上
                  last = newNode;
              else
                  //当前首部指针不为null,就把新建的节点挂到,当前first指针指向元素的prev指针上
                  f.prev = newNode;
              //元素个数+1
              size++;
              //LinkedList修改记录+1
              modCount++;
          }
          

          首部追加的逻辑与尾部追加基本相同,伪代码:

          Node newNode=new Node();
          newNode.next=first;
          first.prev=newNode;
          first=newNode;
          first.prev=null;(也可以:newNode.prev=null)

          指定位置添加元素:add(int index, E element):

          public void add(int index, E element) {
              //检查要插入的位置是否合法
              checkPositionIndex(index);
              //如要插入的位置在最后,直接调用linkLast()
              if (index == size)
                  linkLast(element);
              else
                  //如要插入的位置不在最后,就先查找再插入
                  linkBefore(element, node(index));
          }
           
          //查找要插入元素的位置
          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;
              }
          }
          //将新建的元素插入到查找的元素前面
          void linkBefore(E e, Node<E> succ) {
              // assert succ != null;
              final Node<E> pred = succ.prev;
              final Node<E> newNode = new Node<>(pred, e, succ);
              succ.prev = newNode;
              if (pred == null)
                  first = newNode;
              else
                  pred.next = newNode;
              size++;
              modCount++;
          }
          

          LinkedList是一个双向链表,他只记录了头部和尾部位置,如果我们要指定位置插入,他会这么做:

          1.先遍历查找出要插入的元素位置,然后再插入;查找方式是根据 index < (size >> 1) 判断结果,决定是从头遍历,还是从尾部遍历,这种遍历方式类似于二分查找(只在第一层循环二分)

          2.新建一个Node节点,插入到查找出来的元素的前面

          由此可知为何链表对随机位置读写是不合适的;他的时间复杂度=O(n/2) ,如果n很大,我们一般就认为他的时间复杂度=O(n)

          2.2、删除元素

          //重写List的remove()
          public boolean remove(Object o) {
              if (o == null) {
                  //如果要删除的元素null元素,从头开始查找这个null元素
                  for (Node<E> x = first; x != null; x = x.next) {
                      if (x.item == null) {
                          unlink(x);
                          return true;
                      }
                  }
              } else {
                   //如果要删除的元素不null元素,从头开始查找这个非null元素
                  for (Node<E> x = first; x != null; x = x.next) {
                      if (o.equals(x.item)) {
                          unlink(x);
                          return true;
                      }
                  }
              }
              return false;
          }
          //执行删除逻辑,实质就是打断改元素与链表的引用关系
          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;
              //判断 x->prev 节点是否为null,为null就是删除头结点 
              if (prev == null) {
                  first = next;
              } else {
                  //将 x->prev节点的next指针指向x节点的下一个节点
                  prev.next = next;
                  //将 x->prev 指针,设置为null(断开引用关系)
                  x.prev = null;
              }
               //判断 x->next 节点是否为null,为null就是删尾部结点 
              if (next == null) {
                  last = prev;
              } else {
                  //将x->next节点的prev指针指向x->prev
                  next.prev = prev;
                  //将 x->next指针,设置为null(断开引用关系)
                  x.next = null;
              }
              //将x的值设置为null
              x.item = null;
              //集合大小-1
              size--;
              //集合的修改记录-1
              modCount++;
              return element;
          }
          

          这里我们看到LinkedList重写了List的remove方法,整个删除逻辑也是先查找再删除,时间复杂度O(n),如果是删除首部元素时间复杂度=O(1),若要删除尾部元素请使用removeLast( ) 

          • LinkedLis删除首部元素:removeFirst()
          • LinkedLis删除尾部元素:removeLast()
          • LinkedLis首部出队:pollFirst( ) ,队列的特点
          • LinkedLit尾部出队:pollLast( ),队列的特点

          2.3、迭代器

          Iterator迭代器只能是从头往尾迭代,而LinkedList是双向链表,他还可以从尾往头部迭代,JAVA提供了一个新的迭代器接口:

          public interface ListIterator<E> extends Iterator<E>{
              //判断是否存在下一个元素
              boolean hasNext();
              //获取下一个元素
              E next();
              //判断是否还有前一个元素
              boolean hasPrevious();
              //获取前一个元素
              E previous();
          }
          

          LinkedList实现该接口:

          private class ListItr implements ListIterator<E> {
              private Node<E> lastReturned;//上一次next() 或者 previous()的元素
              private Node<E> next;//lastReturned->next 指向的元素
              private int nextIndex;//下一个元素的位置
              private int expectedModCount = modCount;
          }
          

          LinkedList从前往后遍历:

          //是否存在下一个元素
          public boolean hasNext() {
              return nextIndex < size;
          }
          public E next() {
              //检查集合的版本
              checkForComodification();
              if (!hasNext())
                  throw new NoSuchElementException();
              //lastReturned赋值上次next
              lastReturned = next;
              //next赋值为上次next->next
              next = next.next;
              //下一个元素的位置
              nextIndex++;
              return lastReturned.item;
          }
          

          LinkedList从后往前遍历:

          //判断是否到头了
          public boolean hasPrevious() {
              return nextIndex > 0;
          }
          //从尾部往头部取数据
          public E previous() {
              checkForComodification();
              if (!hasPrevious())
                  throw new NoSuchElementException();
              // next==null:第一次遍历取尾节点(last),或者上一次遍历时把尾节点删除掉了
              // next!=null:已经发生过遍历了,直接取前一个节点即可(next.prev)
              lastReturned = next = (next == null) ? last : next.prev;
              //遍历的指针-1
              nextIndex--;
              return lastReturned.item;
          }
          

          迭代器删除元素:

          public void remove() {
              checkForComodification();
              // lastReturned 是本次迭代需要删除的值
              // lastReturned==null则调用者没有主动执行过 next() 或者 previos(),二直接调remove()
              // lastReturned!=null,是在上次执行 next() 或者 previos()方法时赋的值
              if (lastReturned == null)
                  throw new IllegalStateException();
              //保存将当前要删除节点的下一个节点(如果是从尾往头遍历,该值永远是null)
              Node<E> lastNext = lastReturned.next;
              //删除当前节点
              unlink(lastReturned);
           
              // next == lastReturned:从尾到头递归顺序,并且是第一次迭代,并且要删除最后一个元素的情况下,
              // previous() 方法里面设置了 lastReturned = next = last,所以 next 和 lastReturned会相等
              if (next == lastReturned)
                  next = lastNext;
              else
                  nextIndex--;
              lastReturned = null;
              expectedModCount++;
          }
          

          三、总结

          LinkedList底层数据结构是双向链表,所以他更适合顺序操作,由于他继承了Deque接口,同时他具有队列的性质,非线程安全的集合

          在线客服
          服务热线

          服务热线

          4008888355

          微信咨询
          二维码
          返回顶部
          ×二维码

          截屏,微信识别二维码

          打开微信

          微信号已复制,请打开微信添加咨询详情!