LinkedList

一、简介

可以先看下官方Api对LinkedList的介绍
在这里插入图片描述
从图中可以看出,LinkedList实现的接口有:

  1. 实现了Serializable是序列化接口,因此它支持序列化,能够通过序列化传输;
  2. 实现了Cloneable接口,能被克隆;
  3. 实现了Iterable接口,可以迭代器遍历;
  4. 实现了Collection,拥有集合操作的方法;
  5. 实现Deque/Queue可以当做对列/双端队列 使用;
  6. 实现了List接口,拥有增删改查等方法;

先看下LInkedList的特点,对LinkedList有 一个大体的认识:

  1. LinkedList底层数据结构时双向链表,但是头结点不存放数据,只有后置节点的引用;
  2. 集合中的元素允许为null,可以看到源码中在查找和删除时,都划分为该元素为null和不为null两种情况来处理;
  3. 允许内部元素重复;
  4. 不存在扩容问题,所以是没有扩容的方法;
  5. 元素在内部是有序存放的,依次在链表上添加节点;
  6. 实现了栈和队列的操作方法,因此也可以作为栈、队列和双端队列来使用;
  7. 由于是链表实现,并且没有实现RandomAccess,虽然在查找的时候,会先判断在前半部分或者后半部分,然后依次从前或者从后查找,但是查找效率还是很低,不过增删效率高,但是查找和修改大部分情况下不如ArrayList;
  8. 线程不安全,可以用个Collection.synchronizedList(new LinkedList())返回一个线程安全的LinkedList;

二、示例展示

LinkedList<String> list = new LinkedList<String>();
list.add("语文: 1");
list.add("数学: 2");
list.add("英语: 3");

在这里插入图片描述

三、源码分析

3.1 属性

public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
    transient int size = 0;

    /**
     * Pointer to first node.
     * Invariant: (first == null && last == null) ||
     *            (first.prev == null && first.item != null)
     */
    transient Node<E> first;

    /**
     * Pointer to last node.
     * Invariant: (first == null && last == null) ||
     *            (last.next == null && last.item != null)
     */
    transient Node<E> last;
}

前面讲了,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;
    }
}

很明显的双向链表的结构。

3.2 构造方法

   /**
     * Constructs an empty list.
     */
    public LinkedList() {
    }

    /**
     * Constructs a list containing the elements of the specified
     * collection, in the order they are returned by the collection's
     * iterator.
     *
     * @param  c the collection whose elements are to be placed into this list
     * @throws NullPointerException if the specified collection is null
     */
    public LinkedList(Collection<? extends E> c) {
        this();
        addAll(c);
    }

构造方法很简单,没有什么特别的操作 。

3.3 添加方法——add()

不指定插入元素的下标(默认插入尾部)

   /**
     * Appends the specified element to the end of this list.
     * 
     * @param e element to be appended to this list
     * @return {@code true} (as specified by {@link Collection#add})
     */
    public boolean add(E e) {
        linkLast(e);
        return true;
    }

    /**
     * Links e as last element.
     */
    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++;
    }

在指定位置添加

   /**
     * Inserts the specified element at the specified position in this list.
     * Shifts the element currently at that position (if any) and any
     * subsequent elements to the right (adds one to their indices).
     *
     * @param index index at which the specified element is to be inserted
     * @param element element to be inserted
     */
    public void add(int index, E element) {
        // 检查插入的位置是否合法,及是否比0大,比当前size小
        checkPositionIndex(index);
        // 如果等于当前大小,就是相当于在尾部再插入一个节点
        // 否则就是插入到index 所在位置的节点的前面
        if (index == size)
            linkLast(element);
        else
            linkBefore(element, node(index));
    }
    
    /**
     * 返回指定索引处的一个非空节点
     */
    Node<E> node(int index) {
        // assert isElementIndex(index);
        // 这里LinkedList做了一个优化,先判断索引是在前半部分和后半部分
        // 如果在前半部分,从头节点开始找,正序找
        // 如果在后半部分,从尾结点开始找,倒序找
        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;
        // 构建新节点,前置节点找到节点的原前置节点,e是元素值,后置节点是根据位置找到的succ
        final Node<E> newNode = new Node<>(pred, e, succ);
        //原位置的前置节点设置为要插入的节点
        succ.prev = newNode;
        if (pred == null)
            // 如果原位置的前置节点为空,即原位置succ是头节点,即add(0,E)然后把新建节点赋值为头节点
            first = newNode;
        else
            // 不为null,原位置的前置节点的后置节点设置为新节点
            pred.next = newNode;    
        size++;
        modCount++;
    }

先检查是否在可插入的范围内,不在则抛出异常,如果index和当前size相等,则直接插入到尾节点,如果小于当前size,那么久插入到index节点的前面。

addAll()

 // 没有传入位置,直接加到最后 
 public boolean addAll(Collection<? extends E> c) {
        return addAll(size, c);
    }

    /**
     * 加入置顶位置
     */
    public boolean addAll(int index, Collection<? extends E> c) {
        // 检查index合法性
        checkPositionIndex(index);
        // 传入的Collection转换成数组 
        Object[] a = c.toArray();
        int numNew = a.length;
        // 空数组,直接返回插入失败
        if (numNew == 0)
            return false;
        // pred是succ的前置节点,succ指向当前需要插入节点的位置的节点
        Node<E> pred, succ;
        // index等于size,即在尾节点插入
        // 不相等,找到需要插入的位置的节点,以及其前节点,pred可能为空
        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);
            // 前节点为null,则给first赋值为当前新节点
            if (pred == null)
                first = newNode;
            else
                // 不为null,pred后置节点设置为新节点
                pred.next = newNode;
            // 每次设置完,pred表示刚插入的节点,一次往后插入。    
            pred = newNode;
        }

        // 如果是从size位置开始添加,最后添加的节点成了尾节点
        if (succ == null) {
            last = pred;
        } else {
            // 如果不是从size开始添加,数组中最后一个元素的后置节点指向为原index位置节点
            // 原index位置节点的前置节点置为数组中最后一个元素构建的节点。
            pred.next = succ;
            succ.prev = pred;
        }

        size += numNew;
        modCount++;
        return true;
    }

addFirst 、addLast

   /**
    *  添加元素到链表头
    */
    public void addFirst(E e) {
        linkFirst(e);
    }

 /**
     * Links e as first element.
     */
    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++;
        modCount++;
    }

   /**
    *  添加元素到链表尾
    */
    public void addLast(E e) {
        // linkLast()在上面介绍add()方法时已经讲了
        linkLast(e);
    }
    

3.4 删除——remove()

  
     public E remove(int index) {
        checkElementIndex(index);
        // 先拿到置顶位置的节点
        return unlink(node(index));
    }

   // 移除指定元素(只移除第一个)
   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;
    }

    /**
     * Unlinks non-null node x.
     */
    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;

        if (prev == null) {
            first = next;
        } else {
            prev.next = next;
            x.prev = null;
        }

        if (next == null) {
            last = prev;
        } else {
            next.prev = prev;
            x.next = null;
        }

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

这两个删除的方法基本都是先找到要删除元素对应的节点,然后再去执行unlink()方法 对该节点前置节点、后置节点重新进行指向。然后把引用的元素置为null,便于gc回收,最后返回移除的元素。
此外还有移除第一个和最后一个

   //移除第一个元素
   public E removeFirst() {
        final Node<E> f = first;
        if (f == null)
            throw new NoSuchElementException();
        return unlinkFirst(f);
    }

   // 移除第一个元素,调整指针指向,并把头部元素置空,便于gc回收
   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--;
        modCount++;
        return element;
    }
    
   // 移除最后一个元素
   public E removeLast() {
        final Node<E> l = last;
        if (l == null)
            throw new NoSuchElementException();
        return unlinkLast(l);
    }
    
    ///移除最后一个元素,调整指针指向,并把尾部元素置空,便于 gc 回收
   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 = prev;
        if (prev == null)
            first = null;
        else
            prev.next = null;
        size--;
        modCount++;
        return element;
    }

3.5 修改——set()

    /**
     * Replaces the element at the specified position in this list with the
     * specified element.
     */
    public E set(int index, E element) {
        checkElementIndex(index);
        Node<E> x = node(index);
        E oldVal = x.item;
        x.item = element;
        return oldVal;
    }

3.6 查找——get()

    /**
     * Returns the element at the specified position in this list.
     */
    public E get(int index) {
        checkElementIndex(index);
        return node(index).item;
    }

  // 查找第一个元素
  public E getFirst() {
      final Node<E> f = first;
      if (f == null)
          throw new NoSuchElementException();
      return f.item;
  }
  
   // 查找最后一个元素
   public E getLast() {
       final Node<E> l = last;
       if (l == null)
            throw new NoSuchElementException();
       return l.item;
    }

3.7 队列

作为队列使用的一些方法
队列是什么?

  1. 队列是一种比较特殊的线性结构。它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作。进行插入操作的端称为队尾,进行删除操作的端为队头;
  2. 队列中最先插入的元素也将最先被删除,对应的最后插入的元素将最后被删除。因此队列又称为“先进先出”(FIFO)的线性表,与栈(FILO)刚好相反;
  3. 队列的抽象是Queue,而LinedList是实现了Deque接口,Deque又继承了Queue,所以LinkedList是可以当做队列来使用的。
    先看下Queue接口:
    在这里插入图片描述
public interface Queue<E> extends Collection<E> {
  	// 增加一个元素到队列尾部,如果队列有大小限制,并且队列已满,会抛出异常 IllegalArgumentException
    boolean add(E e);

  	// 增加一个元素到队列尾部,和 add 不同的是:如果队列有大小限制,并且队列已满,则返回 false,不抛出异常
    boolean offer(E e);
  	
  	// 检索到队列头部元素,并且将其移出队列。和 poll 方法不同的是如果队列是空的,那么抛出 NoSuchElementException 异常
    E remove();

  	// 检索到队列头部元素,并且将其移出队列。如果队列是空的,那么返回 null;
    E poll();
  
  	// 检索队列头部的元素,并不会移除,和 peek 方法不同的是:如果队列是空的,那么抛出 NoSuchElementException 异常;
    E element();

  	// 检索队列头部的元素,并不会移除,如果队列是空的,那么返回 null;
    E peek();
}

下面介绍一下对应的方法:
add、offer


public boolean offer(E e) {
    return add(e);
}

前面队列中的定义已经写了,在add会在队列满时会抛出异常,但是我们可以看到这个offer()方法也是调了add方法,只是对add的一种包装,实际使用效果是一样的。这是因为LinkedList中并没有设置大小的方法,所以也就不存在超出队列大小的限制。
remove、poll

public E remove() {
    return removeFirst();
}

public E poll() {
    final Node<E> f = first;
    return (f == null) ? null : unlinkFirst(f);
}

这两个方法也不会抛出异常。
remove会直接调用removeFirst从头部移除元素,并且在removeFirst方法移除的过程中可能抛出异常。
poll则先把头部元素取出来,进行判空。
element、peek

public E element() {
    return getFirst();
}

public E getFirst() {
    final Node<E> f = first;
    if (f == null)
        throw new NoSuchElementException();
    return f.item;
}

public E peek() {
    final Node<E> f = first;
    return (f == null) ? null : f.item;
}

3.8 双向链表

双向队列是队列(Queue)的一个子接口Denque,双向队列两端的元素都可以入队列和出队列。可以实现先先进先出或者先进后出的数据结构。
如果把Deque限制为只能从一端入队和出队,那么就可以实现栈的数据结构,遵循先进后出的规则。
如果不对Deque进行限制,用做双线队列,那么就是先进先出。
在这里插入图片描述
主要方法如下:

public interface Deque<E> extends Queue<E> {
    //  将指定元素插入此双端队列的开头(如果可以直接这样做而不违反容量限制)
    void addFirst(E e);

    //将指定元素插入此双端队列的末尾(如果可以直接这样做而不违反容量限制)。
    void addLast(E e);

    //在不违反容量限制的情况下,将指定的元素插入此双端队列的开头。
    boolean offerFirst(E e);

    // 在不违反容量限制的情况下,将指定的元素插入此双端队列的末尾。
    boolean offerLast(E e);

    // 获取并移除此双端队列第一个元素。
    E removeFirst();

    // 获取并移除此双端队列的最后一个元素。
    E removeLast();

    //获取并移除此双端队列的第一个元素;如果此双端队列为空,则返回 null。
    E pollFirst();

    //获取并移除此双端队列的最后一个元素;如果此双端队列为空,则返回 null。
    E pollLast();

    // 获取,但不移除此双端队列的第一个元素。
    E getFirst();

    // 获取,但不移除此双端队列的最后一个元素。
    E getLast();

    // 获取,但不移除此双端队列的第一个元素;如果此双端队列为空,则返回 null。
    E peekFirst();

    //获取,但不移除此双端队列的最后一个元素;如果此双端队列为空,则返回 null。
    E peekLast();

    //从此双端队列移除第一次出现的指定元素。
    boolean removeFirstOccurrence(Object o);

    // 从此双端队列移除最后一次出现的指定元素。
    boolean removeLastOccurrence(Object o);

    // *** Queue methods ***

    // 将指定元素插入此双端队列所表示的队列(换句话说,此双端队列的尾部),如果可以直接这样做而不违反容量限制的话;
  	// 如果成功,则返回 true,如果当前没有可用空间,则抛出 IllegalStateException。
    boolean add(E e);

    // 将指定元素插入此双端队列所表示的队列(换句话说,此双端队列的尾部),如果可以直接这样做而不违反容量限制的话;
  	// 如果成功,则返回 true,如果当前没有可用的空间,则返回 false。
    boolean offer(E e);

    //获取并移除此双端队列所表示的队列的头部(换句话说,此双端队列的第一个元素)。
    E remove();

    //获取并移除此双端队列所表示的队列的头部(换句话说,此双端队列的第一个元素);如果此双端队列为空,则返回 null。
    E poll();

    //获取,但不移除此双端队列所表示的队列的头部(换句话说,此双端队列的第一个元素)。
    E element();

    //获取,但不移除此双端队列所表示的队列的头部(换句话说,此双端队列的第一个元素);如果此双端队列为空,则返回 null。
    E peek();


    // *** Stack methods ***

    // 将一个元素推入此双端队列所表示的堆栈(换句话说,此双端队列的头部),如果可以直接这样做而不违反容量限制的话;
  	// 如果成功,则返回 true,如果当前没有可用空间,则抛出 IllegalStateException。
    void push(E e);

    // 从此双端队列所表示的堆栈中弹出一个元素
    E pop();


    // *** Collection methods ***

    //  从此双端队列中移除第一次出现的指定元素
    boolean remove(Object o);

    // 是否包含一个元素
    boolean contains(Object o);

    // 队列大小
    int size();

    // 返回此双端队列的迭代器
    Iterator<E> iterator();

    // 返回一个迭代器,该迭代器具有此双端队列的相反顺序
    Iterator<E> descendingIterator();

}

具体就不做记录。

四、总结

  1. LinkedList底层数据结构时双向链表,但是头节点不存放数据,只有后置节点的引用;
  2. 集合中的元素允许为null;
  3. 允许内部元素重复;
  4. 不存在扩容问题;
  5. 元素在内部是有序存放的,依次在链表上添加节点;
  6. 实现了栈和队列的操作方法,因此也可以作为栈、队列、双联队列来使用;
  7. 由于是链表实现,并且没有实现RandomAccess,虽然在查找的时候,会先判断是在前半部分或者后半部分,然后依次从前或者从后查找,但是查找效率还是很低,不过增删效率高,但是查找后修改大部分情况不如ArrayList;
  8. 线程不安全,可以用Collections.synchronizedList(new LinkedList())返回一个线程安全的LinkedList;

ArrayList与LinkedList性能比较ArrayList、LinkedList性能测试与分析」

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值