美文网首页
LinkedList源码解析

LinkedList源码解析

作者: 代码potty | 来源:发表于2018-09-11 22:15 被阅读0次

    先对LinkedList的特性进行一个概述:
    (1)LinkedList底层实现为双向循环链表。链表的特点就是插入删除数据快,而查询数据慢。

    (2)因为使用链表的原因,所以不存在容量不足的问题,没有扩容机制。

    (3)从后面的源码分析中我们也可以看出,LinkedList支持null并且LinkedList没有同步机制。

    (4)LinkedList直接继承于AbstractSequentialList,同时实现了List接口,也实现了Deque接口。

    AbstractSequentialList为顺序访问的数据存储结构提供了一个骨架类实现,如果要支持随机访问,则优先选择AbstractList类继承。LinkedList 基于链表实现,因此它继承了AbstractSequentialList。本文原创,转载请注明出处:[http://blog.csdn.net/seu_calvin/article/details/53012654]

    LinkedList的数据存储格式:

        private static class Node<E>{
                E item;
                Node<E> prev;
                Node<E> next;
        
                public Node(Node<E> prev,E item,Node<E> next){
                    this.prev = prev;
                    this.item = item;
                    this.next = next;
                }
            }
    

    LinkedList中定义了三个临时的变量

     transient int size = 0;//链表大小
     transient Node<E> first;//头节点
     transient Node<E> last; //尾节点
    

    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;
                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++;
                return true;
            }
    

    可以看到在有参的构造方法中,首先先调用了无参的构造方法,然后调用addAll()方法,下边讲解下这个方法,首先判断第一个参数index的大小,如果大于size或者小于0则抛出越界异常,然后设置了两个Node类型的量,pred表示插入集合C的头节点的前一个节点,succ表示插入集合C的尾节点的后一个节点,如果index==size,说明插入的位置是在链表的尾部,那么头结点就是原来的last,尾节点就是Null;否则,则是中间插入的方式,那么首先将该节点的头尾节点分别赋值给pred和succ,然后使用一个for循环输出集合C中的数据,每个都使用Node进行包装,这部分只要注意pred在里边类似于指针的作用,每次循环,pred 都将指向下一个节点;这样循环到最后,还要对插入位置进行判断,如果succ为Null,则是在链表尾插入,则直接设置last=pred,否则将pred.next=succ;succ.prev = pred;对插入节点尾部进行一个头尾的指针指向。

    LinkedList的一些内部方法,仅供内部方法调用
    private void linkFirst(E e)//插入头结点
    void linkLast(E e)//插入尾节点
    void linkBefore(E e, Node<E> succ)//在某个节点前插入
    private E unlinkFirst(Node<E> f)//删除头结点
    private E unlinkLast(Node<E> l)//删除尾节点
    E unlink(Node<E> x)//删除某个节点

      //插入头结点
      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++;
      }
    
      //插入尾节点
      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++;
      }
    
      //在某个节点前插入
      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++;
      }
        //删除头结点
        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;
            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;
        }
    

    注意一个点,添加和删除节点,都会对first和last进行相关的处理工作,并且这些方法中创建的节点都是final的。

    LinkedList常用的一些方法:
    public E getFirst()
    public E getLast()
    public E removeFirst()
    public E removeLast()
    public void addFirst(E e)
    public void addLast(E e)

    这六个方法是LinkedList中对于首尾节点的操作方法,直接调用上面的内部方法就能够解决了。
    下面介绍几个其他的方法:

      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的中间值进行了判断,如果index<size的中间值,则只需遍历左半边的,如果index>size的中间值,则遍历右半边。这样在遍历右半边的时候,将大大的提高程序的执行效率。

    public int indexOf(Object o)和方法 public int lastIndexOf(Object o)类似,前一个是从头找到跟o数据匹配的节点索引,后一个是从尾找到跟o数据匹配的节点索引,这边就将第一个方法吧,代码如下:

        public int indexOf(Object o) {
            int index = 0;
            if (o == null) {
                for (Node<E> x = first; x != null; x = x.next) {
                    if (x.item == null)
                        return index;
                    index++;
                }
            } else {
                for (Node<E> x = first; x != null; x = x.next) {
                    if (o.equals(x.item))
                        return index;
                    index++;
                }
            }
            return -1;
        }
    

    看到这段代码有没有点熟悉感,没错,先进行了o是否是空值的判断,空值用==进行比较,非空值用equals进行比较,最后返回Index的值大小。

    对List的其他相关方法总结一下:
    public boolean contains(Object o)//集合中是否包含o数据
    public boolean add(E e)//从尾节点新增一个节点,数据为e
    public boolean remove(Object o)//从头开始,删除一个集合中数据为o的节点
    public void clear()//清空集合所有的节点
    public E get(int index)//获取索引位置为Index的数据信息
    public E set(int index, E element)//设置Index位置的节点数据为element
    public void add(int index, E element)//在index位置新增一个节点数据为element
    public E remove(int index)//删除Index位置的节点
    public boolean removeFirstOccurrence(Object o)//里边调用的是remove(Object o)的方法,跟这个方法作用相同
    public boolean removeLastOccurrence(Object o)//从尾开始,删除一个集合中数据为o的节点。

    关于list的方法方面讲完了,下面介绍LinkedList实现deque和stack的方法:

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

    peek()方法和element()方法都是 获取但不移除第一个元素,区别在于,如果第一个元素为空,peek返回Null,而element抛出异常

        public E poll() {
            final Node<E> f = first;
            return (f == null) ? null : unlinkFirst(f);
        }
    
      
        public E remove() {
            return removeFirst();
        }
    

    poll()方法和remove()方法都是获取并且会移除第一个元素,区别在于,如果第一个元素为空,poll()返回Null,而remove抛出异常。

        public E peekFirst() {
            final Node<E> f = first;
            return (f == null) ? null : f.item;
         }
    
        public E peekLast() {
            final Node<E> l = last;
            return (l == null) ? null : l.item;
        }
    
        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);
        }
    

    前两个方法获取头尾节点的值但不移除头尾节点,如果为空则返回null,
    后两个方法获取头尾节点的值,并且会移除头尾节点,如果为空则返回null

    public void push(E e) {
        addFirst(e);
    }
    
    public E pop() {
        return removeFirst();
    }
    

    这两个方法是属于stack方面的,入栈和出栈,符合先进先出的规则,如果遇到空元素则抛出异常

    这两天研究了ArrayList和LinkedList的源码,下面对这两个集合类进行对比
    共同点:
    1、都是线程不安全的集合
    2、都支持null值
    3、都实现了List接口

    不同点:
    1、ArrayList的底层是用数组实现的,每次增加数据都要考虑扩容的情况,LinkedList底层是通过链表实现的,不需要考虑扩容的情况
    2、LinkedList 基于链表实现,便于顺序访问,它继承了AbstractSequentialList。而ArrayList支持随机访问,继承了AbstractList类
    3、通过源码我们知道,如果要对数据进行查询,那么ArrayList比较合适,因为ArrayList是通过数组实现的,那么只需要获取索引就可以获取该值,但是LinkedList需要从头或尾遍历到值才行;如果要对数据进行增删操作,则LinkedList比较合适,通过代码我们知道,ArrayList需要通过System.arraycopy移动数组来实现,而LinkedList只需要修改前后的指针指向就可以完成增删操作,所以在增删操作上,LinkedList比较合适,当然如果是在头部或者尾部,二者的效率没有多大的区别

    参考链接:
    https://blog.csdn.net/wangbiao007/article/details/52539061
    https://blog.csdn.net/seu_calvin/article/details/53012654

    相关文章

      网友评论

          本文标题:LinkedList源码解析

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