LinkedList源码解析(一)

本篇文章为大家进行Java中的LinkedList源码解析。在进行正式解析前先给大家介绍下线性表的概念。

线性表:线性表是最常用、最简单的一种数据结构,简言之,线性表是n个数据元素的有限序列。线性表中的每个数据元素最多只能又一个直接前趋元素,每个数据元素只能有一个直接后继元素;只有第一个元素没有直接前趋元素,而最后一个数据元素没后直接后继元素。线性表中的数据元素个数是该线性表的长度。

线性表的结构分类:线性表分为顺序存储(比如Java中的ArrayList)和非顺序存储(比如Java中的LinkedList)。

两种分类的优劣:线性表的顺序存储结构的特点是逻辑关系上相邻的两个元素在物理位置上也相邻,因此随机存取元素时比较简单,但是这个特点也使得在插入和删除元素时,造成大量的数据元素移动,同时如果使用静态分配存储单元,还要预先占用连续的储存空间,可能造成空间的浪费或者空间的溢出。如果采用链式存储,就不要求逻辑上相邻的数据元素在物理位置上也相邻,因此它没有顺序结构的所具有的缺点,但同时也失去了可随机存取的优点。

线性表与LinkedList之间的关系:Java的LinkedList实现所用的数据结构即为线性表的链式存储,而且是双向链表。

本位针对LinkedList的一些方法进行解析。

众所周知,链表是有一个个的节点组成的,LinkedList同理。

1.Node<E>

在LinkedList中Node充当节点的角色,它的结构如下:

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

1.LinkedList.add(E);

    /**
     * 将指定的元素追加到此列表的末尾
     *
     * <p>这个方法等价于 {@link #addLast}.
     *
     * @param e 元素添加到此列表中
     * @return {@code true} (as specified by {@link Collection#add})
     */
    public boolean add(E e) {
        linkLast(e);
        return true;
    }


    /**
     * 链接e作为最后一个元素。
     */
    void linkLast(E e) {
        //获取当前链表最后一个节点
        final Node<E> l = last;
        //创建一个新的节点 并且前趋节点为l,值为e,后继节点为空
        final Node<E> newNode = new Node<>(l, e, null);
        //将last也就是链表的最后一个节点赋值为新添加的节点
        last = newNode;
        //如果链表最后一个节点为空
        if (l == null)
            //那么表示链表原本是空的,将链表的第一个节点也赋值为新添加的节点
            first = newNode;
        else
            //反正则将最后一个节点的后继赋值为新添加的节点
            l.next = newNode;
        //线性表长度加一
        size++;
        //链表操作次数加一
        modCount++;
    }

2.LinkedList.add(index,E);

    /**
     * 将指定元素插入到列表中的指定位置。
     * 将当前位于该位置的元素(如果有)和任何后续元素向右移动(将一个元素添加到它们的索引中)。
     *
     * @param 要插入指定元素的索引
     * @param 要插入的元素
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public void add(int index, E element) {
        //验证index是否合理 由源码可见 index必须是大于0并且小于当前链表的实际长度
        checkPositionIndex(index);
        
        //如果index等于链表的实际长度 则把元素插入到链表尾部
        if (index == size)
            linkLast(element);
        //反之则插入到特定位置
        else
            linkBefore(element, node(index));
    }



    //验证index合理性的具体实现
    private void checkPositionIndex(int index) {
        if (!isPositionIndex(index))
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

    private boolean isPositionIndex(int index) {
        return index >= 0 && index <= size;
    }

    /**
     * 链接e作为最后一个元素。
     */
    void linkLast(E e) {
        //获取当前链表最后一个节点
        final Node<E> l = last;
        //创建新的节点 前趋节点为当前链表的最后一个节点,值为e,后继节点为空
        final Node<E> newNode = new Node<>(l, e, null);
        //将链表最后一个节点赋值为新添加的节点
        last = newNode;
        //如果当前链表的最后一个节点为空
        if (l == null) 
            //则将当前链表的第一个节点也赋值为新添加的节点
            first = newNode;
        else
            //反之将当前链表的后继节点改为新添加的节点
            l.next = newNode;
        //链表的实际长度加一
        size++;
        //链表的操作次数加一
        modCount++;
    }

    
    /**
     * 返回指定元素索引处的(非空)节点。
     */
    Node<E> node(int index) {
        // 确保index的存在 isElementIndex(index);
        
        //如果index小于size >> 1(注:按位右移运算符。左操作数按位右移右操作数指定的位数。
        是关于二进制的计算,不了解的可以去查API或者百度...)
        
        //此处使用二分算法 大大提高执行效率 此判断的方法意思就是如果index在链表的前半部分就执行以下,index在链表的后半部分就执行else部分。
        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;
        }
    }

    
    /**
     * 在非空节点succ之前插入元素e。
     */
    void linkBefore(E e, Node<E> succ) {
        // assert succ != null; 确保succ不为空!

        //获取succ的前趋节点
        final Node<E> pred = succ.prev;
        //创建新的节点 前趋为succ的前趋节点,值为e,后继为succ
        final Node<E> newNode = new Node<>(pred, e, succ);
        将succ的前趋节点设置为新添加的节点
        succ.prev = newNode;
            
        //如果succ的前趋节点为空 表示succ为链表的第一个节点
        if (pred == null)
            //则将链表的第一个节点设置为新添加的节点
            first = newNode;
        else
            //反正则将succ的前趋节点的 后继节点 设置为新添加的节点
            pred.next = newNode;
        //链表的真实长度 加一
        size++;
        //链表的操作次数加一
        modCount++;
    }

3.LinkedList.remove(index);

    /**
     * 删除列表中指定位置的元素。 
     * 将任何后续元素向左移动(从它们的索引中减去1)。
     * 返回从列表中删除的元素。
     *
     * @param index 索引要删除的元素的索引
     * @return 先前位于指定位置的元素
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E remove(int index) {
        //验证index是否合理。由源码可见,index必须大于等于0并且小于当前链表的实际长度
        checkElementIndex(index);
        //unlink方法所需参数为一个节点 节点通过node传入index获取 node方法见以上方法描述,此                         处不详细描述
        return unlink(node(index));
    }

    //验证index是否合理的具体实现
    private void checkElementIndex(int index) {
        if (!isElementIndex(index))
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

    /**
     * 指示该参数是否为现有元素的索引。
     */
    private boolean isElementIndex(int index) {
        return index >= 0 && index < size;
    }

    /**
     * Unlinks non-null node x.
     */
    E unlink(Node<E> x) {
        // assert x != null; 确保要删除的节点不为空,其中x代表要删除的节点
        //x节点的值
        final E element = x.item;
        //x节点的后继节点
        final Node<E> next = x.next;
        //x节点的前趋节点
        final Node<E> prev = x.prev;
        
        //如果x节点的前趋节点为空 代表x节点为链表的第一个节点
        if (prev == null) {
            //将链表的第一个节点赋值为x节点的后继节点
            first = next;
        } else {
            //将x节点的前趋节点 的 后继节点 设置为x的后继节点
            prev.next = next;
            //将x节点的前趋节点 设置为空
            x.prev = null;
        }
        
        //如果x没有后继节点 代表x节点为链表的最后一个节点
        if (next == null) {
            //则将链表的最后一个节点设置为x节点的前趋节点
            last = prev;
        } else {
            //反正,将x节点的 后继节点 的 前趋节点 赋值为x节点的前趋节点
            next.prev = prev;
            //将x节点的后继节点设置为空
            x.next = null;
        }
        //将x节点的值设置为空
        x.item = null;
        //将链表的长度减一
        size--;
        //将链表的操作次数加一
        modCount++;
        //将删除节点的值返回
        return element;
    }

4.LinkedList.remove(Object);

     /**
     * 如果指定元素出现,则从该列表中删除它的第一个匹配项
     * 如果此列表不包含该元素,则更正式地保持不变,删除索引最低的元素(相当于调用remove(),
     * 删除链表第一个数据元素)
     * {@code i} such that
     * {@code Objects.equals(o, get(i))}
     * (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) {
        if (o == null) {
            for (Node<E> x = first; x != null; x = x.next) {
                if (x.item == null) {
                    unlink(x);
                    return true;
                }
            }
        } else {
            //Object不为空的情况 则循环获取到值为object节点,调用unlink删除
            //unlink方法在remove(index)中有详细描述,此处不做解释
            for (Node<E> x = first; x != null; x = x.next) {
                if (o.equals(x.item)) {
                    unlink(x);
                    return true;
                }
            }
        }
        return false;
    }

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
完整版:https://download.csdn.net/download/qq_27595745/89522468 【课程大纲】 1-1 什么是java 1-2 认识java语言 1-3 java平台的体系结构 1-4 java SE环境安装和配置 2-1 java程序简介 2-2 计算机中的程序 2-3 java程序 2-4 java类库组织结构和文档 2-5 java虚拟机简介 2-6 java的垃圾回收器 2-7 java上机练习 3-1 java语言基础入门 3-2 数据的分类 3-3 标识符、关键字和常量 3-4 运算符 3-5 表达式 3-6 顺序结构和选择结构 3-7 循环语句 3-8 跳转语句 3-9 MyEclipse工具介绍 3-10 java基础知识章节练习 4-1 一维数组 4-2 数组应用 4-3 多维数组 4-4 排序算法 4-5 增强for循环 4-6 数组和排序算法章节练习 5-0 抽象和封装 5-1 面向过程的设计思想 5-2 面向对象的设计思想 5-3 抽象 5-4 封装 5-5 属性 5-6 方法的定义 5-7 this关键字 5-8 javaBean 5-9 包 package 5-10 抽象和封装章节练习 6-0 继承和多态 6-1 继承 6-2 object类 6-3 多态 6-4 访问修饰符 6-5 static修饰符 6-6 final修饰符 6-7 abstract修饰符 6-8 接口 6-9 继承和多态 章节练习 7-1 面向对象的分析与设计简介 7-2 对象模型建立 7-3 类之间的关系 7-4 软件的可维护与复用设计原则 7-5 面向对象的设计与分析 章节练习 8-1 内部类与包装器 8-2 对象包装器 8-3 装箱和拆箱 8-4 练习题 9-1 常用类介绍 9-2 StringBuffer和String Builder类 9-3 Rintime类的使用 9-4 日期类简介 9-5 java程序国际化的实现 9-6 Random类和Math类 9-7 枚举 9-8 练习题 10-1 java异常处理 10-2 认识异常 10-3 使用try和catch捕获异常 10-4 使用throw和throws引发异常 10-5 finally关键字 10-6 getMessage和printStackTrace方法 10-7 异常分类 10-8 自定义异常类 10-9 练习题 11-1 Java集合框架和泛型机制 11-2 Collection接口 11-3 Set接口实现类 11-4 List接口实现类 11-5 Map接口 11-6 Collections类 11-7 泛型概述 11-8 练习题 12-1 多线程 12-2 线程的生命周期 12-3 线程的调度和优先级 12-4 线程的同步 12-5 集合类的同步问题 12-6 用Timer类调度任务 12-7 练习题 13-1 Java IO 13-2 Java IO原理 13-3 流类的结构 13-4 文件流 13-5 缓冲流 13-6 转换流 13-7 数据流 13-8 打印流 13-9 对象流 13-10 随机存取文件流 13-11 zip文件流 13-12 练习题 14-1 图形用户界面设计 14-2 事件处理机制 14-3 AWT常用组件 14-4 swing简介 14-5 可视化开发swing组件 14-6 声音的播放和处理 14-7 2D图形的绘制 14-8 练习题 15-1 反射 15-2 使用Java反射机制 15-3 反射与动态代理 15-4 练习题 16-1 Java标注 16-2 JDK内置的基本标注类型 16-3 自定义标注类型 16-4 对标注进行标注 16-5 利用反射获取标注信息 16-6 练习题 17-1 顶目实战1-单机版五子棋游戏 17-2 总体设计 17-3 代码实现 17-4 程序的运行与发布 17-5 手动生成可执行JAR文件 17-6 练习题 18-1 Java数据库编程 18-2 JDBC类和接口 18-3 JDBC操作SQL 18-4 JDBC基本示例 18-5 JDBC应用示例 18-6 练习题 19-1 。。。
完整版:https://download.csdn.net/download/qq_27595745/89522468 【课程大纲】 1-1 什么是java 1-2 认识java语言 1-3 java平台的体系结构 1-4 java SE环境安装和配置 2-1 java程序简介 2-2 计算机中的程序 2-3 java程序 2-4 java类库组织结构和文档 2-5 java虚拟机简介 2-6 java的垃圾回收器 2-7 java上机练习 3-1 java语言基础入门 3-2 数据的分类 3-3 标识符、关键字和常量 3-4 运算符 3-5 表达式 3-6 顺序结构和选择结构 3-7 循环语句 3-8 跳转语句 3-9 MyEclipse工具介绍 3-10 java基础知识章节练习 4-1 一维数组 4-2 数组应用 4-3 多维数组 4-4 排序算法 4-5 增强for循环 4-6 数组和排序算法章节练习 5-0 抽象和封装 5-1 面向过程的设计思想 5-2 面向对象的设计思想 5-3 抽象 5-4 封装 5-5 属性 5-6 方法的定义 5-7 this关键字 5-8 javaBean 5-9 包 package 5-10 抽象和封装章节练习 6-0 继承和多态 6-1 继承 6-2 object类 6-3 多态 6-4 访问修饰符 6-5 static修饰符 6-6 final修饰符 6-7 abstract修饰符 6-8 接口 6-9 继承和多态 章节练习 7-1 面向对象的分析与设计简介 7-2 对象模型建立 7-3 类之间的关系 7-4 软件的可维护与复用设计原则 7-5 面向对象的设计与分析 章节练习 8-1 内部类与包装器 8-2 对象包装器 8-3 装箱和拆箱 8-4 练习题 9-1 常用类介绍 9-2 StringBuffer和String Builder类 9-3 Rintime类的使用 9-4 日期类简介 9-5 java程序国际化的实现 9-6 Random类和Math类 9-7 枚举 9-8 练习题 10-1 java异常处理 10-2 认识异常 10-3 使用try和catch捕获异常 10-4 使用throw和throws引发异常 10-5 finally关键字 10-6 getMessage和printStackTrace方法 10-7 异常分类 10-8 自定义异常类 10-9 练习题 11-1 Java集合框架和泛型机制 11-2 Collection接口 11-3 Set接口实现类 11-4 List接口实现类 11-5 Map接口 11-6 Collections类 11-7 泛型概述 11-8 练习题 12-1 多线程 12-2 线程的生命周期 12-3 线程的调度和优先级 12-4 线程的同步 12-5 集合类的同步问题 12-6 用Timer类调度任务 12-7 练习题 13-1 Java IO 13-2 Java IO原理 13-3 流类的结构 13-4 文件流 13-5 缓冲流 13-6 转换流 13-7 数据流 13-8 打印流 13-9 对象流 13-10 随机存取文件流 13-11 zip文件流 13-12 练习题 14-1 图形用户界面设计 14-2 事件处理机制 14-3 AWT常用组件 14-4 swing简介 14-5 可视化开发swing组件 14-6 声音的播放和处理 14-7 2D图形的绘制 14-8 练习题 15-1 反射 15-2 使用Java反射机制 15-3 反射与动态代理 15-4 练习题 16-1 Java标注 16-2 JDK内置的基本标注类型 16-3 自定义标注类型 16-4 对标注进行标注 16-5 利用反射获取标注信息 16-6 练习题 17-1 顶目实战1-单机版五子棋游戏 17-2 总体设计 17-3 代码实现 17-4 程序的运行与发布 17-5 手动生成可执行JAR文件 17-6 练习题 18-1 Java数据库编程 18-2 JDBC类和接口 18-3 JDBC操作SQL 18-4 JDBC基本示例 18-5 JDBC应用示例 18-6 练习题 19-1 。。。
完整版:https://download.csdn.net/download/qq_27595745/89522468 【课程大纲】 1-1 什么是java 1-2 认识java语言 1-3 java平台的体系结构 1-4 java SE环境安装和配置 2-1 java程序简介 2-2 计算机中的程序 2-3 java程序 2-4 java类库组织结构和文档 2-5 java虚拟机简介 2-6 java的垃圾回收器 2-7 java上机练习 3-1 java语言基础入门 3-2 数据的分类 3-3 标识符、关键字和常量 3-4 运算符 3-5 表达式 3-6 顺序结构和选择结构 3-7 循环语句 3-8 跳转语句 3-9 MyEclipse工具介绍 3-10 java基础知识章节练习 4-1 一维数组 4-2 数组应用 4-3 多维数组 4-4 排序算法 4-5 增强for循环 4-6 数组和排序算法章节练习 5-0 抽象和封装 5-1 面向过程的设计思想 5-2 面向对象的设计思想 5-3 抽象 5-4 封装 5-5 属性 5-6 方法的定义 5-7 this关键字 5-8 javaBean 5-9 包 package 5-10 抽象和封装章节练习 6-0 继承和多态 6-1 继承 6-2 object类 6-3 多态 6-4 访问修饰符 6-5 static修饰符 6-6 final修饰符 6-7 abstract修饰符 6-8 接口 6-9 继承和多态 章节练习 7-1 面向对象的分析与设计简介 7-2 对象模型建立 7-3 类之间的关系 7-4 软件的可维护与复用设计原则 7-5 面向对象的设计与分析 章节练习 8-1 内部类与包装器 8-2 对象包装器 8-3 装箱和拆箱 8-4 练习题 9-1 常用类介绍 9-2 StringBuffer和String Builder类 9-3 Rintime类的使用 9-4 日期类简介 9-5 java程序国际化的实现 9-6 Random类和Math类 9-7 枚举 9-8 练习题 10-1 java异常处理 10-2 认识异常 10-3 使用try和catch捕获异常 10-4 使用throw和throws引发异常 10-5 finally关键字 10-6 getMessage和printStackTrace方法 10-7 异常分类 10-8 自定义异常类 10-9 练习题 11-1 Java集合框架和泛型机制 11-2 Collection接口 11-3 Set接口实现类 11-4 List接口实现类 11-5 Map接口 11-6 Collections类 11-7 泛型概述 11-8 练习题 12-1 多线程 12-2 线程的生命周期 12-3 线程的调度和优先级 12-4 线程的同步 12-5 集合类的同步问题 12-6 用Timer类调度任务 12-7 练习题 13-1 Java IO 13-2 Java IO原理 13-3 流类的结构 13-4 文件流 13-5 缓冲流 13-6 转换流 13-7 数据流 13-8 打印流 13-9 对象流 13-10 随机存取文件流 13-11 zip文件流 13-12 练习题 14-1 图形用户界面设计 14-2 事件处理机制 14-3 AWT常用组件 14-4 swing简介 14-5 可视化开发swing组件 14-6 声音的播放和处理 14-7 2D图形的绘制 14-8 练习题 15-1 反射 15-2 使用Java反射机制 15-3 反射与动态代理 15-4 练习题 16-1 Java标注 16-2 JDK内置的基本标注类型 16-3 自定义标注类型 16-4 对标注进行标注 16-5 利用反射获取标注信息 16-6 练习题 17-1 顶目实战1-单机版五子棋游戏 17-2 总体设计 17-3 代码实现 17-4 程序的运行与发布 17-5 手动生成可执行JAR文件 17-6 练习题 18-1 Java数据库编程 18-2 JDBC类和接口 18-3 JDBC操作SQL 18-4 JDBC基本示例 18-5 JDBC应用示例 18-6 练习题 19-1 。。。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值