一文搞定LinkedList

LinkedList介绍

LinkedList接口的链接列表实现。实现所有可选的列表操作,并且允许所有元素(包括 null )。除了实现 List 接口外, LinkedList 类还为在列表的开头及结尾 get 、 remove 和 insert 元素提供了统一 的命名方法。这些操作允许将链接列表用作堆栈、队列或双端队列。

LinkedList详解

LinkedList的特点

  • 有序性存入和读取的顺序一致
  • 元素可以重复
  • 含有带索引的方法
  • 独有特点 : 数据结构是链表,可以作为栈、队列或者双端队列!

LnkedList是一个双向的链表结构,双向链表,结构如下

image-20211212115418172.png

数据结构详解

Linkedlist是链表源码如下:

public class LinkedList<E>{
    transient int size = 0;
    //双向链表的头结点
    transient Node<E> first;
    //双向链表的最后一个节点
    transient Node<E> last;
   //节点类【内部类】
   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是双向链表,在代码中是一个Node类。内部并没有数组的结构。双向链表肯定存在一个头节点和一个尾部节点。node节点类,是以内部类的形式存在于LinkedList中的。Node类都有两个成员变 量:

  • prev : 当前节点上一个节点,头节点的上一个节点是null
  • next : 当前节点下一个节点,尾结点的下一个节点是null

链表数据结构的特点 : 查询慢,增删快!

  • 链表数据结构基本构成,是一个node,每个node类中,有上一个节点prev和下一个节点next
  • 链表一定存在至少两个节点,first和last节点 如果LinkedList没有数据,first和last都是为null

LinkedList的数据结构的特点查询慢、增删快。

image-20211216133300420.png

删除的话和插入类似 例如删除B,先查到B节点的上一个节点A和下一个节点C,然后把A节点的下一个节点指向C,把B的上一个节点置空 把C节点的上一个节点指向A,B的下一个节点置空,最后把B节点中的数据置为空 查询的慢则是需要一个节点一个节点的往后查知道查到自己想要的节点,数据量越大效率越低,下面咱们从源码层面进行分析

添加元素

public boolean add(E e) {
 //连接到链表的末尾
  linkLast(e);
  return true;
}
//连接到最后一个节点
void linkLast(E e) {
  //将末尾节点赋值给l
  final Node<E> l = last;
  //创建一个新节点,新节点的上一个节点为l,数据为e,下一个节点为null(因为是最后一个节点所以下一个节点为null)
  final Node<E> newNode = new Node<>(l, e, null);
  //将当前节点作为末尾节点
  last = newNode;
  //判断l节点是否为null
  if (l == null)
    //即是尾结点也是头节点
    first = newNode;
  else
    //之前的末尾节点的下一个节点设置为新增加的节点
    l.next = newNode;
  //当前集合的元素数量+1
  size++;
  //操作集合数+1。modCount属性是修改计数器
  modCount++;
}
复制代码

向链表指定位置添加元素

public void add(int index, E element) {
  //检查索引位是否符合要求
  checkPositionIndex(index);
//判断index是否等于元素的个数,如果等于则为最后一个元素
  if (index == size)
    linkLast(element);
  else
    //连接到指定节点的后面
    linkBefore(element, node(index));
}
////根据索引查询链表中节点!
Node<E> node(int index) {
  //判断索引是否小于已经存储元素个数的1/2
  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) {
  // 取出succ节点的前一个节点
  final Node<E> pred = succ.prev;
  //创建元素的节点 : 上一个节点,当前元素,下一个节点
  //则元素的上一个节点为 pred,下一个节点为succ
  final Node<E> newNode = new Node<>(pred, e, succ);
  //给succ的前一个节点重新赋值为新节点
  succ.prev = newNode;
  //判断succ的上一个节点是否为null,如果为null 则表示succ为头部节点
  if (pred == null)
    //当新创建的节点作为头部节点
    first = newNode;
  else
    //给succ的前一个节点的下一个节点重新赋值为新创建的节点
    pred.next = newNode;
  size++;
  modCount++;
}
复制代码

删除指定元素

public E remove(int index) {
  checkElementIndex(index);
  //删除元素节点,
 //node(index) 根据索引查到要删除的节点
 //unlink()删除节点
  return unlink(node(index));
}
//删除一个指定节点
E unlink(Node<E> x) {
  //获取要删除节点中的元素
  final E element = x.item;
  //获取要删除节点的下一个节点
  final Node<E> next = x.next;
  //获取要删除节点的下一个节点
  final Node<E> prev = x.prev;
  if (prev == null) {
    //如果为null,说明要删除节点为头部节点
    first = next;
  } else {
    //上一个节点的下一个节点改为要删除节点的下一个节点
    prev.next = next;
    //将要删除节点的上一个节点置空
    x.prev = null;
  }
  if (next == null) {
    //如果为null,说明要删除节点为尾部节点
    last = prev;
  } else {
    //要删除节点的下一个节点的上节点改为要删除节点的上一个节点
    next.prev = prev;
    //要删除节点的上一个节点置空
    x.next = null;
  }
//删除要删除节点内的元素 
  x.item = null;
  size--;
  modCount++;
  return element;
}
复制代码

查找元素

public E get(int index) {
  //验证节点是否存在
  checkElementIndex(index);
  //node(index) 获取节点
  return node(index).item;
}
复制代码

node(index) 放在已经在上面进行分析了,需要对节点遍历(不是遍历所有的节点,而是和所有节点的1/2节点进行比较,类似二分查找 )

猜你喜欢

转载自juejin.im/post/7042185792671711262