Java集合源码分析之LinkedList

Posted linteresting

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java集合源码分析之LinkedList相关的知识,希望对你有一定的参考价值。

1. LinkedList简介

技术图片

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

可以看到LinkedList类继承AbstractSequentialList类,实现了List, Deque, Cloneable, java.io.Serializable接口。实现List接口,实现对列表的增删改查操作,并且元素可以为null,实现Deque接口,为add,poll提供先进先出队列操作,以及其他堆栈和双端队列操作。LinkedList是不同步的,多线程不安全,迭代器是快速失败的(fail-fast),不能再迭代过程中对集合进行修改操作。

2. 成员变量

//集合的元素个数
transient int size = 0;
//头节点
transient Node<E> first;
//尾节点
transient Node<E> last;

LinkedList的成员变量很简单只有三个集合:size元素个数,first头节点,last尾节点。

3. 构造方法

空参构造方法:

public LinkedList() {
}

传入一个集合:

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

可以看到调用了addAll()方法将传入集合的所有元素添加到了LinkedList中。

4. 内部方法

4.1 linkFirst(E e)将e链接到链表的头
private void linkFirst(E e) {
    //保存当前集合的头节点到f
    final Node<E> f = first;
    //e保存到newNode中,关于  Node<E>的源码分析在后面
    final Node<E> newNode = new Node<>(null, e, f);
    //头节点赋为newNode
    first = newNode;
    //判断原集合是否为空(判断原头节点是否为null)
    if (f == null)
        //如果原集合为空,将集合的last尾节点也赋为newNode
        last = newNode;
    else
        //如果原集合不为空(first不为null),将原头节点的前驱节点赋为newNode
        f.prev = newNode;
    //结合长度自加
    size++;
    //对集合做出修改modCount自加保证集合版本一致
    modCount++;
}
4.2 linkLast(E e)将e链接到链表的尾
void linkLast(E e) {
    //保存当前集合的尾节点到l
    final Node<E> l = last;
    //e保存到newNode中
    final Node<E> newNode = new Node<>(l, e, null);
    //尾节点赋为newNode
    last = newNode;
    //判断尾节点是否为null(集合是否为空)
    if (l == null)
        //如果集合是空,头节点赋为newNode  first=last
        first = newNode;
    else
        //集合不为空,将新的节点追加到原集合的last尾节点后作为新的尾节点
        l.next = newNode;
    //集合个数自加
    size++;
    //对集合做出修改modCount自加保证集合版本一致
    modCount++;
}
4.3 linkBefore(E e, Node succ)在一个非空节点前插入元素
void linkBefore(E e, Node<E> succ) {
    //取得要插入的节点的前驱节点pred
    final Node<E> pred = succ.prev;
    //插入节点的前驱节点为pred,后继节点为当前节点succ
    final Node<E> newNode = new Node<>(pred, e, succ);
    //succ的前驱节点赋为新节点
    succ.prev = newNode;
    //判断succ即要插入的位置的节点的前驱节点是否为null
    if (pred == null)
        //如果为空那么原节点就是头节点,所以将新节点置为头节点
        first = newNode;
    else
        //如果不为空,succ不是集合的头节点,那么将前驱节点的后继节点置为新节点
        pred.next = newNode;
    //集合个数自加
    size++;
    //对集合做出修改modCount自加保证集合版本一致
    modCount++;
}
4.4 unlinkFirst(Node f)解除头节点并且返回该节点保存的值
private E unlinkFirst(Node<E> f) {
    //取出头节点的值存到element中
    final E element = f.item;
    //将头节点的后继节点存到next中
    final Node<E> next = f.next;
    //将头节点的值和下个节点的地址值置为null
    f.item = null;
    f.next = null; // help GC
    //原头节点的后继节点置为集合的头节点
    first = next;
    //判断原头节点的后继节点是否为空
    if (next == null)
        //如果为空,那么原集合就只有一个节点,那么尾节点置为空
        last = null;
    else
        //不为空,将新的头节点的前驱节点置为null
        next.prev = null;
    //集合长度自减
    size--;
    //对集合做出修改modCount自加保证集合版本一致
    modCount++;
    //返回原头节点保存的数据
    return element;
}
4.5 unlinkLast(Node l)移除尾节点并将尾节点保存的数据返回
private E unlinkLast(Node<E> l) {
    //取出位节点保存的数据
    final E element = l.item;
    //取出尾节点的前驱节点
    final Node<E> prev = l.prev;
    //尾节点保存数据置为null
    l.item = null;
    //尾节点的前驱节点置为null
    l.prev = null; // help GC
     //原尾节点的前驱节点置为当前结合的尾节点
    last = prev;
    //判断原尾节点的前驱节点是否为null
    if (prev == null)
        //如果是那么头节点置为null,集合为空
        first = null;
    else
        //否,那么原尾节点的前驱节点的后继节点置为null
        prev.next = null;
    //集合长度自减
    size--;
    //对集合做出修改modCount自加保证集合版本一致
    modCount++;
    //返回原尾节点保存的数据
    return element;
}
4.6 nulik(Node x)移除一个非空节点
E unlink(Node<E> x) {
    //取出要移除节点保存的数据
    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;
        //移除节点的后继节点置为null
        x.prev = null;
    }
    //移除节点的后继节点为null即要移除的节点为集合的尾节点
    if (next == null) {
        //将移除节点的前驱节点值为集合的尾节点
        last = prev;
    } else {
        //将原节点的前驱节点置为后继节点的前驱节点
        next.prev = prev;
        //移除节点的后继节点置为null
        x.next = null;
    }
    //移除节点保存的数据置为null
    x.item = null;
    //集合长度自减
    size--;
    //对集合做出修改modCount自加保证集合版本一致
    modCount++;
    //返回移除节点保存的数据
    return element;
}
4.7 isElementIndex(int index) 判断指定索引位置是否为元素索引位置
private boolean isElementIndex(int index) {
    //判断index是否在0到size之间 包括0 但是不包括size
    return index >= 0 && index < size;
}
4.8 isPositionIndex(int index) 判断指定索引位置是否为可用位置(对于迭代器和add方法)
private boolean isPositionIndex(int index) {
    return index >= 0 && index <= size;
}
4.9 node(int index) 返回指定位置的节点
Node<E> node(int index) {
    //判断传入的index是否在集合的前半部分
    if (index < (size >> 1)) {
        Node<E> x = first;
        //从0开始遍历到到指定的index处
        for (int i = 0; i < index; i++)
            x = x.next;
        return x;
    } else {
        //如果index在集合的后半部分
        Node<E> x = last;
        //倒着遍历集合
        for (int i = size - 1; i > index; i--)
            x = x.prev;
        return x;
    }
}

尽管知道索引index,但是由于LinkedList没有实现RandomAccess接口,所以不能依靠索引快速查询,只能通过遍历的方法,node通过先判断index是在前半部分还是在后半部分,将遍历的时间复杂度从O(n)降低到了O(n/2)但是对于数据庞大的集合,这样的查找效率还是太低了。

5. 查找方法

5.1 getFirst()获取集合的头节点
public E getFirst() {
    //将集合头节点取出
    final Node<E> f = first;
    //判断头节点是否为空
    if (f == null)
        throw new NoSuchElementException();
    //返回头节点的数据
    return f.item;
}
5.2 getLat()获取集合的尾节点
public E getLast() {
    //取出集合的尾节点
    final Node<E> l = last;
    //尾节点是否为空
    if (l == null)
        throw new NoSuchElementException();
    //返回尾节点的数据
    return l.item;
}
5.3 contains()是否包含某个元素
public boolean contains(Object o) {
    return indexOf(o) >= 0;
}
5.4 size返回集合长度
public int size() {
    return size;
}
5.5 indexOf(Object o) 返回指定数据的索引
public int indexOf(Object o) {
    //index初始化index
    int index = 0;
    //如果传入o为null
    if (o == null) {
        //遍历集合找到为村川数据为null的元素返回其索引
        for (Node<E> x = first; x != null; x = x.next) {
            if (x.item == null)
                return index;
            index++;
        }
    } else {
        //遍历集合找到存储数据和传入的o相同的元素返回其索引
        for (Node<E> x = first; x != null; x = x.next) {
            if (o.equals(x.item))
                return index;
            index++;
        }
    }
    //没有找到返回-1
    return -1;
}

##### 5.6 lastIndexOf(Object o)返回指定数据最后出现的索引

public int lastIndexOf(Object o) {
    //倒着遍历,所以初始化index为size
    int index = size;
    //如果穿的数据为null,倒着遍历找到数据为null的节点
    if (o == null) {
        for (Node<E> x = last; x != null; x = x.prev) {
            index--;
            if (x.item == null)
                return index;
        }
    } else {
        //倒着遍历找到数据为o的节点
        for (Node<E> x = last; x != null; x = x.prev) {
            index--;
            if (o.equals(x.item))
                return index;
        }
    }
    //没有找到返回-1
    return -1;
}
5.7 peek() 返回头节点
public E peek() {
    final Node<E> f = first;
    return (f == null) ? null : f.item;
}
5.8 element() 返回头节点
public E element() {
    return getFirst();
}
5.9 peekFirst() 返回头节点不删除
public E peekFirst() {
    final Node<E> f = first;
    return (f == null) ? null : f.item;
 }
5.10 peekLast() 返回尾节点不删除
public E peekLast() {
    final Node<E> l = last;
    return (l == null) ? null : l.item;
}

6. 增删方法

6.1 removeFirst()删除头节点
public E removeFirst() {
    //取出头节点
    final Node<E> f = first;
    if (f == null)
        throw new NoSuchElementException();
    //直接调用unlikFirst方法删除头节点
    return unlinkFirst(f);
}
6.2 removeLast()删除尾节点
public E removeLast() {
    //取出尾节点
    final Node<E> l = last;
    if (l == null)
        throw new NoSuchElementException();
    //调用unlinkLast方法删除尾节点
    return unlinkLast(l);
}
6.3 addFirst()在集合头部插入节点
public void addFirst(E e) {
    //直接调用linkFirst方法添加头节点
    linkFirst(e);
}
6.4 addLast()在集合尾部添加节点
public void addLast(E e) {
    //调用linkLast添加节点
    linkLast(e);
}
6.5 add()集合尾部追加一个元素
public boolean add(E e) {
    //调用linkLast方法在更换尾节点
    linkLast(e);
    return true;
}
6.6 remove()移除集合中第一次出现的某个数据的节点
public boolean remove(Object o) {
    //判断传入数据是否为null
    if (o == null) {
        //遍历集合
        for (Node<E> x = first; x != null; x = x.next) {
            if (x.item == null) {
                //找到元素,用unlink方法删除
                unlink(x);
                return true;
            }
        }
    } else {
        //遍历集合
        for (Node<E> x = first; x != null; x = x.next) {
            if (o.equals(x.item)) {
                //找到元素,用unlink方法删除
                unlink(x);
                return true;
            }
        }
    }
    //没有找到这个元素返回false
    return false;
}
6.7 addAll()在集合尾部添加指定集合
public boolean addAll(Collection<? extends E> c) {
    return addAll(size, c);
}
6.8 addAll(int index, Collection<? extends E> c)向指定位置添加集合
public boolean addAll(int index, Collection<? extends E> c) {
    //检查下标是否越界
    checkPositionIndex(index);
    //将传入的c转化为数组
    Object[] a = c.toArray();
    //获取数组的长度
    int numNew = a.length;
    //如果数组为空返回false
    if (numNew == 0)
        return false;
    //pred succ节点  index节点
    Node<E> pred, succ;
    //如果插入的索引位置为集合尾部
    if (index == size) {
        //succ节点置null
        succ = null;
        //将集合的尾节点赋给pred
        pred = last;
    } else {
        //如果不是在尾部插入指定的集合,那么用node方法找到要插入位置的节点
        //赋给succ
        succ = node(index);
        //pred就为succ的前驱节点
        pred = succ.prev;
    }

    //遍历数组
    for (Object o : a) {
        @SuppressWarnings("unchecked") E e = (E) o;
        //数组的值放置到节点中newNode前驱节点为pred
        Node<E> newNode = new Node<>(pred, e, null);
        //如果前驱节点为null
        if (pred == null)
            //将插入的节点置为集合头节点
            first = newNode;
        else
            //前驱节点不为null,将前驱节点的后继节点置为插入的节点
            pred.next = newNode;
        //pred置为插入的节点,进入下个循环
        pred = newNode;
    }
    //succ是否为null,说明在结合尾部添加的所以尾节点就是pred
    if (succ == null) {
        //将最后的新节点置为尾节点
        last = pred;
    } else {
        //否则是在集合中插入的 那么前驱节点的后继节点就是succ succ是index处的节点
        pred.next = succ;
        //插入的最后的一个节点为succ的前驱节点
        succ.prev = pred;
    }
    //集合个数增加插入节点个数
    size += numNew;
    //对集合做出修改modCount自加保证集合版本一致
    modCount++;
    return true;
}
6.9 clear() 清除所有节点
public void clear() {
    //遍历所有节点将所有节点前驱节点,保存数据,后继节点都置为null
    for (Node<E> x = first; x != null; ) {
        Node<E> next = x.next;
        x.item = null;
        x.next = null;
        x.prev = null;
        x = next;
    }
    //头节点和尾节点置为null
    first = last = null;
    //集合长度为0
    size = 0;
    //对集合做出修改modCount自加保证集合版本一致
    modCount++;
}
6.10 get(int index) 获取指定索引处的节点
public E get(int index) {
    //检查index是否在0到size范围
    checkElementIndex(index);
    //调用node方法获取index处的节点的数据
    return node(index).item;
}
6.11 set(int index, E element) 对指定index处的节点修改
public E set(int index, E element) {
    //检查index是否在0到size范围
    checkElementIndex(index);
    //取得index处的节点
    Node<E> x = node(index);
    //取得原index处节点的数据
    E oldVal = x.item;
    //index处的节点数据置为element
    x.item = element;
    //将index处的原数据返回
    return oldVal;
}
6.12 add(int inedx, E element) 在指定索引位置添加数据
public void add(int index, E element) {
    //检查index是否在0到size范围内
    checkPositionIndex(index);
    //如果index等于size,那么在集合尾添加
    if (index == size)
        linkLast(element);
    else
        //调用linkBefore方法,指定位置前添加节点
        linkBefore(element, node(index));
}
6.13 remove(int index) 移除指定索引处的节点
public E remove(int index) {
    //检查index是否在0到size范围内
    checkElementIndex(index);
    //调用unLink方法解除一个节点
    return unlink(node(index));
}
6.14 poll() 返回头节点并且删除
public E poll() {
    final Node<E> f = first;
    //判断头节点是否为null,如果为null的话直接返回null,不是的话调用nulinkFirst方法删除并返回头节点
    return (f == null) ? null : unlinkFirst(f);
}
6.15 remove() 删除头节点并返回删除的头节点
public E remove() {
    return removeFirst();
}
6.16 offer() 在集合尾部添加元素
public boolean offer(E e) {
    return add(e);
}
6.17 offerFirst() 添加头节点
public boolean offerFirst(E e) {
    addFirst(e);
    return true;
}
6.18 offerLast() 添加尾节点
public boolean offerLast(E e) {
    addLast(e);
    return true;
}
6.20 pollFirst() 返回并移除头节点
public E pollFirst() {
    final Node<E> f = first;
    return (f == null) ? null : unlinkFirst(f);
}
6.21 pollLast() 返回并删除尾节点
public E pollLast() {
    final Node<E> l = last;
    return (l == null) ? null : unlinkLast(l);
}
6.22 push() 将元素插入到头节点
public void push(E e) {
    addFirst(e);
}
6.23 pop() 移除头节点并返回
public E pop() {
    return removeFirst();
}
6.24 removeFirstOccurrence() 移除第一次出现的某个元素
public boolean removeFirstOccurrence(Object o) {
    return remove(o);
}
6.25 removeLastOccurrence() 移除最后一次出现的某个元素
public boolean removeLastOccurrence(Object o) {
    //当要删除的元素为null
    if (o == null) {
        //从尾节点向前遍历集合
        for (Node<E> x = last; x != null; x = x.prev) {
            if (x.item == null) {
                //调用nulink方法删除节点
                unlink(x);
                return true;
            }
        }
    } else {
        //从后向前遍历集合
        for (Node<E> x = last; x != null; x = x.prev) {
            if (o.equals(x.item)) {
                unlink(x);
                return true;
            }
        }
    }
    //没有找到这个元素返回false
    return false;
}

7. 总结

7.1 底层实现

LinkedList底层是通过双向链表来实现的

7.2 线程安全?

LinkedList线程不安全。

7.3 增删和查找效率

对于双向链表来说增删效率很高,对于单向链表删除某个节点还需要从头遍历来说,效率高很多,对于要操作的节点直接使用unlink()方法就可以实现删除。而对于查找来说,由于LinkedList没有索引,所以无法快速获取某个节点,必须要遍历,尽管遍历的时候已经可以降低到O(N/2),但是相比数组直接通过索引快速获取来说还是效率低了很多。因此,需要经常插入增删的数据采用LinkedList保存更好。

7.4 LinkedList与ArrayList
  • ArrayList使用可变数组实现,LinkedList底层是通过双向链表来实现的。
  • 对于新增和删除操作add和remove,LinedList比较占优势,因为ArrayList要移动数据。
  • 对于随机访问get和set,ArrayList觉得优于LinkedList,因为LinkedList要移动指针。
  • ArrayList适合用于需要频繁查找和修改的环境,LinkedList则适合用于需要频繁增删的环境。

关于ArrayList的源码分析可以看这里

以上是关于Java集合源码分析之LinkedList的主要内容,如果未能解决你的问题,请参考以下文章

java集合之LinkedList源码分析

Java集合源码分析之 LinkedList

死磕 java集合之LinkedList源码分析

死磕 java集合之LinkedList源码分析

Java集合源码分析之LikedList

jdk源码阅读笔记之java集合框架(LinkedList)