• 博客园logo
  • 会员
  • 众包
  • 新闻
  • 博问
  • 闪存
  • 赞助商
  • HarmonyOS
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录
南轲梦
博客园    首页    新随笔    联系   管理    订阅  订阅
LinkedList其实就那么一回事儿之源码分析
LinkedList其实是基于双向链表实现的, 因此具有链表 插入慢、 索引快的特性

上篇文章《ArrayList其实就那么一回儿事儿之源码分析》,给大家谈了ArrayList, 那么本次,就给大家一起看看同为List 家族的LinkedList。 下面就直接看源码吧:

public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
    transient int size = 0;
    
    //Node 是LinkedList的一个内部类,下面贴出了这个内部类的源码, 
    //主要用于保存上一个、当前和下一个元素的引用
    
    //头(第一个)元素
    transient Node<E> first;

    //尾(最后一个)元素
    transient Node<E> last;

    public LinkedList() {
    }

    //构造方法传入Collection, 那么将Collection转换为链表结构
    public LinkedList(Collection<? extends E> c) {
        this();
        addAll(c);
    }
    
    public boolean addAll(Collection<? extends E> c) {
        return addAll(size, c);
    }
    
    
    /**
     * 内部类
     */
    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;
        }
    }
    
    
    
    //这就是将一个集合转换为链表的方法
    public boolean addAll(int index, Collection<? extends E> c) {
        //index >= 0 && index <= size
        checkPositionIndex(index);

        Object[] a = c.toArray();
        int numNew = a.length;
        if (numNew == 0)
            return false;

        //succ保存的是index位置的元素
        Node<E> pred, succ;
        if (index == size) {
            //当index == size 的时候, 当前元素的上一个元素就是之前已存在链表的最后一个元素
            //(如果觉得有点绕, 可以再好好体会一下)
            succ = null;
            pred = last;
        } else {
            //当index != size 的时候, 那么index就一定出现在之前链表中
            //此处的调用的node方法,下面源码也已经给出,
            //node方法的主要作用就是判断index所处位置是在之前链表的上半部分还是下半部分,
            //在上半部分就从第一个元素开始循环,循环到index位置时返回元素, 如果是在后半部分,那么就从最后一个元素往前循环,循环到index位置时返回元素
            succ = node(index);
            //得到index所在元素的上一个元素引用
            pred = succ.prev;
        }
        
        //别急,上面还在热身, 这儿才开始转换
        
        for (Object o : a) {
            @SuppressWarnings("unchecked") E e = (E) o;
            //构造Node对象, 此时Node对象持有对前一个元素以及当前元素的引用
            Node<E> newNode = new Node<>(pred, e, null);
            if (pred == null)
                //如果前一个元素为null, 那么说明之前不存在链表,此元素将设置为链表的第一个元素
                first = newNode;
            else
                //如果已存在链表,那么就从之前链表的index位置开始插入
                pred.next = newNode;
            pred = newNode;
        }

        if (succ == null) {
            last = pred;
        } else {
            pred.next = succ;
            succ.prev = pred;
        }

        size += numNew;
        modCount++;
        return true;
    }
    
    private void checkPositionIndex(int index) {
        if (!isPositionIndex(index))
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }
    
    private boolean isPositionIndex(int index) {
        return index >= 0 && index <= size;
    }
    
    //上面已经把这方法解释了一遍,这儿就不多说了,贴出来就为了让大家看得直观
    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;
        }
    }
    
    
    
    //下面开始分析常用的add 、 remove 方法
    
    //先看看add方法
    public void add(int index, E element) {
        checkPositionIndex(index);
        
        //相信通过上面的分析,你已经能够猜到add 改怎么做了,
        //当index == size的时候, 已存在链表的最后一个元素就是当前待插入元素的上一个节点(元素)
        //当index != size的时候, 老规矩,先找出index位置的节点元素, 然后再插入(上面已经详解,这儿只做概述,加深印象)
        if (index == size)
            linkLast(element);
        else
            linkBefore(element, node(index));
    }
    
    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++;
    }
    
    
    //接下来再看看remove方法
    public E remove(int index) {
        checkElementIndex(index);
        return unlink(node(index));
    }
    
    //此方法作用: 先得到index位置的node, 然后拿到其上一个元素(pre)和下一个元素(next),
    //将上一个元素(pre)的下一个元素设置为index的next, 此时,就成功的删除了index位置的元素,
    //举个例子吧: 李四左手牵着张三,右手牵着王五, 现在我们要删除李四, 那么只需要直接将张三的手牵向王五, 明白了吧
    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;
    }
    
    
    
}

通过代码分析,我们可以看到,LinkedList其实是基于双向链表实现的, 因此,书上所讲的LinkedList的特性咱也别去记了, 知道链表的特性就对了, 到此,不得不谈谈LinkedList和ArrayList的区别:

ArrayList基于数组实现,因此具有: 有序、元素可重复、插入慢、 索引快 这些数组的特性; 

LinkedList 基于双向链表实现, 因此具有链表 插入快、 索引慢的特性;

了解了它们的特性之后,你就可以根据实际需要,选择合适的List了

posted on 2014-10-10 09:18  南轲梦  阅读(3640)  评论(1)    收藏  举报
刷新页面返回顶部
博客园  ©  2004-2025
浙公网安备 33010602011771号 浙ICP备2021040463号-3