zoukankan      html  css  js  c++  java
  • LinkedList原理及源码解析

    简介

    LinkedList是一个双向线性链表,但是并不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的指针(Pointer)。由于不必须按顺序存储,链表在插入的时候可以达到O(1)的复杂度,比另一种线性表顺序表快得多,但是查找一个节点或者访问特定编号的节点则需要O(n)的时间,而顺序表相应的时间复杂度分别是O(logn)和O(1)。

    image

    UML关系图

    image

    使用示例

    LinkedList list = new LinkedList<>();
    //新增
    list.add("a");
    list.add("a");
    list.add("b");
    System.out.println(list);
    
    //删除
    list.remove("a");//删除第一个对应对象
    list.remove(0);//删除下标为0的元素
    list.removeFirst();//删除第一个元素
    list.removeLast();//删除最后一个元素
    System.out.println(list);
    
    //修改
    list.set(0,"c");//修改下标为0的元素
    System.out.println(list);
    
    //插入 
    list.add(1,"a");//只能在已有元素的前后或中间位置插入
    System.out.println(list);
    
    //获取
    list.get(0);//根据下标获取元素
    list.getFirst();//获取第一个元素
    list.getLast();//获取最后一个元素
    
    //循环
    //普通循环
    for(int i=0;i<list.size();i++){
        System.out.println(list.get(i));
    }
    //foreach循环
    for(Object o:list){
        System.out.println(o);
    }
    //迭代循环
    Iterator iterator = list.iterator();
    while (iterator.hasNext()){
        System.out.println(iterator.next());
    }

    源码解析

    初始化

    节点

    //链表的核心类-节点
    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);
    }

    addAll的分析

    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;
        //如果index==size,说明链表只有一个节点
        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;
        }
        
        //当一个节点的时候last和当前的节点相同
        if (succ == null) {
            last = pred;
        } else {
            pred.next = succ;
            succ.prev = pred;
        }
        
        //更新链表的长度
        size += numNew;
        modCount++;
        return true;
    }

    常用函数分析

    增加

    //增加元素,默认向链表的结尾增加节点
    public boolean add(E e) {
            linkLast(e);
            return true;
        }
    void linkLast(E e) {
        //获取链表尾部节点
        final Node<E> l = last;
        //创建新的节点,将最后的节点做为新节点的上一个节点引用,元素为传入元素
        final Node<E> newNode = new Node<>(l, e, null);
        //链表尾部指向最新节点
        last = newNode;
        //如果原来尾部节点为空,说明为空链表需要设置链表头部节点
        //否则将原来链表尾部节点的next指向新的节点
        if (l == null)
            first = newNode;
        else
            l.next = newNode;
        //增加大小
        size++;
        //记录修改次数,主要用于集合迭代,保证迭代数据的准确性
        //在迭代时候如果集合发生变化则会抛出异常
        //throw new ConcurrentModificationException();
        modCount++;
    }

    删除

    //根据下标进行删除节点
    public E remove(int index) {
        //检测下标是否越界
        checkElementIndex(index);
        return unlink(node(index));
    }
    //获取index的节点
    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 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)
            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) {
        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;
    }

    修改(替换)元素

    //修改某个元素
    public E set(int index, E element) {
            //检测下标是否越界
            checkElementIndex(index);
            //根据下标获取节点
            Node<E> x = node(index);
            //获取节点的元素
            E oldVal = x.item;
            //赋值新的元素
            x.item = element;
            return oldVal;
        }

    插入元素

    //插入指定位置元素
    public void add(int index, E element) {
        //检测下标是否越界
        checkPositionIndex(index);
        //判断下标是否为最后一个位置,如果是直接插入到最后一个节点
        //否则查询出当前index的节点,将新的节点放到原来index节点之前
        if (index == size)
            linkLast(element);
        else
            linkBefore(element, node(index));
    }
    //插入index节点之前位置
    void linkBefore(E e, Node<E> succ) {
        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++;
    }

    获取元素

    //根据下标获取元素
    public E get(int index) {
        //检测下标是否越界
        checkElementIndex(index);
        //根据下标获取节点,然后返回节点的元素
        return node(index).item;
    }
    
    //获取第一个元素
    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;
    }

    清除所有数据

    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
        //将所有数据进行逐个赋值为null,帮助gc的垃圾回收
        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;
        //记录清除的操作,保证迭代的准确性
        modCount++;
    }

    总结

    • 优点
      1、插入和移除数据效率高 2、链表形式方便做为栈或队列场景使用 3、相对arraylist来说,linkedlist无需扩容
    • 缺点
      1、根据随机获取元素效率低(根据下标获取)
      2、批量添加集合效率低

    其他

    在arraylist和linkedlist中的全局标量都出现了一个修饰符transient,此修饰符的属性默认是不能够被序列化的,jdk作者为了减少空间的浪费所以实现了writeObject(ObjectOutputStream)和readObject(ObjectInputStream)这两个方法,进行手动的序列化有存储数据的有用属性。

    参考

    更多请关注微信………

  • 相关阅读:
    [改善Java代码]在equals中使用getClass进行类型判断
    [改善Java代码]equals应该考虑null值的情景
    [改善Java代码]覆写equals方法时不要识别不出自己
    [改善Java代码] 推荐使用序列化实现对象的拷贝
    [改善Java代码]避免对象的浅拷贝
    [改善Java代码]让工具类不可实例化
    [改善Java代码]建议40:匿名类的构造函数很特殊
    [改善Java代码]使用匿名类的构造函数
    [改善Java代码]使用静态内部类提高封装性
    [改善Java代码]构造函数尽量简化
  • 原文地址:https://www.cnblogs.com/mvilplss/p/7118064.html
Copyright © 2011-2022 走看看