头图

概述

  1. LinkedList 继承自 AbstrackSequentialList 并实现了 List 接口以及 Deque 双向队列接口,因此 LinkedList 不但拥有 List 相关的操作方法,也有队列的相关操作方法。
  2. LinkedListArrayList 一样实现了序列化接口 SerializableCloneable 接口使其拥有了序列化和克隆的特性。
  3. 继承了AbstractSequentialList抽象类,在遍历的时候,推荐使用迭代器进行遍历。
但是只支持浅克隆,在LinkedList类中,其中的内部类Node并没有被克隆,只是调用了Object类中的clone方法进行可克隆。

LinkedList 双向链表实现及成员变量

核心组成:用来存储数据的结点,在LinkedList中设计成了内部类。

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 主要成员变量有下边三个:

//LinkedList 中的节点个数
transient int size = 0;

//LinkedList 链表的第一个节点
transient Node<E> first;

//LinkedList 链表的最后一个节点
transient Node<E> last;

构造方法

LinkedList的构造方法只提供了两个:

/**
 * 空参数的构造由于生成一个空链表 first = last = null
 */
 public LinkedList() {
 }

/**
 * 传入一个集合类,来构造一个具有一定元素的 LinkedList 集合
 * @param  c  其内部的元素将按顺序作为 LinkedList 节点
 * @throws NullPointerException 如果 参数 collection 为空将抛出空指针异常
 */
public LinkedList(Collection<? extends E> c) {
   this();
   addAll(c);
}

带参数的构造方法,调用 addAll(c) 这个方法,

public boolean addAll(Collection<? extends E> c) {
    return addAll(size, c);
}

实际上这方法调用了 addAll(size, c) 方法,

/**
 * 在 index 节点前插入包含所有 c 集合元素的节点。
 * 返回值表示是否成功添加了对应的元素.
 */
public boolean addAll(int index, Collection<? extends E> c) {
   // 查看索引是否满足 0 =< index =< size 的要求
   checkPositionIndex(index);
    // 调用对应 Collection 实现类的 toArray 方法将集合转为数组
   Object[] a = c.toArray();
   //检查数组长度,如果为 0 则直接返回 false 表示没有添加任何元素
   int numNew = a.length;
   if (numNew == 0)
       return false;
   // 保存 index 当前的节点为 succ,当前节点的上一个节点为 pred
   Node<E> pred, succ;
   // 如果 index = size 表示在链表尾部插入
   if (index == size) {
       succ = null;
       pred = last;
   } else {
       succ = node(index);
       pred = succ.prev;
   }
    
    // 遍历数组将对应的元素包装成节点添加到链表中
   for (Object o : a) {
       @SuppressWarnings("unchecked") E e = (E) o;
       Node<E> newNode = new Node<>(pred, e, null);
       //如果 pred 为空表示 LinkedList 集合中还没有元素
       //生成的第一个节点将作为头节点 赋值给 first 成员变量
       if (pred == null)
           first = newNode;
       else
           pred.next = newNode;
       pred = newNode;
   }
   // 如果 index 位置的元素为 null 则遍历数组后 pred 所指向的节点即为新链表的末节点,赋值给 last 成员变量
   if (succ == null) {
       last = pred;
   } else {
       // 否则将 pred 的 next 索引指向 succ ,succ 的 prev 索引指向 pred
       pred.next = succ;
       succ.prev = pred;
   }
   // 更新当前链表的长度 size 并返回 true 表示添加成功
   size += numNew;
   //fast-fail
   modCount++;
   return true;
}

addAll(c)在外部单独调用时,将指定集合的元素作为节点,添加到 LinkedList 链表尾部。

addAll(size, c) 可以将集合元素插入到指定索引节点。

对于 checkPositionIndex方法这里想顺带分析了,

LinkedList 中有两个方法用于检查角标越界,内部实现一样。

    private boolean isElementIndex(int index) {
        return index >= 0 && index < size;
    }



    private boolean isPositionIndex(int index) {
        return index >= 0 && index <= size;
    }

都是通过 index >= 0 && index <= size 判断。

LinkedList 的增删改查

LinkedList 可以方便的在头尾插入一个节点,而 add 方法默认在链表尾部添加节点:

添加节点


 public void addFirst(E e) {
    linkFirst(e);
 }


 public void addLast(E e) {
    linkLast(e);
 }
    

 public boolean add(E e) {
    linkLast(e);
    return true;
 }





/**
 * 在指定 index 位置插入节点
 */
public void add(int index, E element) {
   // 检查角标是否越界
   checkPositionIndex(index);
   // 如果 index = size 代表是在尾部插入节点
   if (index == size)
       linkLast(element);
   else
       linkBefore(element, node(index));
}

可以看到默认是“尾插”,有返回值。

linkXXX

我们可以看到 add 方法是有返回值的,这个可以注意下。

看来这一系方法都调用用了 linkXXX 方法。

 /**
  * 添加一个元素在链表的头节点位置
  */
private void linkFirst(E e) {
   // 添加元素之前的头节点
   final Node<E> f = first;
   //以添加的元素为节点值构建新的头节点 并将 next 指针指向 之前的头节点
   final Node<E> newNode = new Node<>(null, e, f);
   // first 索引指向将新的节点
   first = newNode;
   // 如果添加之前链表空则新的节点也作为未节点
   if (f == null)
       last = newNode;
   else
       f.prev = newNode;//否则之前头节点的 prev 指针指向新节点
   size++;
   modCount++;//操作数++
}

/**
 * 在链表末尾添加一个节点
 */
 void linkLast(E e) {
   final Node<E> l = last;//保存之前的未节点
   //构建新的未节点,并将新节点 prev 指针指向 之前的未节点
   final Node<E> newNode = new Node<>(l, e, null);
   //last 索引指向末节点
   last = newNode;
   if (l == null)//如果之前链表为空则新节点也作为头节点
       first = newNode;
   else//否则将之前的未节点的 next 指针指向新节点
       l.next = newNode;
   size++;
   modCount++;//操作数++
}

node(int index)


/**
 * 返回一个非空节点,这个非空节点位于 index 位置
 */
 Node<E> node(int index) {
   // assert isElementIndex(index);
    // 如果 index < size/2 则从0开始寻找指定角标的节点
    if (index < (size >> 1)) {
        Node<E> x = first;
        for (int i = 0; i < index; i++)
            x = x.next;
        return x;
    } else {
         // 如果 index >= size/2 则从 size-1 开始寻找指定角标的节点
        Node<E> x = last;
        for (int i = size - 1; i > index; i--)
            x = x.prev;
        return x;
    }
 }

linkBefore(E e, Node<E> succ)

void linkBefore(E e, Node<E> succ) {
   // assert succ != null;
   // 由于 succ 一定不为空,所以可以直接获取 prev 节点
   final Node<E> pred = succ.prev;
   // 新节点 prev 节点为 pred,next 节点为 succ
   final Node<E> newNode = new Node<>(pred, e, succ);
   // 原节点的 prev 指向新节点
   succ.prev = newNode;
   // 如果 pred 为空即头节点出插入了一个节点,则将新的节点赋值给 first 索引
   if (pred == null)
       first = newNode;
   else
       pred.next = newNode;//否则 pred 的下一个节点改为新节点
   size++;
   modCount++;
}

删除节点

remove()

/**
 *  删除头节点
 * @return 删除的节点的值 即 节点的 element
 * @throws NoSuchElementException 如果链表为空则抛出异常
 */
 public E removeFirst() {
    final Node<E> f = first;
    if (f == null)
        throw new NoSuchElementException();
    return unlinkFirst(f);
 }

/**
 *  删除尾节点
 *
 * @return  删除的节点的值 即 节点的 element
 * @throws NoSuchElementException  如果链表为空则抛出异常
 */
 public E removeLast() {
    final Node<E> l = last;
    if (l == null)
        throw new NoSuchElementException();
    return unlinkLast(l);
 }
 


/**
 * 删除指定索引位置的节点
 */
public E remove(int index) {
   checkElementIndex(index);
   return unlink(node(index));
}

/**
 *删除从头节点其第一个与 o 相同的节点
 */
public boolean remove(Object o) {
    // 区别对待 null 元素,比较元素时候使用 == 而不是 equals
   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;
}

unlink()

/**
 * 移除头节点
 */
 private E unlinkFirst(Node<E> f) {
    // assert f == first && f != null;
    // 头节点的 element 这里作为返回值使用
    final E element = f.item;
    // 头节点下个节点
    final Node<E> next = f.next;
    // 释放头节点的 next 指针,和 element 下次 gc 的时候回收这个内部类
    f.item = null;
    f.next = null; // help GC
    // 将 first 索引指向新的节点
    first = next;
    // 如果 next 节点为空,即链表只有一个节点的时候,last 指向 null
    if (next == null)
        last = null;
    else
        next.prev = null; //否则 next 的 prev 指针指向 null
    size--;//改变链表长度
    modCount++;//修改操作数
    return element;//返回删除节点的值
 }

/**
 * 移除未节点
 */
 private E unlinkLast(Node<E> l) {
    // assert l == last && l != null;
    final E element = l.item;
    //未节点的前一个节点,
    final Node<E> prev = l.prev;
    //释放未节点的内容
    l.item = null;
    l.prev = null; // help GC
    //将 last 索引指向新的未节点
    last = prev;
    // 链表只有一个节点的时候,first 指向 null
    if (prev == null)
       first = null;
    else
        prev.next = null;
    size--;
    modCount++;
    return element;
 }

关于unlink的代码示意图:

/**
 * Unlinks non-null node x.
 */
E unlink(Node<E> x) {
   // assert x != null;
   final E element = x.item;
   //保存 index 节点的前后两个节点
   final Node<E> next = x.next;
   final Node<E> prev = x.prev;
    // 如果节点为头节点,则做 unlinkFirst 相同操作
   if (prev == null) {
       first = next;
   } else {//否则将上一个节点的 next 指针指向下个节点
       prev.next = next;
       // 释放 index 位置 prev 指针
       x.prev = null;
   }
    // 如果节点为尾节点,则将 last 索引指向上个节点
   if (next == null) {
       last = prev;
   } else {//否则下个节点 prev 指针指向上个节点
       next.prev = prev;
       x.next = null;
   }

   x.item = null;
   size--;
   modCount++;
   return element;
}

clear 操作

/**
* Removes all of the elements from this list.
* The list will be empty after this call returns.
*/
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++;
}

查询节点

LinkedList 查询节点的方法,可分为根据指定的索引查询,获取头节点,获取未节点三种。

值得注意的是,根据索引去获取节点内容的效率并不高,所以如果查询操作多余增删操作的时候建议用 ArrayList 去替代。

/**
* 根据索引查询
*
public E get(int index) {
   checkElementIndex(index);
   return node(index).item;
}

/**
* 返回 first 索引指向的节点的内容
*
* @return the first element in this list
* @throws NoSuchElementException 如果链表为空则抛出异常
*/
public E getFirst() {
   final Node<E> f = first;
   if (f == null)
       throw new NoSuchElementException();
   return f.item;
}

/**
* 返回 last 索引指向的节点的内容
*
* @return the last element in this list
* @throws NoSuchElementException 如果链表为空则抛出异常
*/
public E getLast() {
   final Node<E> l = last;
   if (l == null)
       throw new NoSuchElementException();
   return l.item;
}

修改节点

LinkedList 只提供了 set(int index, E element) 一个方法

public E set(int index, E element) {
   // 判断角标是否越界
   checkElementIndex(index);
   // 采用 node 方法查找对应索引的节点
   Node<E> x = node(index);
   //保存节点原有的内容值
   E oldVal = x.item;
   // 设置新值
   x.item = element;
   // 返回旧的值
   return oldVal;
}

元素查询

/* 
* 返回参数元素在链表的节点索引,如果有重复元素,那么返回值为从头节点起的第一相同的元素节点索引,
* 如果没有值为该元素的节点,则返回 -1;
* 
* @param o element to search for
* @return 
*/
public int indexOf(Object o) {
   int index = 0;
  // 区别对待 null 元素,用 == 判断,非空元素用 equels 方法判断 
   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;
*
* @param o element to search for
* @return the index of the last occurrence of the specified element in
*         this list, or -1 if this list does not contain the element
*/
public int lastIndexOf(Object o) {
   int index = size;
   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;
}

调用 indexOf 从头结点开始查询元素位置遍历完成后若 返回值 !=-1 则表示存在,反之不存在

public boolean contains(Object o) {
    return indexOf(o) != -1;
}

LinkedList 作为双向队列的增删改查

Deque 这个双端队列就厉害了,它既可以实现栈的操作,也可以实现队列的操作,换句话说,实现了这个接口的类,既可以作为栈使用也可以作为队列使用。

我们来看下 Queue 给我们提供了的方法:

头部头部尾部尾部
插入addFirst(e)offerFirst(e)addLast(e)offerLast(e)
移除removeFirst()pollFirst()remveLast()pollLast
获取getFirst()peekFirst()getLast()peekLast

由于 Deque 接口继承 Queue 接口,当 Deque 当做队列使用时(FIFO),只需要在头部删除,尾部添加即可。我们现在复习下 Queue 中的方法及区别:

  1. Queueofferadd 都是在队列中插入一个元素,具体区别在于,对于一些 Queue 的实现的队列是有大小限制的,因此如果想在一个满的队列中加入一个新项,多出的项就会被拒绝。此时调用 add()方法会抛出异常,而 offer() 只是返回的 false。
  2. remove()poll() 方法都是从队列中删除第一个元素。remove()也将抛出异常,而 poll() 则会返回 null
  3. element()peek() 用于在队列的头部查询元素。在队列为空时, element() 抛出一个异常,而 peek() 返回 null

添加元素

// queue 的添加方法实现,
public boolean add(E e) {
   linkLast(e);
   return true;
}
// Deque 的添加方法实现,
public void addLast(E e) {
   linkLast(e);
} 
  
// queue 的添加方法实现,
public boolean offer(E e) {
   return add(e);
}

// Deque 的添加方法实现,
public boolean offerLast(E e) {
        addLast(e);
        return true;
}
    

很明显 LinkedList 的大小并没有限制,所以在 LinkedList 中他们的实现并没有实质性不同。

删除元素

// Queue 删除元素的实现 removeFirst 会抛出 NoSuchElement 异常
public E remove() {
   return removeFirst();
}

// Deque 的删除方法实现
public E removeFirst() {
   final Node<E> f = first;
   if (f == null)
       throw new NoSuchElementException();
   return unlinkFirst(f);
}
    
// Queue 删除元素的实现 不会抛出异常 如果链表为空则返回 null 
public E poll() {
   final Node<E> f = first;
   return (f == null) ? null : unlinkFirst(f);
}

// Deque 删除元素的实现 不会抛出异常 如果链表为空则返回 null 
public E pollFirst() {
   final Node<E> f = first;
   return (f == null) ? null : unlinkFirst(f);
}

获取元素

 // Queue 获取队列头部的实现 队列为空的时候回抛出异常
 public E element() {
    return getFirst();
 }
// Deque 获取队列头部的实现 队列为空的时候回抛出异常
public E getFirst() {
   final Node<E> f = first;
   if (f == null)
       throw new NoSuchElementException();
   return f.item;
}

// Queue 获取队列头部的实现 队列为空的时候返回 null
public E peek() {
   final Node<E> f = first;
   return (f == null) ? null : f.item;
}

// Deque 获取队列头部的实现 队列为空的时候返回 null
public E peekFirst() {
   final Node<E> f = first;
   return (f == null) ? null : f.item;
}

参考链接

https://juejin.cn/post/684490...


ML李嘉图
7 声望0 粉丝

« 上一篇
ArrayList-源码
下一篇 »
Integer-源码

引用和评论

0 条评论