JDK17源码系列-LinkedList源码解读

JDK17源码系列-LinkedList源码解读

1.LinkedList类图结构

2.存储结构

LinkedList与ArrayList存储结构不同,它是一个双向的链表,主要由其内部类Node维护链表,因此它不需要连续的存储空间,空间利用率较高

LinkedList类实现了List、Deque 、Cloneable、Serializable 接口

List : 表明它是一个列表,支持添加、删除、查找等操作,并且可以通过下标进行访问。

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

Cloneable :Cloneable 注解是一个标记接口,我们点进去会发现它并没有任何方法。此接口表明LinkedList具有拷贝能力,可以进行深拷贝或浅拷贝操作 。

3.两个重要属性:头结点与尾节点

java 复制代码
transient Node<E> first; // 首节点
transient Node<E> last;  // 尾节点

4.链表节点存储结构

java 复制代码
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;
    }
}

5.构造函数

  • public LinkedList() 空构造函数
java 复制代码
 public LinkedList() {}
  • public LinkedList(Collection<? extends E> c) 初始化一个包含指定集合的链表
java 复制代码
public LinkedList(Collection<? extends E> c) {
   this(); // 调用空的构造函数创建链表
   addAll(c); // 将指定集合添加到空链表中
}

6.添加元素

  • public boolean add(E e) 添加元素 ,默认使用尾插法
java 复制代码
public boolean add(E e) {
    linkLast(e);
    return true;
}
  • public void add(int index, E element) 在指定位置插入元素
java 复制代码
public void add(int index, E element) {
   checkPositionIndex(index); // 检查索引位置是否合法

   if (index == size)  // 如果索引位置为队尾,则尾插法
       linkLast(element);
   else
       linkBefore(element, node(index)); // 否则使用头插法
}
  • public void addFirst(E e) 添加头节点
java 复制代码
public void addFirst(E e) {
   linkFirst(e);
}
  • public void addLast(E e) 插入尾节点
java 复制代码
public void addLast(E e) {
    linkLast(e);
}
  • public boolean addAll(Collection<? extends E> c) 添加指定集合
java 复制代码
public boolean addAll(Collection<? extends E> c) {
  return addAll(size, c);
}
  • public boolean addAll(int index, Collection<? extends E> c) 在指定索引位置添加集合,并返回是否添加成功
java 复制代码
	checkPositionIndex(index); // 检查索引位置是否合法
     Object[] a = c.toArray();  // 将指定集合c转换为数组
      int numNew = a.length;
      if (numNew == 0) // 如果指定集合为空,则返回false
          return false;

      Node<E> pred, succ;
      if (index == size) { // 如果当前索引在尾节点位置,则将尾节点赋值给临时前向节点pred;
          succ = null;
          pred = last; 
      } else {  // 否则获取index索引的节点并将该节点的前向节点赋值给临时前向节点pred
                // 如果index为0,则succ为头结点,此时pred为空
          succ = node(index);
          pred = succ.prev;
      }
	
	  // 遍历数组a(即遍历集合c)	
      for (Object o : a) { 
          @SuppressWarnings("unchecked") E e = (E) o;
          Node<E> newNode = new Node<>(pred, e, null);
          if (pred == null) // 如果临时前向节点pred为空, 则将新建节点作为头节点
              first = newNode;
          else
              pred.next = newNode; 否则将临时前向节点pred的下个节点指向新建节点
          pred = newNode; // 移动前向节点pred为新建节点
      }

      if (succ == null) { // 如果索引位置节点succ为空,则将尾节点赋值为临时的前向节点
          last = pred;
      } else {
          pred.next = succ; // 否则pred的下个节点为索引位置节点succ
          succ.prev = pred; // 索引位置节点succ的前向节点为pred
      }

      size += numNew;
      modCount++;
      return true;
}
  1. 获取元素
  • public E get(int index) 根据索引值获取元素
java 复制代码
public E get(int index) {
    checkElementIndex(index); // 检查索引位置是否合法
    return node(index).item; // 返回索引位置节点中存储的值
}
  • public E getFirst() 获取列表首元素
java 复制代码
public E getFirst() {
    final Node<E> f = first; // 获取首节点
    if (f == null)
        throw new NoSuchElementException(); // 如果为空,则抛出异常
    return f.item; // 返回首节点存储的值
}
  • public E getLast() 获取尾节点
java 复制代码
public E getLast() {
    final Node<E> l = last;  // 获取尾节点
    if (l == null)
        throw new NoSuchElementException(); //如果尾节点为空,则抛出异常
    return l.item; // 获取尾节点存储的值
}
  1. 删除元素
  • public boolean remove(Object o) 删除指定元素,并返回是否删除成功
java 复制代码
public boolean remove(Object o) {
    // 如果被删除的元素为空,遍历链表,找到存储值为空的节点,释放
    if (o == 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;
}
  • public E removeFirst() 删除头结点
java 复制代码
public E removeFirst() {
    final Node<E> f = first;  
    if (f == null) // 如果头节点为空,抛出异常
        throw new NoSuchElementException();
    return unlinkFirst(f); //释放头节点
}
  • public E removeLast() 删除尾节点
java 复制代码
public E removeLast() {
   final Node<E> l = last;
   if (l == null)
       throw new NoSuchElementException(); // 如果尾节点为空,则抛出异常
   return unlinkLast(l); // 释放尾节点
}

public E set(int index, E element) 更新指定位置节点存储的,并返回该节点存储的原值

java 复制代码
public E set(int index, E element) {
    checkElementIndex(index); // 检查索引位置是否合法
    Node<E> x = node(index); 
    E oldVal = x.item;
    x.item = element;
    return oldVal;
}
  1. 节点的连接与释放操作
  • private void linkFirst(E e) 连接头节点
java 复制代码
// 头插法 
private void linkFirst(E e) {
    final Node<E> f = first; 
    final Node<E> newNode = new Node<>(null, e, f); // 创建新节点
    first = newNode; // 将原头结点赋值为新节点
    if (f == null) // 如果原头节点为空,则表示空链表
        last = newNode;  // 将原尾节点指向新建节点
    else
        f.prev = newNode; // 将原头结点的前向指针指向新建节点
    size++; // 链表元素个数+1
    modCount++; // 链表修改次数+1
}
  • void linkLast(E e) 连接尾节点
java 复制代码
// 尾插法
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++;   // 链表元素个数+1
    modCount++; // 链表修改次数+1
}
  • void linkBefore(E e, Node succ) 在指定节点前插入新节点
java 复制代码
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++;
}
  • E unlink(Node x) 释放非空节点
java 复制代码
E unlink(Node<E> x) {
    // assert x != null;
    final E element = x.item;   // 获取节点x的元素值
    final Node<E> next = x.next; // 获取节点x的后向节点
    final Node<E> prev = x.prev; // 获取节点x的前向节点

    if (prev == null) { // 如果前向节点为空
        first = next; // 则将链表的头节点设置为x的后向节点
    } else {
        // 如果x的前向节点不为空,则x的前向节点的后向节点指向x的后向节点
        // x的前向节点设置为空
        prev.next = next;
        x.prev = null;
    }
	// 如果x的后向节点为空,则将链表的尾节点指向x的前向节点
    if (next == null) {
        last = prev;
    } else {
        // 否则 x的后向节点的前向节点指向x的前向节点,x的后向节点设置为空,即释放了x
        next.prev = prev;
        x.next = null;  
    }

    x.item = null; //x存储的元素设置为空
    size--;
    modCount++;
    return element;
}
  • private E unlinkFirst(Node f) 释放链表的头节点
java 复制代码
private E unlinkFirst(Node<E> f) {
    // assert f == first && f != null;
    final E element = f.item; // 获取头节点的值
    final Node<E> next = f.next; 获取头节点的下个节点
    f.item = null; // 将头节点的元素以及下个节点置空
    f.next = null; // help GC
    first = next; // 将链表的头节点设置为原头节点的下个节点
    if (next == null) // 如果原链表的头节点下个节点为空(即链表中只存储一个节点),释放头节点后将尾节点置空
        last = null;
    else
        next.prev = null;// 否则将原头节点的后向节点的前向节点置空
    size--; // 链表大小-1
    modCount++;
    return element; // 返回被删除的头节点中存储的元素值
}
  • private E unlinkLast(Node l) 释放尾节点
java 复制代码
private E unlinkLast(Node<E> l) {
   // assert l == last && l != null;
    final E element = l.item; // 获取尾节点存储的值
    final Node<E> prev = l.prev; // 获取尾节点l的前向节点
    l.item = null;   // 将尾节点l存储的值以及前向节点置空
    l.prev = null; // help GC
    last = prev;   // 将尾节点的前向节点设置为链表新的尾节点
    if (prev == null)  // 如果原尾节点l的前向节点为空(链表中仅存储一个节点),释放尾节点l后将头节点置空
        first = null;
    else
        prev.next = null; // 如果原尾节点的前向节点不为空,则将其后向节点置空(因为它已经成为链表新的尾节点)
    size--;
    modCount++;
    return element;
}
  1. 查找元素索引操作
  • public int indexOf(Object o) 查找元素在链表中的位置
java 复制代码
public int indexOf(Object o) {
   int index = 0;
    if (o == null) { // 如果查找元素为空,则遍历链表,查找存储元素为空的节点的索引位置
        for (Node<E> x = first; x != null; x = x.next) {
            if (x.item == null)
                return index;
            index++;
        }
    } else {
       // 如果查找元素不为空,则遍历链表,查找存储值与查找值相等的节点的索引,并返回             
        for (Node<E> x = first; x != null; x = x.next) {
            if (o.equals(x.item))
                return index;
            index++;
        }
    }
    return -1;// 如果没找到返回-1
}
  • public int lastIndexOf(Object o) 查找指定元素在链表中最后一次出现的索引位置
java 复制代码
public int lastIndexOf(Object o) {
    int index = size; //将index设置为链表长度
    // 如果查找元素为空,则倒序遍历链表。查找存储元素为空的节点的索引值
    if (o == null) {
        for (Node<E> x = last; x != null; x = x.prev) {
            index--;
            if (x.item == null)
                return index;
        }
    } else {
        // 如果查找元素不为空,则倒序遍历链表。查找存储元素与查找元素想的节点的索引值
        for (Node<E> x = last; x != null; x = x.prev) {
            index--;
            if (o.equals(x.item))
                return index;
        }
    }
    return -1; // 如果没找到,则返回-1
}
  1. 队列的操作
  • public E peek() 获取头节点存储的元素值
java 复制代码
public E peek() {
    final Node<E> f = first;
    return (f == null) ? null : f.item; // 如果头节点为空,则返回空,否则返回头节点存储的值
}
// 这个方法也是获取链表的头节点,不同的是如果链表为空,它会抛出NoSuchElementException异常
public E element() {  
    return getFirst();
}
public E peekFirst() { 
  final Node<E> f = first;
   return (f == null) ? null : f.item;  // 获取头结点元素值
}
  • public E poll() 出队操作,并移动头节点位置
java 复制代码
public E poll() {
    final Node<E> f = first;
    return (f == null) ? null : unlinkFirst(f); // 返回链表头节点,并释放头节点
}
public E pollFirst() {
   final Node<E> f = first;
    return (f == null) ? null : unlinkFirst(f); // 从队头出队,并释放头节点
}
// 这个也是出队操作,并且也需要释放头节点
public E remove() {
   return removeFirst();
}
  • public E peekLast() 从队尾出队,不释放节点
java 复制代码
public E peekLast() {
    final Node<E> l = last;
    return (l == null) ? null : l.item;
}
// 从队尾出队,但是释放了链表节点
public E pollLast() {
    final Node<E> l = last;
    return (l == null) ? null : unlinkLast(l);
}
  • public boolean offer(E e) 向队尾添加元素
java 复制代码
public boolean offer(E e) {
	return add(e);
}
public boolean offerLast(E e) {
    addLast(e);
    return true;
}
  • public boolean offerFirst(E e) 向队头插入元素
java 复制代码
public boolean offerFirst(E e) {
    addFirst(e);
    return true;
}
  • public void push(E e) 入队操作
java 复制代码
public void push(E e) {
  addFirst(e);
}
  • public E pop() 出队操作
java 复制代码
public E pop() {
   return removeFirst();
}
  1. 清空链表操作
  • public void clear() 清空链表
java 复制代码
// 清空每个节点,清空头尾节点
public void clear() {
	for (Node<E> x = first; x != null; ) {
	    Node<E> next = x.next;
	    x.item = null;
	    x.next = null;
	    x.prev = null;
	    x = next;
	}
	first = last = null;
	size = 0;
	modCount++;
}
  1. 链表转数组
  • public Object[] toArray()
java 复制代码
public Object[] toArray() {
    Object[] result = new Object[size];
    int i = 0;
    // 遍历链表,为数组赋值
    for (Node<E> x = first; x != null; x = x.next)
        result[i++] = x.item;
    return result;
}
  • public T[] toArray(T[] a)
java 复制代码
public <T> T[] toArray(T[] a) {
    // 如果指定数组长度小于链表大小,则通过反射重新生成一个长度与链表大小一致的数组
    if (a.length < size)
        a = (T[])java.lang.reflect.Array.newInstance(
                            a.getClass().getComponentType(), size);
    int i = 0;
    Object[] result = a;
    // 遍历链表为数组元素赋值
    for (Node<E> x = first; x != null; x = x.next)
        result[i++] = x.item;

    // 如果指定数组长度大于链表大小,则将索引位置为size的数组元素置空
    if (a.length > size)
        a[size] = null;

    return a;
}
  1. 链表序列化与反序列化
  • private void writeObject(java.io.ObjectOutputStream s) 序列化调用
java 复制代码
private void writeObject(java.io.ObjectOutputStream s)throws java.io.IOException {
  // Write out any hidden serialization magic
    s.defaultWriteObject();

    // Write out size
    s.writeInt(size); // 将size序列化

    // Write out all elements in the proper order.
    for (Node<E> x = first; x != null; x = x.next)
        s.writeObject(x.item); // 将每个节点序列话
}

示例:

生成了序列化文件

  • private void readObject(java.io.ObjectInputStream s) 反序列化调用
java 复制代码
private void readObject(java.io.ObjectInputStream s)throws java.io.IOException,ClassNotFoundException {
    // Read in any hidden serialization magic
    s.defaultReadObject();

    // Read in size
    int size = s.readInt(); // 反序列化链表大小

    // Read in all elements in the proper order.
    for (int i = 0; i < size; i++)
        linkLast((E)s.readObject()); // 发序列化每个节点
}

示例:

打印反序列化结果

相关推荐
Legendary_00814 分钟前
LDR6020驱动的Type-C接口显示器解决方案
c语言·开发语言·计算机外设
cooldream200915 分钟前
SpringMVC 执行流程详解
java·spring·springmvc
redemption_217 分钟前
SpringMVC-01-回顾MVC
java
techdashen19 分钟前
Go context.Context
开发语言·后端·golang
凡人的AI工具箱21 分钟前
40分钟学 Go 语言高并发:Select多路复用
开发语言·后端·架构·golang
redemption_221 分钟前
Spring-02-springmvc
java
GGBondlctrl23 分钟前
【Spring MVC】初步了解Spring MVC的基本概念与如何与浏览器建立连接
java·spring boot·spring mvc·requestmapping·restcontroller
ModelBulider26 分钟前
SpringMVC应用专栏介绍
java·开发语言·后端·spring·springmvc
恬淡虚无真气从之28 分钟前
go 结构体方法
开发语言·后端·golang
licy__29 分钟前
Python BeautifulSoup 常用语句详解
开发语言·python·beautifulsoup