zoukankan      html  css  js  c++  java
  • java源码阅读LinkedList

    1类签名与注释

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

    双向链表实现了ListDeque接口。 实现所有可选列表操作,并允许所有元素(包括null )。

    请注意,此实现不同步。 如果多个线程同时访问链接列表,并且至少有一个线程在结构上修改列表,则必须在外部进行同步。 (结构修改是添加或删除一个或多个元素的任何操作;仅设置元素的值不是结构修改。)这通常通过在自然封装列表的对象上进行同步来实现。 如果没有这样的对象存在,列表应该使用Collections.synchronizedList方法“包装”。 这最好在创建时完成,以防止意外的不同步访问列表:

     List list = Collections.synchronizedList(new LinkedList(...)); 

    这个类的iteratorlistIterator方法返回的迭代器是故障快速的:迭代器创建之后,除了自己的remove和add方法外的任何方法改变了集合的结构,迭代器会抛出ConcurrentModificationException异常。

    注意:LinkedList实现了Deque接口,而Deque又继承了Queue接口,所以LinkedList可以当作队列(Queue)来使用。

    2数据结构

    LinkedList的基本属性有三个

    transient int size = 0;
    
    transient Node<E> first;
    
    transient Node<E> last;

    size:集合中元素的个数

    first:链表的第一个节点

    last:链表的最后一个节点

    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类维护了本节点的元素item,以及前向节点prev和后向节点next的引用。

    3添加元素

    (1)add(E e)

    将指定的元素追加到此列表的末尾。

     1 public boolean add(E e) {
     2         linkLast(e);
     3         return true;
     4     }
     5 
     6 void linkLast(E e) {
     7         final Node<E> l = last;
     8         final Node<E> newNode = new Node<>(l, e, null);
     9         last = newNode;
    10         if (l == null)
    11             first = newNode;
    12         else
    13             l.next = newNode;
    14         size++;
    15         modCount++;
    16     }

    (2)add(int index, E element)

    在此列表中的指定位置插入指定的元素。

    public void add(int index, E element) {
            checkPositionIndex(index);
    
            if (index == size)
                linkLast(element);
            else
                linkBefore(element, node(index));
        }
    
    //检查索引是否越界(0<=index<=size)
    private void checkPositionIndex(int index) {
            if (!isPositionIndex(index))
                throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
        }
    private boolean isPositionIndex(int index) {
            return index >= 0 && index <= size;
        }
    
    //在节点succ前插入新节点e
    void linkBefore(E e, Node<E> succ) {
            // assert succ != null;
            final Node<E> pred = succ.prev;
            final Node<E> newNode = new Node<>(pred, e, succ);
            succ.prev = newNode;
            if (pred == null)
                first = newNode;
            else
                pred.next = newNode;
            size++;
            modCount++;
        }
    
    //返回对应索引位置的节点
    Node<E> node(int index) {
            // assert isElementIndex(index);
    
            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;
            }
        }

    node方法做了优化,当index小于size/2时,从first开始往后遍历,否则从last开始往前遍历。这里是一个简单的二分思路。

    (3)addFirst(E e)

    在该列表开头插入指定的元素。

    public void addFirst(E e) {
            linkFirst(e);
        }
    
    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++;
        }

    4)addLast(E e)

    将指定的元素追加到此列表的末尾。
    public void addLast(E e) {
            linkLast(e);
        }
    
    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++;
        }

    4删除元素

    (1)remove()

    检索并删除此列表的头(第一个元素)。
    public E remove() {
            return removeFirst();
        }
    
    public E removeFirst() {
            final Node<E> f = first;
            if (f == null)
                throw new NoSuchElementException();
            return unlinkFirst(f);
        }
    
    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;
        }

    (2)remove(int index)

    删除该列表中指定位置的元素。
    public E remove(int index) {
            checkElementIndex(index);
            return unlink(node(index));
        }
    
    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;
        }

    (3)removeFirst()

    从此列表中删除并返回第一个元素。

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

    (4)removeLast()

    从此列表中删除并返回最后一个元素。
    public E removeLast() {
            final Node<E> l = last;
            if (l == null)
                throw new NoSuchElementException();
            return unlinkLast(l);
        }
    
    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;
        }

    还有其他一些删除方法(如下所示)这里就不放代码,感兴趣的可以自己去看一下。

    boolean remove(Object o) 
    从列表中删除指定元素的第一个出现(如果存在)。  
    
    boolean removeFirstOccurrence(Object o) 
    删除此列表中指定元素的第一个出现(从头到尾遍历列表时)。  
    
    boolean removeLastOccurrence(Object o) 
    删除此列表中指定元素的最后一次出现(从头到尾遍历列表时)。  

    5 LinkedList当作队列使用

    (1)入队 offer

    将指定的元素添加为此列表的尾部(最后一个元素)。

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

    offer直接调用add(e)在队列的末尾添加一个元素。

    (2)出队 poll

    检索并删除此列表的头(第一个元素)。
    public E poll() {
            final Node<E> f = first;
            return (f == null) ? null : unlinkFirst(f);
        }

    简单使用如下

    Queue<Integer> queue = new LinkedList<>();
    for(int i = 1 ; i < 5 ; i++){
        queue.offer(i);
    }
    System.out.println(queue.poll());
    for(int i : queue){
        System.out.print(i+" ");
    }

    输出

    1
    2 3 4 

    (3)查看队头元素 peek()

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

    6 LinkedList当作栈来使用

    (1)入栈 push(E e)

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

    (2)出栈 pop()

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

    (3)查看栈顶元素 peek()

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

    当作栈使用的时候,链表的表头相当于栈顶,每次入栈都是往表头插入,每次出栈都是删除表头指向的节点。简单使用如下

    LinkedList<Integer> stack = new LinkedList<>();
    for(int i = 1 ; i < 5 ; i++){
        stack.push(i);
    }
    System.out.println(stack.pop());
    for(int i : stack){
        System.out.print(i+" ");
    }

    输出

    4
    3 2 1 

    7总结

    简单来讲,LinkedList就是一个双向链表。分析一下数组与链表各自的优缺点,就可以很清楚的知道什么时候用ArrayList什么时候该用LinkedList。

    链表的优势在于增加和删除节点。而数组的优势在于遍历。

  • 相关阅读:
    Nginx 静态站点配置不对导致301跳转的坑
    Prometheus(一):Web服务环境监控
    10BASE-T
    计算机网络之物理层:7、物理层设备(中继器、集线器)
    广域网
    转载------对比网络模拟器软件——Cisco Packet Tracer、华为eNSP、H3C Cloud Lab
    二、Python的使用
    MobSF移动安全扫描平台本地化部署与简单汉化
    MobSF移动安全扫描平台环境搭建与试用
    BSTestRunner增加历史执行记录展示和重试功能
  • 原文地址:https://www.cnblogs.com/ouym/p/9019501.html
Copyright © 2011-2022 走看看