zoukankan      html  css  js  c++  java
  • LinkedList学习笔记

    一、前言

      前面文章写了关于ArrayList的源码解读,今天也正好把LinkedList一些方法的源码也研究一下。

    二、LinkedList特点

      基于双向列表,查询速度慢,增删改速度快

    三、LinkedList的继承实现关系

    public class LinkedList<E>
            extends AbstractSequentialList<E>
            implements List<E>, Deque<E>, Cloneable, java.io.Serializable 
    
    • LinkedList 是一个继承于AbstractSequentialList的双向链表。它也可以被当作堆栈、队列或双端队列进行操作。
    • LinkedList 实现 List 接口,能对它进行队列操作。
    • LinkedList 实现 Deque 接口,即能将LinkedList当作双端队列使用。
    • LinkedList 实现了Cloneable接口,即覆盖了函数clone(),能克隆。
    • LinkedList 实现java.io.Serializable接口,这意味着LinkedList支持序列化,能通过序列化去传输。
    • LinkedList 是非同步的。

    四、LinkedList类属性和Node节点

      //记录链表实际长度
        transient int size = 0;
    
        //记录整条链表的头结点
        transient Node<E> first;
    
        //记录整条链表的尾节点
        transient Node<E> last;
    
        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;
            }
        }
    

    五、LinkedList构造方法

    五、LinkedList构造方法
    
     
    
    public LinkedList() {
        }
    
    //使用一个集合来当做参数的构造器
        public LinkedList(Collection<? extends E> c) {
            this();
            addAll(c);
        }
    
       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;
    //数组的长度为0直接返回
            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;
    //构造一个后继节点为null的节点
                Node<E> newNode = new Node<>(pred, e, null);
                if (pred == null)
    //前驱节点为null,说明在头节点之前插入,newNode指向头节点
                    first = newNode;
                else
    //不是头节点之前插入的,newNode节点就是前驱节点的后继节点
                pred.next = newNode;
    //newNode就是前驱节点
                pred = newNode;
            }
    //同理
            if (succ == null) {
                last = pred;
            } else {
                pred.next = succ;
                succ.prev = pred;
            }
    
            size += numNew;
            modCount++;
            return true;
        }
    
    

    六、add、offer基础方法分析

    //从头部增加
        private void linkFirst(E e) {
    //获取头节点
            final Node<E> f = first;
    //构建一个prev值为null,next为f,内容为e的节点
            final Node<E> newNode = new Node<>(null, e, f);
    //将newNode作为首节点
            first = newNode;
            if (f == null)
    //f为null,说明是原链表是空链表,newNode节点既是头节点也是尾结点
                last = newNode;
            else
    //如果不为null,newNode节点就为原链表的上一节点
                f.prev = newNode;
            size++;
            modCount++;
        }
    
    //从尾部增加
        public void linkLast(E e) {
    //获取尾节点
            final Node<E> l = last;
    //构造一个以尾部为前驱节点创建一个新节点
            final Node<E> newNode = new Node<>(l, e, null);
    //newNode作为尾结点
            last = newNode;
            if (l == null)
     ////l为null,链表为空,newNode节点既是头节点也是尾结点
                first = newNode;
            else
    //当前节点为原链表尾结点的下一节点
                l.next = newNode;
            //容量加一
            size++;
            //修改操作数+1
            modCount++;
        }
    
    //在一个非空节点之前插入一个节点
        void linkBefore(E e, Node<E> succ) {
    //获取当前节点的前置节点
            final Node<E> pred = succ.prev;
    //构建一个新的节点
            final Node<E> newNode = new Node<>(pred, e, succ);
    //succ的前置节点改为当前节点
            succ.prev = newNode;
            if (pred == null)
    //succ的前置节点为空,newNode设置为头节点
                first = newNode;
            else
    //succ的前置节点不为空,newNode设置为succ前置节点的下一节点
                pred.next = newNode;
            size++;
            modCount++;
        }
    

    结论:增加都是在头部、中间、尾部增加一个双端节点的操作。但是需要注意添加时候对头节点和尾结点为空的处理。final Node f = first; first = newNode;(当时看源码的时候有点晕,后来自己写了一个差不多的才理解,final修饰的不可修改,所以first = newNode的时候并没有修改f的值)

    七、查找方法分析

    //根据索引位置取值
        public E get(int index) {
    //检查索引的边界是否在0-size之间
            checkElementIndex(index);
    //获取值返回
            return node(index).item;
        }
    
        Node<E> node(int 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;
            }
        }
    
        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;
        }
    

    结论:获取头节点和尾结点的时候如果链表为null都是会抛出异常,element()、peek()、peekFirst()、peekLast()。这些方法都大致相同

    八、poll、remove方法分析

    //删除指定对象,都是从前向后遍历,找到匹配对象调用unlink()方法
      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;
        }
    
    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;
                x.prev = null;
            }
    //删除后继节点
            if (next == null) {
    //删除的是尾结点,就设置前驱节点为尾结点
                last = prev;
            } else {
    //该节点后继节点的前驱节点指向该节点的前驱节点
                next.prev = prev;
                x.next = null;
            }
    //内容置null
            x.item = null;
            size--;
            modCount++;
            return element;
        }
    //删除该节点需要将当前节点的内容前驱节点后继节点置空,并且需要将该节点的前驱节点的后继节点指向该节点的后句节点,该节点的后继节点也是同样的道理。
    
    //根据索引删除,先判断index的范围
      public E remove(int index) {
            checkElementIndex(index);
            return unlink(node(index));
        }
    
    //删除头节点
        public E removeFirst() {
            final Node<E> f = first;
            if (f == null)
                throw new NoSuchElementException();
            return unlinkFirst(f);
        }
    
       private E unlinkFirst(Node<E> f) {
        
            final E element = f.item;
    //获取该节点的后继节点
            final Node<E> next = f.next;
            f.item = null;
            f.next = null; // help GC
    //头节点执行该节点的后继节点
            first = next;
            if (next == null)
    //如果next为空,说明链表已经为null了,将尾结点设置为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);
        }
    
     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;
        }
    
    // poll和pollFirst调用的方法相同
    public E poll() {
            final Node<E> f = first;
            return (f == null) ? null : unlinkFirst(f);
        }
    
        public E pollFirst() {
            final Node<E> f = first;
            return (f == null) ? null : unlinkFirst(f);
        }
    
        public E pollLast() {
            final Node<E> l = last;
            return (l == null) ? null : unlinkLast(l);
        }
    
    //他们三个调用的方法都是unlinkFirst,unlinkLast。上面也已经说过了,这里就不做累述了
    

    九、总结

     LinkedList存储的元素就是一个一个的节点,每个节点记录着前面和后面节点的信息。所以在查询的时候需要前置或者后继以此查找,ArrayList根据下边直接查找,但是删除的时候ArrayList需要移动元素然后置空,但是LinkedList只需要将修改前驱和后继节点,然后置空当前节点的内容就行了。

  • 相关阅读:
    xxx.app已损坏,打不开.你应该将它移到废纸篓-已解决
    idea 配置maven一直停留在loading archetype list
    pom.xml 识别xml文件
    idea .defaultMessage
    处理GitHub上的不允许100MB大文件上传
    Makefile 简易教程
    Android:用Intent传送图片
    Android: ListView的使用(列表控件)
    Android: SharedPreferences的简单使用(数据可持久化)
    Andriod:一个Activity向另一个Activity传递数据
  • 原文地址:https://www.cnblogs.com/yangk1996/p/12653398.html
Copyright © 2011-2022 走看看