zoukankan      html  css  js  c++  java
  • 【深入Java基础】LinkedList源码分析

    LinkedList源码分析


    LinkedList是基于链表实现的。适合大量数据的插入、修改以及删除。

    链表节点定义

    这是一个双向链表,有前驱节点和后继节点。

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

    全局变量

    虽然这不是用指针实现的,但是为了表述还是用“指针”和“指向”来说明(也可以说为引用)。

        transient int size = 0;//链表元素个数
    
        transient Node<E> first;//头指针
    
        transient Node<E> last;//尾指针

    构造方法

    有两个构造方法。

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

    添加头节点

        private void linkFirst(E e) {
            final Node<E> f = first;//f指向头节点
            final Node<E> newNode = new Node<>(null, e, f);//创建新节点,元素为e,新节点的前驱节点为null,后继节点为f
            first = newNode;//头指针指向新节点
            if (f == null)
                last = newNode;//如果原头节点为空(说明链表为空),则将尾指针指向新节点
            else
                f.prev = newNode;//否则原节点的前驱节点指向新节点
            size++;//元素数量加1
            modCount++;//链表结构改变次数加1
        }

    添加尾节点

    这里并没有用private修饰,而添加头节点确用了private,不知道为什么。

        void linkLast(E e) {
            final Node<E> l = last;//l指向尾节点
            final Node<E> newNode = new Node<>(l, e, null);//创建新节点,元素为e,新节点的前驱节点为l,后继节点为null
            last = newNode;//尾指针指向新节点
            if (l == null)
                first = newNode;//如果原尾节点为空(说明链表为空),则将头指针指向新节点
            else
                l.next = newNode;//否则原尾节点的后继节点指向新节点
            size++;//元素个数加1
            modCount++;//链表结构改变次数加1
        }

    在非空节点前插入新节点

        void linkBefore(E e, Node<E> succ) {
            // assert succ != null;//声明succ不为空
            final Node<E> pred = succ.prev;//succ的前驱节点
            final Node<E> newNode = new Node<>(pred, e, succ);//新节点,前驱指向pred,后继指向succ
            succ.prev = newNode;//succ的前驱指向新节点
            if (pred == null)
                first = newNode;//如果前驱为null,则当前插入的新节点为头节点,头指针指向它
            else
                pred.next = newNode;//否则pred的next指向新节点
            size++;
            modCount++;
        }

    删除头节点和尾节点

         /**
         * 删除头节点
         */
        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;
        }
    
        /**
         * 删除尾节点
         */
        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;
        }

    删除非空节点

    对于非空节点的删除的判断较多。

        E unlink(Node<E> x) {
            // assert x != null;
            final E element = x.item;//当前元素
            final Node<E> next = x.next;//x的前驱节点
            final Node<E> prev = x.prev;//x的后继节点
    
            if (prev == null) {
                first = next;//如果前驱节点为空,则当前节点为头节点,删除当前节点x后,头指针指向x的后继
            } else {
                prev.next = next;//否则x的前驱节点的后继指向x的后继节点
                x.prev = null;//x的前驱指向null
            }
    
            if (next == null) {
                last = prev;//如果后继节点为空,则当前节点为尾节点,删除当前节点x后,尾指针指向x的前驱节点
            } else {
                next.prev = prev;/否则x的后继节点的前驱指向x的前驱节点
                x.next = null;//x的后继指向null
            }
    
            x.item = null;//x的元素赋值为null便于GC回收空间
            size--;
            modCount++;
            return element;
        }

    LinkedList主要的链表操作就这么多,对外公开的方法都是基于以上这些操作的,以下是源码:

         /**
         * Returns the first element in this list.
         *
         * @return the first element in this list
         * @throws NoSuchElementException if this list is empty
         */
           public E getFirst() {
            final Node<E> f = first;
            if (f == null)
                throw new NoSuchElementException();
            return f.item;
        }
    
        /**
         * Returns the last element in this list.
         *
         * @return the last element in this list
         * @throws NoSuchElementException if this list is empty
         */
        public E getLast() {
            final Node<E> l = last;
            if (l == null)
                throw new NoSuchElementException();
            return l.item;
        }
    
        /**
         * Removes and returns the first element from this list.
         *
         * @return the first element from this list
         * @throws NoSuchElementException if this list is empty
         */
        public E removeFirst() {
            final Node<E> f = first;
            if (f == null)
                throw new NoSuchElementException();
            return unlinkFirst(f);
        }
    
        /**
         * Removes and returns the last element from this list.
         *
         * @return the last element from this list
         * @throws NoSuchElementException if this list is empty
         */
        public E removeLast() {
            final Node<E> l = last;
            if (l == null)
                throw new NoSuchElementException();
            return unlinkLast(l);
        }
    
        /**
         * Inserts the specified element at the beginning of this list.
         *
         * @param e the element to add
         */
        public void addFirst(E e) {
            linkFirst(e);
        }
    
        /**
         * Appends the specified element to the end of this list.
         *
         * <p>This method is equivalent to {@link #add}.
         *
         * @param e the element to add
         */
        public void addLast(E e) {
            linkLast(e);
        }

    以上操作的时间复杂度都是O(1),所以LinkedList对头/尾节点的插入删除操作效率还是比较高的。但是对于查找,以及修改等操作的就不高效了,时间复杂度变为O(n)

            public E set(int index, E element) {
            checkElementIndex(index);
            Node<E> x = node(index);
            E oldVal = x.item;
            x.item = element;
            return oldVal;
        }

    查找元素

        public E get(int index) {
            checkElementIndex(index);
            return node(index).item;
        }
    
        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;
            }
        }

    添加元素

       public void add(int index, E element) {
            checkPositionIndex(index);
    
            if (index == size)
                linkLast(element);
            else
                linkBefore(element, node(index));
        }

    修改元素

       public E set(int index, E element) {
            checkElementIndex(index);
            Node<E> x = node(index);
            E oldVal = x.item;
            x.item = element;
            return oldVal;
        }

    删除元素

        public E remove(int index) {
            checkElementIndex(index);
            return unlink(node(index));
        }

    对于LinkedList和ArrayList的效率总结如下:

    **直接添加元素:**LinkedList和ArrayList都是在尾部添加的。对于ArrayList来说,因为自动增长机制,在数组复制上会花费大量时间,所以数据量大的情况下,LinkedList的添加效率更高。

    指定位置插入/删除/修改元素:这种方式下,两种方式都要先找到指定位置的元素,然后再进行插入操作,时间复杂度都为O(n)。但是ArrayList还需要数组复制,所以会相对慢一些。

    随机访问:基于数组的ArrayList的随机访问时间远小于LinkedList 的,因为LinkedList需要移动指针。


    所以数据不是经常变动时,用ArrayList好,而需要平凡改动时,用LinkedList好。如果需要线程同步,则使用Vector,因为ArrayList和LinkedList都不是同步的

    另外LinkedList经常被用作队列及栈使用

    实现队列操作:

       public E peek() {//查看第一个,但不删除
            final Node<E> f = first;
            return (f == null) ? null : f.item;
        }
    
        public E element() {//查看第一个,但不删除
            return getFirst();
        }
    
        public E poll() {//获取第一个,并且删除
            final Node<E> f = first;
            return (f == null) ? null : unlinkFirst(f);
        }
    
        public E remove() {//获取第一个并且删除
            return removeFirst();
        }
    
        public boolean offer(E e) {//在队列尾部添加一个
            return add(e);
        }

    实现双向队列操作:

    K

    实现栈的操作:

        public E poll() {//查看栈顶但不删除
            final Node<E> f = first;
            return (f == null) ? null : unlinkFirst(f);
        }
    
        public void push(E e) {//压栈
            addFirst(e);
        }
    
        public E pop() {//出栈
            return removeFirst();
        }

    两篇参考文章:

  • 相关阅读:
    CF1539 VP 记录
    CF1529 VP 记录
    CF875C National Property 题解
    CF1545 比赛记录
    CF 1550 比赛记录
    CF1539E Game with Cards 题解
    CF1202F You Are Given Some Letters... 题解
    vmware Linux虚拟机挂载共享文件夹
    利用SOLR搭建企业搜索平台 之九(solr的查询语法)
    利用SOLR搭建企业搜索平台 之四(MultiCore)
  • 原文地址:https://www.cnblogs.com/cnsec/p/13286736.html
Copyright © 2011-2022 走看看