zoukankan      html  css  js  c++  java
  • LinkedList源码分析

    LinkedList实现了List,Deque,Cloneable,Serializable接口,具有集合,队列,克隆以及序列化功能。使用泛型在编译期提早发现类型错误。

    LinkedList使用私有内部类Node保存集合中元素的信息,本例分析一些主要的方法,比如add方法,其方法内部调用了linkLast方法,那就只查看linkLast方法。

    添加节点,主要是确定并设置链表首节点和尾节点以及新添加节点的位置。

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

    linkFirst

    头部添加元素,先定位当前要添加的元素的位置,其pre节点为null,next节点为当前的first,如果链表的首节点或者尾节点为空,那么这个链表必定为空

    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
                // 原首节点不为空,则其pre节点一定指向当前新加节点,其next节点没有变化
                f.prev = newNode;
            size++;
            modCount++;
        }
    View Code

    linkLast

    尾部添加元素,先定位当前要添加的元素的位置,其pre节点为当前的last节点,next节点为null

    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
                // 尾节点的next节点一定指向当前新加节点,其pre节点没有变化
                l.next = newNode;
            size++;
            modCount++;
        }
    View Code

     addAll(int index, Collection<? extends E> c)

    1、判断索引及添加的集合是否为空
    2、将要添加的集合作为一个整体,获取其前驱节点pred和后继节点succ
    3、将集合中的每个元素依次添加到链表中
    4、处理要添加的集合的最后一个元素的next节点/链表尾部节点

    public boolean addAll(int index, Collection<? extends E> c) {
            // 检测索引是否合法
            checkPositionIndex(index);
    
            // 要添加的集合为空,返回false
            Object[] a = c.toArray();
            int numNew = a.length;
            if (numNew == 0)
                return false;
    
            // 将当前Collection看成一个整体,为简单,称为cton,获取其前驱节点pred和后继节点succ
            Node<E> pred, succ;
            if (index == size) {
                // 在尾部添加
                succ = null;
                pred = last;
            } else {
                // 
                succ = node(index);
                pred = succ.prev;
            }
            
            // 遍历要添加的元素,确定每个元素在LinkedList中的前一个pred及succ节点,最终确定其位置
            for (Object o : a) {
                E e = (E) o;
                Node<E> newNode = new Node<>(pred, e, null);
                if (pred == null)
                    first = newNode;
                else
                    pred.next = newNode;
                pred = newNode;
            }
            /*
             * 经过上述遍历,pred为cton中最后一个元素的节点。cton集合中size-1个元素的pred和succ节点都已经确定,
             * 但最后一个元素的next节点为null,不能够确定是否准确
             */
            if (succ == null) {
                // 后继节点为null,则以上操作结束后,除了链表的尾部节点需要调整之外,其他节点的位置都已确定。
                last = pred;
            } else {
                // cton中最后一个元素节点next节点为cton的succ节点,succ节点的pred节点为cton最后一个元素节点pred
                pred.next = succ;
                succ.prev = pred;
            }
    
            size += numNew;
            modCount++;
            return true;
        }
    View Code

     linkBefore(E e, Node<E> succ)

    在某个元素前添加节点
    1、获取元素的前驱节点
    2、确定要添加的节点的位置
    3、设置要添加元素的后继节点的前驱节点,前驱节点的后继节点

    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++;
                }
    View Code

     getFirst/getLast

    链表中维护有头部和尾部节点,获取头部或者尾部节点比较容易,直接取出首节点/尾节点对应的值即可

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

    node(int)

    获取指定节点的元素,二分查找,提高效率

    1、判断index是否小于链表元素的一半
    2、若是,则从左侧开始查找,若否,则从右侧开始查找
    3、将每次查找节点重新复制给原数据,则在index-1节点获取的节点的next即为所需要的节点

    Node<E> node(int index) {
                if (index < (size >> 1)) {
                    // 左侧开始遍历,初始值为首节点
                    Node<E> x = first;
                    for (int i = 0; i < index; i++)
                        // 将当前节点的next节点重新赋值给x变量
                        x = x.next;
                    // index-1位置的node节点的next即为我们所查找的节点
                    return x;
                } else {
                    Node<E> x = last;
                    for (int i = size - 1; i > index; i--)
                        // 将当前节点的next节点重新赋值给x变量
                        x = x.prev;
                    // size-(index +1)节点的prev节点为我们所查找的节点
                    return x;
                }
            }
    View Code

    set(int index, E element)
    修改指定位置元素
    1、验证索引是否超出范围
    2、获取index处原节点
    3、将index处原节点的值设置为当前值

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

    indexOf(Object o)

    从首节点遍历,查找首次出现与item值相同的节点所在位置,要查找的元素必须实现equals方法

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

    lastIndexOf(Object o)

    从尾节点遍历,查找首次出现于item值相同的节点所在位置,要查找的元素必须实现equals方法

    public int lastIndexOf(Object o) {
            int index = size;
            if (o == null) {
                for (Node<E> x = last; x != null; x = x.prev) {
                    index--;
                    if (x.item == null)
                        return index;
                }
            } else {
                for (Node<E> x = last; x != null; x = x.prev) {
                    index--;
                    if (o.equals(x.item))
                        return index;
                }
            }
            return -1;
        }
    View Code

    unlinkFirst
    获取首节点的next节点,将首节点的next节点和item设置为null,将first设置为原首节点的next节点,原首节点的next节点的pre节点设置为null

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

    unlinkLast
    获取尾节点的pre节点,将尾节点的pre及item设置为null,将尾节点设置为原尾节点的pre节点,原尾节点的pre节点的next节点设置为null

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

     remove(object)

    1、找到要删除的元素对应的节点
    2、找到该节点的前一个元素和后一个元素
    3、设置前后节点的对应关系
    4、将当前节点的前驱节点和后继节点以及item设置为null

    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) {
        // 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;
    }
    View Code
  • 相关阅读:
    Effective Java 第三版——26. 不要使用原始类型
    Effective Java 第三版——25. 将源文件限制为单个顶级类
    Effective Java 第三版——24. 优先考虑静态成员类
    Effective Java 第三版——23. 优先使用类层次而不是标签类
    Effective Java 第三版——22. 接口仅用来定义类型
    Effective Java 第三版——21. 为后代设计接口
    Effective Java 第三版——20. 接口优于抽象类
    Effective Java 第三版——19. 如果使用继承则设计,并文档说明,否则不该使用
    Effective Java 第三版——18. 组合优于继承
    Effective Java 第三版——17. 最小化可变性
  • 原文地址:https://www.cnblogs.com/qq931399960/p/10911122.html
Copyright © 2011-2022 走看看