Java集合系列——LinkedList源码详解

1、LinkedList概述

LinkedList类是双向列表,列表中的每个节点Node都包含了对前一个和后一个元素的引用.

public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable{
    }

继承了AbstractSequentialList,它又继承了AbstractList,则是列表的升级版,称为双向链表
实现了List接口,可以进行队列操作
实现了Deque接口,双向链表操作
实现了Cloneable接口,浅拷贝操作
实现了java.io.Serializable,支持序列化。

2、常用api

添加元素
新增元素到末尾
public boolean add(E e) {}
新增元素到特定位置
public void add(int index, E element) {
新增集合到末尾
public boolean addAll(Collection<? extends E> c) {}
新增集合到特定位置
public boolean addAll(int index, Collection<? extends E> c) {}
新增元素到第一个
public void addFirst(E e) {
新增元素到最后一位
public void addLast(E e) {

删除元素
删除特定位置的元素
public E remove(int index) {}
删除第一个元素
public E removeFirst() {}
删除最后一个元素
public E removeLast() {}
删除指定元素
public boolean remove(Object o) {}
删除全部
public void clear() {}


查询
查询第一个元素
public E getFirst() {
查询最后一个元素
public E getLast() {
是否包含元素
public boolean contains(Object o) {
查询长度
public int size() {}
根据下标查询
public E get(int index) {}
查询某个元素第一次出现的下标
public int indexOf(Object o) {}
查询某个元素最后一个出现的下标
public int lastIndexOf(Object o) {

修改
修改特定元素
public E set(int index, E element) {

队列操作
检索,但是不删除第一个元素
public E peek() {
检索,但是不删除第一个元素
public E element() {
检索并且删除第一个元素
public E poll() {
检索并且删除第一个元素
public E remove() {
添加元素
public boolean offer(E e) {
添加到第一个元素
public boolean offerFirst(E e) {
插入元素到最后一个
public boolean offerLast(E e) {
检索,但是不删除第一个元素
public E peekFirst() {
检索,但是不删除最后一个元素
public E peekLast() {
检索并且删除第一个元素
public E pollFirst() {
检索并且删除最后一个元素
public E pollLast() {
插入元素到第一个位置
public void push(E e) {
删除一个元素
public E pop() {
删除第一次出现的元素
public boolean removeFirstOccurrence(Object o) {
删除最后一个出现的元素
public boolean removeLastOccurrence(Object o) {

迭代器
public ListIterator<E> listIterator(int index) {

转换成数组
public Object[] toArray() {
转换成特定数组
public <T> T[] toArray(T[] a) {

以上所有的用法应该来说很简单,就不介绍api的用法了。

3、源码分析

/**
 * 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);  //这里调用的事addAll方法,这是一个公共方法
}

1、新增,add源码
public boolean add(E e) {
    linkLast(e);   // 这里是新增的具体工作
    return true; // 这里都是true,好像说只要执行了add方法,就肯定会返回true
}

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++;
}
//看看linkLast这个方法中的实现原理,
    final Node<E> l = last;
    final Node<E> newNode = new Node<>(l, e, null);
    last = newNode;
//这里做了个交换,把原来最后一个节点赋给l,在新建一个节点newNode,在把这个新街点newNode赋给新集合最后一个节点,其实就是在原有集合上在末尾追加一个新节点。
    if (l == null)
        first = newNode;
    else
        l.next = newNode;
//判断l是不是空,也就是原有集合是不是空,如果是空,那么新增的节点newNode就赋给首个节点,如果不是空,则把上个节点指向最后一个节点,这样就在原有集合添加了一个新节点。

2.新增整个集合addAll

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

public boolean addAll(int index, Collection<? extends E> c) {
    checkPositionIndex(index);  //这里检查下标合法性

    Object[] a = c.toArray();  
    int numNew = a.length;
    if (numNew == 0)
        return false;   //检查添加的集合是否有数据,如果没有,则直接返回false

    Node<E> pred, succ;       //定义两个节点
    if (index == size) {
        succ = null;
        pred = last;   //如果下标等于长度,则把最后一个节点赋给pred
    } else {
        succ = node(index);
        pred = succ.prev;    //否则把下标所在的节点赋给succ,这个节点的前节点引用赋给pred
    }

    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;   //把新节点赋给pred
    }

    if (succ == null) {
        last = pred;
    } else {
        pred.next = succ;
        succ.prev = pred;  //互相引用,这里可以看出链表是双向的,互相引用
    }

    size += numNew;
    modCount++;
    return true;
}
//这里在介绍下node()方法,其实是根据下标查询这个节点
/**
 * Returns the (non-null) Node at the specified element index.
 */
Node<E> node(int index) {
    // assert isElementIndex(index);

    if (index < (size >> 1)) {   //与size>>1比较,其实大概就是size长度的一半,这样其实加快了查询速度,两头查询。
        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;
    }
}


2、删除
public E remove(int index) {
    checkElementIndex(index);  //检查是否合法
    return unlink(node(index));
}
// 以上用了unlink()方法,这介绍下unlink做了哪些工作。
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;
}

清空数据
public void clear() {
    // Clearing all of the links between nodes is "unnecessary", but:
    // - helps a generational GC if the discarded nodes inhabit
    //   more than one generation
    // - is sure to free memory even if there is a reachable Iterator
    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;  //长度为0
    modCount++;
}

3、查询
public E get(int index) {
    checkElementIndex(index);  //检查下标合法性
    return node(index).item;  //用了node方法
}

4、修改
public E set(int index, E element) {
    checkElementIndex(index);   //检查合法性
    Node<E> x = node(index);    //查询到index所在的数据
    E oldVal = x.item;    //获取当前元素
    x.item = element;    //把新元素赋给当前位置
    return oldVal;
}

4、总结:

以上的增删改查中所有方法都会涉及到一个很重要的类Node,这个类其实是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;
    }
}

看Node类代码很清晰,有一个具有三个参数的构造函数,当我们
新增一个元素时,我们会new一个Node,这个Node必须具有前后两个元素的引用,还有这个元素本身,这样,除了首尾两个节点,每个节点都会跟前后两个节点产生关联,我们增删改查,都是围绕这个节点的引用来进行操作。当新增元素时,先生成一个有带元素的前后引用的节点,然后断开具体某处的引用,然后在把新节点加进去,这有点像水管不够,然后在中间加水管一样的道理。
这个跟ArrayList是不一样的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值