美文网首页Java 杂谈
深入分析 LinkedList

深入分析 LinkedList

作者: Franck_ | 来源:发表于2018-06-14 17:54 被阅读5次

基于JDK 1.8.0。

简介:

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

特点:

  • 底层实现:双向链表
  • 线程安全:线程不安全。
  • 扩容:没有限制大小,随时增加元素,不需要扩容。
  • 是否可以存放null:可以存放null
  • 是否有序:
  • 效率:因为是链表的实现,添加和删除元素比较快,时间复杂度都是O(1)。查找元素比较慢,时间复杂度是O(n)
  • 是否可以重复:可以放入重复的元素。

源码分析:

LinkedList有3个最基础的属性: int size , Node<E> first , Node<E> last。 first 存放第一个元素。last存放最后一个元素。size存的是元素的个数。

Node是LinkedList的静态内部类。用于存放LinkedList的元素。Node类有3个属性item,next,prev。 item用于存放元素,next存放当前元素的下一个结点,prev存放当前元素的上一个结点。

 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有2个构造函数

  1. 默认的构造函数,构造出一个空的链表。
     /**
     * Constructs an empty list.
     */
    public LinkedList() {
    }



2.用一个集合作为构造函数的参数,构造出链表后,将集合添加入链表。

     /**
     * 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);
    }


在分析添加方法之前,有2个方法需要了解和分析的。一个是linkFirst(),另一个是linkLast()。

linkFirst方法是在当前链表的添加一个结点。

/**
     * Links e as first element.
     */
    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;

        //链表长度加1
        size++;
        //修改次数加1
        modCount++;
    }

linkFirst方法是在当前链表的添加一个结点。

    /**
     * Links e as last element.
     */
    void linkLast(E e) {
        //声明一个结点保存尾结点。
        final Node<E> l = last;

        //创建一个新结点,前驱结点为当前链表的尾结点,数据域为传入的元素,后继结点为空
        final Node<E> newNode = new Node<>(l, e, null);

        //将新结点设置为当前链表的尾结点
        last = newNode;

        //如果链表之前的尾结点为空,则说明链表是空链表,则将新结点也设置为头结点。因为当前链表只有1个结点,则头结点和尾结点都是同一个结点。
        if (l == null)
            first = newNode;
        else
            l.next = newNode;//链表不为空的话,则将新结点设置为之前尾结点的后继结点。
       
        //链表长度加1
        size++;
        //修改次数加1
        modCount++;
    }


添加元素

添加元素方法有两种,一种是添加单个元素,一种是添加一个集合。添加单个元素使用add()方法,add()方法有1个重载。 添加集合使用addAll()方法,addAll()方法也有1个重载。

  1. 添加一个元素,不指定位置。直接将元素添加到链表末端。因为是链表结构,所以不需要移动任何元素,添加的效率非常高。时间复杂度是O(1)。
     /**
     * Appends the specified element to the end of this list.
     *
     * <p>This method is equivalent to {@link #addLast}.
     *
     * @param e element to be appended to this list
     * @return {@code true} (as specified by {@link Collection#add})
     */
    public boolean add(E e) {
        linkLast(e);
        return true;
    }


  1. 将元素添加到指定的位置。
/**
     * Inserts the specified element at the specified position in this list.
     * Shifts the element currently at that position (if any) and any
     * subsequent elements to the right (adds one to their indices).
     *
     * @param index index at which the specified element is to be inserted
     * @param element element to be inserted
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public void add(int index, E element) {
        //检查添加的位置是否合法。index >= 0 && index <= size
        checkPositionIndex(index);
        
        //如果指定的位置是最后一位,则直接在链表尾部添加一个结点。
        if (index == size)
            linkLast(element);
        else
            //否则,则在指定的位置添加一个结点。
            linkBefore(element, node(index));
    }



3.在链表首部添加一个元素。

    /**
     * Inserts the specified element at the beginning of this list.
     *
     * @param e the element to add
     */
    public void addFirst(E e) {
        linkFirst(e);
    }
  1. 在链表尾部添加一个元素。
/**
     * 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);
    }

  1. 在指定的位置添加一个集合。
 /**
     * Inserts all of the elements in the specified collection into this
     * list, starting at the specified position.  Shifts the element
     * currently at that position (if any) and any subsequent elements to
     * the right (increases their indices).  The new elements will appear
     * in the list in the order that they are returned by the
     * specified collection's iterator.
     *
     * @param index index at which to insert the first element
     *              from the specified collection
     * @param c collection containing elements to be added to this list
     * @return {@code true} if this list changed as a result of the call
     * @throws IndexOutOfBoundsException {@inheritDoc}
     * @throws NullPointerException if the specified collection is null
     */
    public boolean addAll(int index, Collection<? extends E> c) {
        //检查添加的位置是否在范围之内。
        checkPositionIndex(index);
        
        //将需要添加的数组,转换成对象数组。
        Object[] a = c.toArray();

        //获取对象数组的长度。
        int numNew = a.length;

        //如果对象数组长度为0,则不用添加,直接返回false。
        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;
            //这里想不明白为什么后继结点为空,后面也没有进行设置了。
            Node<E> newNode = new Node<>(pred, e, null);
            if (pred == null)
                first = newNode;
            else
                pred.next = newNode;
            pred = newNode;
        }
        
        //如果后继结点为空,则尾结点=前驱结点。
        if (succ == null) {
            last = pred;
        } else {
            //否则,前驱结点的后继结点为后继结点
            pred.next = succ;
            //后继结点的前驱结点为前驱结点
            succ.prev = pred;
        }
        
        //链表的长度增加
        size += numNew;
        modCount++;//修改次数加1
        return true;
    }

  1. 添加一个集合到链表的尾端。其实就是调用添加集合到指定位置的方法,指定的位置为链表的尾端。
public boolean addAll(Collection<? extends E> c) {
        return addAll(size, c);
    }




在分析删除方法之前,有3个方法需要分析。其实这3个方法是删除元素方法的关键。
unlinkFirst(), unlinkLast(), unlink()。

unlinkFirst是删除链表首结点的方法。

/**
     * Unlinks non-null first node 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;//如果刚才获取的后继结点为空,则说明链表只有1个结点,并且删除了这个结点。所以链表的首结点和尾结点都为null。
        else
            next.prev = null;//否则,设置后继结点的前驱结点为空,因为这个结点为首结点了。

        //链表长度减1
        size--;
        modCount++;//修改次数加1
        return element;
    }



unlinkLast方法是删除链表的最后一个元素。

 /**
     * Unlinks non-null last node l.
     */
    private E unlinkLast(Node<E> l) {
        // assert l == last && l != null;
        //获取结点的数据域
        final E element = l.item;

        //获取结点的前驱结点
        final Node<E> prev = l.prev;

        //将数据域和前驱结点都设置为null,方便垃圾回收。
        l.item = null;
        l.prev = null; // help GC

        //将链表的尾结点设置为perv。
        last = prev;


        if (prev == null)
            first = null;        //如果perv为空的话,说明链表只有1个结点,并且要删除,所以首结点也为空。
        else
            prev.next = null;//否则,设置prev的后继结点为空,因为perv为尾结点了。

        //链表长度减1
        size--;

        //修改次数加1
        modCount++;
        return element;
    }



unlink方法为删除一个结点,这个结点不是首结点也不是尾结点。


    /**
     * Unlinks non-null node x.
     */
    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--;//链表大小减1
        modCount++;//修改次数加1
        return element;
    }



删除元素

删除元素有7个公开的方法。删除元素使用remove(), 实质就是删除链表的首结点。有2个重载。分别是remove(int index), 指定删除结点的索引,进行删除。 remove(Object o)删除传入的元素。删除首结点的的方法removeFirst()。 删除第一个匹配的结点removeFirstOccurrence(Object o)。删除尾结点removeLast()。 从尾部开始查找,删除第一个匹配的结点removeLastOccurrence(Object o)。

  1. 先看removeFirst(), 因为其他方法是基于这个方法的。
    /**
     * 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);
    }



2.remove(Object o)。根据传入的对象删除结点。


/**
     * Removes the first occurrence of the specified element from this list,
     * if it is present.  If this list does not contain the element, it is
     * unchanged.  More formally, removes the element with the lowest index
     * {@code i} such that
     * <tt>(o==null&nbsp;?&nbsp;get(i)==null&nbsp;:&nbsp;o.equals(get(i)))</tt>
     * (if such an element exists).  Returns {@code true} if this list
     * contained the specified element (or equivalently, if this list
     * changed as a result of the call).
     *
     * @param o element to be removed from this list, if present
     * @return {@code true} if this list contained the specified element
     */
    public boolean remove(Object o) {

        //这里要分开null和非null来写循环,是因为null需要用==判断。而对象则需要调用equals方法判断。

        if (o == null) {
            //如果要删除的对象为null, 则循环找到数据域为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;

    }


3.根据索引删除一个元素。方法比较简单,就判断一下传入的索引是否在范围内,不在就抛出异常, 如果在范围内, 就调用删除结点方法,将节点删除。

  /**
     * Removes the element at the specified position in this list.  Shifts any
     * subsequent elements to the left (subtracts one from their indices).
     * Returns the element that was removed from the list.
     *
     * @param index the index of the element to be removed
     * @return the element previously at the specified position
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E remove(int index) {
        checkElementIndex(index);
        return unlink(node(index));
    }




4.根据元素对象删除元素。


 /**
     * Removes the first occurrence of the specified element from this list,
     * if it is present.  If the list does not contain the element, it is
     * unchanged.  More formally, removes the element with the lowest index
     * <tt>i</tt> such that
     * <tt>(o==null&nbsp;?&nbsp;get(i)==null&nbsp;:&nbsp;o.equals(get(i)))</tt>
     * (if such an element exists).  Returns <tt>true</tt> if this list
     * contained the specified element (or equivalently, if this list
     * changed as a result of the call).
     *
     * @param o element to be removed from this list, if present
     * @return <tt>true</tt> if this list contained the specified element
     */
    public boolean remove(Object o) {
        //如果传过来的对象是null。
        if (o == null) {
            //循环当前的数组,找到等于null的元素,并删除。
            for (int index = 0; index < size; index++)
                if (elementData[index] == null) {
                    fastRemove(index);
                    return true;
                }
        } else {

            //如果对象不是null。循环当前数组,调用equals方法找到元素,并删除。
            for (int index = 0; index < size; index++)
                if (o.equals(elementData[index])) {
                    fastRemove(index);
                    return true;
                }
        }
        //如果找到元素并删除,就会在之前返回true。如果都没有找到的话。则没有对应的元素,返回false。
        return false;
    }



5.remove方法,这个方法比较简单。就是直接调用removeFirst方法删除首结点。

 /**
     * Retrieves and removes the head (first element) of this list.
     *
     * @return the head of this list
     * @throws NoSuchElementException if this list is empty
     * @since 1.5
     */
    public E remove() {
        return removeFirst();
    }




6.removeLast方法, 删除尾结点。这个方法也简单,就获取尾结点,判断如果尾结点不为空,就删除尾结点。


    /**
     * 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);
    }


  1. removeLastOccurrence方法, 根据传入的元素,从链表尾部开始查找,找到第一个匹配的结点, 就删除。

 /**
     * Removes the last occurrence of the specified element in this
     * list (when traversing the list from head to tail).  If the list
     * does not contain the element, it is unchanged.
     *
     * @param o element to be removed from this list, if present
     * @return {@code true} if the list contained the specified element
     * @since 1.6
     */
    public boolean removeLastOccurrence(Object o) {

        if (o == null) {
            //如果参数是null。 则从尾部开始循环查找null。找到后将该结点移出链表。
            for (Node<E> x = last; x != null; x = x.prev) {
                if (x.item == null) {
                    unlink(x);
                    return true;
                }
            }
        } else {
            //如果参数不是null。 则从为不开始循环查找该元素。找到后,将该元素对应的结点移出链表。
            for (Node<E> x = last; x != null; x = x.prev) {
                if (o.equals(x.item)) {
                    unlink(x);
                    return true;
                }
            }
        }
        return false;
    }


  1. clear() 。 清空方法。

 /**
     * Removes all of the elements from this list.
     * The list will be empty after this call returns.
     */
    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 
        //修改次数加1
        modCount++;
    }


获取元素

LinkedArray的优势在于添加和删除元素,只需要改动结点的前驱、后继结点和数据域赋值就可以了。 由于底层是双向链表实现。所以获取结点的效率并不高,时间复杂度是O(n)。

获取方法是基于node这个方法的。

 /**
     * Returns the (non-null) Node at the specified element index.
     */
    Node<E> node(int index) {
        // assert isElementIndex(index);
          
        //这里是为了提升获取的效率,就判断如果需要获取的元素的索引靠近开头,就从链表首部开始查找,如果靠近尾部,则从尾部开始查找。
        //但是这里我想不明白,为什么要用移位运算,size是int类型。使用移位运算的话有可能会导致溢出。感觉直接size/2 更加准确。
        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;
        }
    }

  1. get方法。需要传一个索引,来表明需要获取的是哪个元素。方法实现比较简单,首先检查索引是否越界。越界的话就抛出异常, 没有的话就直接调用node方法获取结点,然后返回结点的数据域。
/**
     * Returns the element at the specified position in this list.
     *
     * @param index index of the element to return
     * @return the element at the specified position in this list
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E get(int index) {
        checkElementIndex(index);
        return node(index).item;
    }

  1. 获取链表的首结点。实现很简单,就判断首结点是否为空,为空就抛出异常,不为空就返回首结点的数据域。
/**
     * 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;
    }
  1. 获取链表的尾结点。实现很简单,就判断尾结点是否为空,为空就抛出异常,不为空就返回尾结点的数据域。
/**
     * 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;
    }


总结

LinkedList 没有实现同步,所以不是线程安全的。

相关文章

网友评论

    本文标题:深入分析 LinkedList

    本文链接:https://www.haomeiwen.com/subject/bckseftx.html