LinkedList

LinkedList是一个实现了List接口和Deque接口的双端链表。由于底层的实现是由链表实现的,使得它支持高效的插入和删除操作,同时实现的Deque接口,又使得它拥有队列的特性。LinkedList不是线程安全的,意味着如果要在多线程环境下使用需要自己加锁,如果想要使用线程安全的LinkedList可以使用Collections.synchronizedList(List<T> list)方法将LinkedList转变为一个线程安全的LinkedList

List<Object> list = Collections.synchronizedList(new LinkedList<>());

LinkedList内部构造

LinkedList的内部构造如下图所示:
LinkedList源码解析_分享
LinkedList是由链表实现的,它内部有一个内部类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;
        }
    }

构造方法

默认的无参构造

 public LinkedList() {
    }

有参构造

添加另外一个集合

 public LinkedList(Collection<? extends E> c) {
        this();
        addAll(c);
    }

添加(add)方法

add方法

 public boolean add(E e) {
        linkLast(e); // 将元素添加进链表尾部
        return true;
    }

linkLast

   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++;// 修改的次数增加
    }

add(int index,E e)在链表中的某个位置添加一个新的节点

 public void add(int index, E element) {
        checkPositionIndex(index); // 检查是否在[0-size]的区间内

        if (index == size) // 如果插入的位置刚好是链表的长度,说明正好在链表尾部
            linkLast(element);
        else // 否则在当前索引位置添加节点
            linkBefore(element, node(index));
    }

addAll(Collection c) 在链表的末尾添加进另一个集合

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

addAll(int index,Collection c) 在链表某个位置添加进一个集合

public boolean addAll(int index, Collection<? extends E> c) {
        checkPositionIndex(index); // 判断索引是否在[0-size]之间
		
        Object[] a = c.toArray(); // 转换为数组
        int numNew = a.length;
        if (numNew == 0) // 如果找个集合的长度为空,插入无效
            return false;

        Node<E> pred, succ;
        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); // 创建节点
            if (pred == null) // 如果前继结点为空,说明链表为空
                first = newNode; // 头结点为当前节点
            else// 否则添加进链表
                pred.next = newNode;
            pred = newNode; // 移动指针位置
        }
  		// 如果插入位置在尾部,重置last节点
        if (succ == null) {
            last = pred;
        } else { // 否则,将插入的链表与先前链表连接起来
            pred.next = succ;
            succ.prev = pred;
        }

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

add First 插入到链表头部

 public void addFirst(E e) {
        linkFirst(e); // 只调用了找个方法
    }

linkFirst 插入元素到链表头部
找个方法的逻辑实现和linkLast基本一致,就是一个是在头部插入,一个是在尾部插入

  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++;
    }

addLast(E e) 将元素添加到链表尾部,与 add(E e) 方法一样

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

根据索引获得元素的方法

get(int index) 根据指定索引返回数据

public E get(int index) {
        //检查index范围是否在size之内n[0-size)
        checkElementIndex(index);
        //调用node(index)去找到index对应的node然后返回它的值
        return node(index).item;
    }

返回头结点的元素

 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;
    }

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

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

返回尾结点的元素

 public E getLast() {
        final Node<E> l = last;
        if (l == null)
            throw new NoSuchElementException();
        return l.item;
    }
public E peekLast() {
        final Node<E> l = last;
        return (l == null) ? null : l.item;
    }

getXX与peekxx的区别

get类方法直译的意思是获得,所以当元素不存在(即为空)的时候,就应该抛出异常。
peek类的方法直译的意思为查看,所以当元素不存在可以返回空。

根据元素获得元素索引的方法

indexof 从头结点开始查找元素,若元素存在,返回索引,否则返回-1
因为Collection接口的子类集合们都支持插入null值,所以分为两种情况

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;
    }

lastIndexof(Object o) 从尾结点开始查找元素

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;
    }

检测链表中是否包含某个元素的方法

contains(Object o)
根据查找索引的方法来判断元素是否在链表中

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

删除(remove/pop)方法

删除头结点 remove(),pop(),removeFirst(),pollFirst()

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

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

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

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

unlinkFirst

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; // 将对象的所有引用置空,帮助GC
        first = next;
        if (next == null) // 如果next为空,那么链表已经空了,尾结点也置空
            last = null;
        else // 将前继结点置空
            next.prev = null;
        size--;
        modCount++;
        return element;
    }

删除尾结点 removeLast(),pollLast()

public E removeLast() {
        final Node<E> l = last;
        if (l == null)
            throw new NoSuchElementException();
        return unlinkLast(l);
    }
public E pollLast() {
        final Node<E> l = last;
        return (l == null) ? null : unlinkLast(l);
    }

unlinkLast
逻辑与unlinkFirst基本一致

 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;
    }

区别: removeLast()在链表为空时将抛出NoSuchElementException,而pollLast()方法返回null。

remove(Object o): 删除指定元素

public boolean remove(Object o) {
        //如果删除对象为null
        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;
    }

当我们想要删除一个确定的元素时,我们只要直接调用remove(Object o)方法就可以了,不过该方法一次只会删除一个匹配的对象,如果删除了匹配对象,返回true,否则false。
unlink(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;
    }

remove(int index) 删除指定位置的元素

public E remove(int index) {
        //检查index范围
        checkElementIndex(index);
        //将节点删除
        return unlink(node(index));
    }

思考

在上面的一些方法中,我们看到了参数是Object的情况,那么我们思考一下,为什么集合是带泛型的,为什么传入的参数不应该是泛型参数呢(比如remove(Object o))?

个人认为,因为Java中的泛型是伪泛型,只在编译时期存在,在运行时会被泛型擦除为Object类型,而又因为这个原因,我们可以通过反射调用add()方法绕开泛型检验,添加可以与泛型不一致的元素,那么,如果remove 方法的参数是(E element),那么在循环匹配的时候,必然会出现类型不一致的问题,所以为了安全考虑,统一传入的是Object类型参数。

如果有不对的地方,欢迎斧正。