上一章我们手写了ArrayList的核心源码,ArrayList底层是用了一个数组来保存数据,数组保存数据的优点就是查找效率高,但是删除效率特别低,最坏的情况下需要移动所有的元素。在查找需求比较重要的情况下可以用ArrayList,如果是删除操作比较多的情况下,用ArrayList就不太合适了。Java为我们提供了LinkedList,是用链接来实现的,我们今天就来手写一个QLinkedList,来提示底层是怎么做的。

如上图,底层用一个双链表,另外有两个指示器,一个指向头,一个指向尾。

链表中的每个节点的next指向下一个节点,同理pre指向上一个节点,第一个节点的pre为null,最后一个节点的next为null

双链表的细节实现较多,尤其是边界的问题,要十分仔细,LinkedList中设计了许多的小函数,本例中就不设计那么多的小方法了,直接把最核心的代码都写到一个方法中。以方便揭示核心原理。

下面是完整的QLinkedList的源码,注释很清楚。public class QLinkedList {    private QNode first; //指向头节点

private QNode last;  //指向尾节点

private int size;    //节点的个数

//节点类

public static class QNode {

T value;        //数据

QNode pre;   //指向上一个节点

QNode next;  //指向下一个节点

public QNode(QNode pre, QNode next, T value) {            this.pre = pre;     //节点的上一个指向

this.next = next;   //节点的下一个指向

this.value = value; //存放的数据

}

}    public QLinkedList() {        //默认是一个空狼链表,first,last都为null, 节点个数为0

first = null;

last = null;

size = 0;

}    //默认添加到尾

public void add(T e) {

addLast(e);

}    //添加到头部

public void addFirst(T e) {        if (first == null && last == null) {

QNode node = new QNode<>(null, null, e);

first = node;

last = node;

} else {

QNode node = new QNode<>(null, first, e);

first.pre = node;

}

size++;

}    //添加到尾部,我们默认添加的都是不为null的数据

public void addLast(T e) {        if (e == null) {            throw new RuntimeException("e == null");

}        //1 链表还是空的时候

if (size == 0) {            //1.1 新建一个节点,pre,next都为null

QNode node = new QNode(null, null, e);            //1.2 只有一个节点,first,last都指向null

first = node;

last = node;        //2 链表不为空

} else {            //2.1 新建一个节点,pre指向last最后一个节点,next为null(因为是最后一个节点)

QNode node = new QNode<>(last, null, e);            //2.2 同时之前的最后一节点的next 指向新建的node节点

last.next = node;            //2.3 然后移动last,让last指向最后一个节点

last = node;

}        //添加一个节点后,别忘了节点的总数加 1

size++;

}    // position 从 0 开始

// 这里面有个小技巧,可以先判断一下 position 是大于 size/2 还是小于 size/2

// 如果 position > size / 2 , 说明position是在链表的后半段,我们可以从last开始往前遍历

// 如果 position

// 这样效率会高许多,这也是双链表的意义所在,我们这里就不这样做了。直接从前往后遍历

// 读者可以自己实现,以加深对链表的理解

public T get(int position) {        // 不合法的position直接抛异常,让开发者直接定位问题

if (position  size - 1) {            throw new RuntimeException("invalid position");

}        // 如果链表为空,直接返回null

if (size == 0) {            return null;

}        // 如果链表只有一个节点,直接返回

// 因为position合法性在前面已经验证过

// 所以在这里面不用验证,一定是0

if(size == 1){            return first.value;

}        // 注意这个新建的 p 节点,p.next 指向的是 first

// 这是为了下面的循环,保证 i == 0 的时候,p 指向第一个节点

QNode p = new QNode<>(null, first, null);        for (int i = 0; i <= position; i++) {

p = p.next;

}        //如果找到了,就返回value

if (p != null) {            return p.value;

}        //否则返回 null

return null;

}    // 返回链表的节点总个数

// 注意first和last节点只是帮助我们方便操作的

// size可不包括first,last

public int size() {        return size;

}    // 删除一个元素,这里传的参数是 T e ,我们也可以传position进行删除,这里就不作演示了

// 可以先调用上面的get()方法,返回对应的值,再调用此方法

// 读者可以自己实现

public T remove(T e) {        //1 不合法,抛异常

if (e == null) {            throw new RuntimeException("e == null");

}        //2 链表为空,返回 null

if (size == 0) {            return null;

}        //2 如果链表只有一个节点

if (size == 1) {

QNode node = first;            //3 如果相等,删除节点 size-- ,并把first,last赋值为null

if(e == node.value || e.equals(node.value)){

first = last = null;

size--;                return node.value;

}else {                //4 不相等,返回null

return null;

}

}        // 如果链表大于1个节点,我们从前往后找value等于e的节点

// 1 查找, 和get()方法一样,注意p的next指向first

QNode p = new QNode<>(null, first, null);        boolean find = false;        for (int i = 0; i

p = p.next;            if (p != null && (e == p.value || e.equals(p.value))) {

find = true;                break;

}

}        // 2 如果找到了

if (find) {            // 2.1 如果找到的节点是最后一个节点

// 删除的是最后一个

if (p.next == null) {                //2.2 改变last的值,指向p的前一个节点

last = p.pre;                //2.3 p的前一个节点,变成了最后一个节点,所以,前一个节点的next值赋值为null

p.pre.next = null;                //2.4 把p.pre赋值为null,已经没有用了

p.pre = null;                //2.5 别忘了节点个数减1

size--;                //2.6 返回删除的节点的value

return p.value;            //3.1 如果删除的是第一个节点(p.pre == null就表明是第一个节点)

} else if (p.pre == null) {                //3.2 改变first的指向,指向p的下一个节点

first = p.next;                //3.3 p的下一个节点变成了第一个节点,需要把p的下一个节点的pre指向为null

p.next.pre = null;                //3.4 p.next没有用了

p.next = null;                //3.5 别忘了节点个数减1

size--;                //3.6 返回删除的节点的value

return p.value;            // 4 如果删除的不是第一个也不是最后一个,是中间的某一个,这种情况最简单

} else {                //4.1 p的上一个节点的next需要指向p的下一个节点

p.pre.next = p.next;                //4.2 p 的下一个节点的pre需要指向p的上一个节点

p.next.pre = p.pre;                //4.3 此时p无用了,把p的pre,next赋值为null

//这时候不需要调整first,last的位置

p.pre = null;

p.next = null;                //4.4 别忘了节点个数减1

size--;                //4.5 返回删除的节点的value

return p.value;

}

}        //没有找到与e相等的节点,直接返回null

return null;

}

}

我们来测试一下QLinkedList,测试代码如下:public static void main(String[] args) {

QLinkedList list = new QLinkedList<>();

list.add("one");

list.add("two");

list.add("three");

list.add("four");

System.out.println(list.size);        for (int i = 0; i

System.out.println(list.get(i));

}

System.out.println("===================");

System.out.println(list.remove("two"));

System.out.println(list.size);        for (int i = 0; i

System.out.println(list.get(i));

}

}

输出如下:4

one

two

three

four

===================

two

3

one

three

four

由此可见我们的QLinkedList可以正常的add,get,size,remove了。

建议可以参考一下JDK中的LinkedList。以加深对LinkedList的理解

明天手写HashMap的核心源码实现

java linkedlist底层_手写Java LinkedList核心源码相关推荐

  1. java字符串拼接_这样写Java,同事直呼666

    作者:涛姐涛哥 来源:cnblogs.com/taojietaoge/p/11575376.html 一.MyBatis 不要写 1=1 当遇到多个查询条件,使用where 1=1 可以很方便的解决我 ...

  2. 一起手写Vue3核心模块源码,掌握阅读源码的正确方法

    最近和一个猎头聊天,说到现在前端供需脱节的境况.一方面用人方招不到想要的中高级前端,另一方面市场上有大量初级前端薪资要不上价. 特别是用 Vue 框架的,因为好上手,所以很多人将 Vue 作为入门框架 ...

  3. 安全系列之——手写JAVA加密、解密

    其他文章: 安全系列之--手写JAVA加密.解密 安全系列之--数据传输的完整性.私密性.源认证.不可否认性 安全系列之--主流Hash散列算法介绍和使用 安全系列之--RSA的公钥私钥有多少人能分的 ...

  4. 呵呵,阿里高工熬夜手写“Java中高级核心知识全面解析”就这?也就让我五体投地的水平!

    前言 先说一下自己的个人情况,大专生,18年通过校招进入湖南金蝶软件公司,干了接近3年的CRUD,今年年初,感觉自己不能够在这样下去了,长时间呆在一个舒适的环境会让一个人堕落!而我已经在一个企业干了三 ...

  5. 视频教程-手写Java框架系列教程之一反射(含配套资料)-Java

    手写Java框架系列教程之一反射(含配套资料) 张长志技术全才.擅长领域:区块链.大数据.Java等.10余年软件研发及企业培训经验,曾为多家大型企业提供企业内训如中石化,中国联通,中国移动等知名企业 ...

  6. 【XML和Java】手写Java程序引用xsd验证xml

    一.首先要有一个xml文件和xsd文件 (1) database.conf.xml <?xml version="1.0" encoding="UTF-8" ...

  7. 未能加载文件或程序集或它的某一个依赖项_手写一个miniwebpack

    前言 之前好友希望能介绍一下 webapck 相关的内容,所以最近花费了两个多月的准备,终于完成了 webapck 系列,它包括一下几部分: webapck 系列一:手写一个 JavaScript 打 ...

  8. 面试官系统精讲Java源码及大厂真题 - 09 TreeMap 和 LinkedHashMap 核心源码解析

    09 TreeMap 和 LinkedHashMap 核心源码解析 更新时间:2019-09-05 10:15:03 人的影响短暂而微弱,书的影响则广泛而深远. --普希金 引导语 在熟悉 HashM ...

  9. 【源码阅读计划】浅析 Java 线程池工作原理及核心源码

    [源码阅读计划]浅析 Java 线程池工作原理及核心源码 为什么要用线程池? 线程池的设计 线程池如何维护自身状态? 线程池如何管理任务? execute函数执行过程(分配) getTask 函数(获 ...

最新文章

  1. html的后绑定事件,HTML 控件绑定事件
  2. 统计数字,空白符,制表符_为什么您应该在HTML中使用制表符空间而不是多个非空白空间(nbsp)...
  3. 谷歌X实验室的“无用”发明
  4. 设置cookie和查找cookie的方法
  5. C语言数据结构(大话数据结构——笔记4)第六章:树
  6. 拆卸invokedynamic
  7. 解决ViewPager添加点击监听器无触发的问题
  8. pid和linux的关系,linux – bash pid和$$之间的区别
  9. Android build.gradle文件详解(转述自《Android第一行代码》第二版)
  10. 数据库问题6-將系統資料表對應至系統檢視
  11. Python列表推导式求素数
  12. 实现二叉树各种遍历算法
  13. 盘点 | 2018年IoT蓄势待发
  14. matlab中制作软件,如何用matlab制作一个小软件
  15. shell 搜索文件夹下所有文件
  16. Excel一键给操作内容添加批注作者
  17. 【Python爬虫】:爬取58同城二手房的所有房产标题
  18. redis SDS介绍
  19. 前端html制作中国地图,echarts实现中国地图
  20. 有趣且重要的Git知识合集(5)Merge branch ‘master‘ of

热门文章

  1. 链表问题15——将搜索二叉树转换成双向链表(方法二)
  2. 栈与队列4——用一个栈实现另一个栈的排序
  3. 侧边栏qq客服对话显示
  4. kali系统破解WPA密码实战
  5. Django中的Form
  6. shell 监控局域网的主机是否up(转)
  7. iis7.5配置.net mvc注意事项
  8. 一起谈.NET技术,微软PDC10:大牛谈ASP.NET和C#技术走向
  9. 求助:Event ID:10021日志错误的解决方法
  10. [翻译]使用HtmlAgilityPack更好的HTML分析和验证