史上最全的Java容器集合之基础数据结构(手撕链表)

简介: 史上最全的Java容器集合之基础数据结构(手撕链表)

数组

第一个数组是一种效率最高的存储和随机访问的方式

数组是一种连续存储线性结构,元素类型相同,大小相等,存取速度快

image.png

无论使用哪种类型的数组,数组的标识符[] ,它其实是一个引用,指在堆中创建了一个真实的对象(这个对象对我们程序员是不可见的),我自己找了半天,我说他既然有length属性,那我肯定能在Java中找到这个对象,结果我硬是没找到,这个对象保存的是对其他对象的引用。length方法是唯一一个能用这个对象访问的属性或者方法

数组的特点

  • 一致性:数组只能保存相同数据类型元素,元素的数据类型可以是任何相同的数据类型。
  • 有序性:数组中的元素是有序的,通过下标访问。
  • 不可变性:数组一旦初始化,则长度(数组中元素的个数)不可变。 数组是一种连续存储线性结构,元素类型相同,大小相等

数组的缺点:

  • 事先必须知道数组的长度
  • 插入删除元素很慢
  • 空间通常是有限制的
  • 需要大块连续的内存块
  • 插入删除元素的效率很低

带你们来解密一下数组

image.png

个[I 感觉像个非法字符,我们继续反射

image.png

我去,我发现 啥都没有,这么奇怪,那我为什么能访问它的length属性呢?

先不管为什么没有length成员变量,我们先搞清楚[I这个类是哪里声明的。既然[I都不是合法的标识符,那么这个类肯定不是在Java代码中显式声明的。想来想去,只能是JVM自己在运行时生成的了。JVM生成类还是一件很容易的事情,甚至无需生成字节码,直接在方法区中创建类型数据,就差不多完工了。

在The JavaTM Virtual Machine Specification Second Edition 5.3.3 Creating Array Classes。描述到: 类加载器先看看数组类是否已经被创建了。如果没有,那就说明需要创建数组类;如果有,那就无需创建了。如果数组元素是引用类型,那么类加载器首先去加载数组元素的类。JVM根据元素类型和维度,创建相应的数组类。

果然是JVM这家伙自个偷偷创建了[I类。JVM不把数组类放到任何包中,也不给他们起个合法的标识符名称,估计是为了避免和JDK、第三方及用户自定义的类发生冲突。其实想想,JVM也必须动态生成数组类,因为Java数组类的数量与元素类型、维度(最多255)有关,相当相当多了,是没法预先声明好的。

0 iconst_2                   //将int型常量2压入操作数栈  
1 newarray 10 (int)    //将2弹出操作数栈,作为长度,创建一个元素类型为int, 维度为1的数组,并将数组的引用压入操作数栈  
3 astore_1                 //将数组的引用从操作数栈中弹出,保存在索引为1的局部变量(即a)中  
4 aload_1                  //将索引为1的局部变量(即a)压入操作数栈  
5 arraylength            //从操作数栈弹出数组引用(即a),并获取其长度(JVM负责实现如何获取),并将长度压入操作数栈  
6 istore_2                 //将数组长度从操作数栈弹出,保存在索引为2的局部变量(即i)中  
7 return                    //main方法返回  
复制代码

7 return //main方法返回

可见,在这段字节码中,根本就没有看见length这个成员变量,获取数组长度是由一条特定的指令arraylength实现编译器对Array.length这样的语法做了特殊处理,直接编译成了arraylength指令。另外,JVM创建数组类,应该就是由newarray这条指令触发的了。

对数组操作有一个工类类Arrays,大家可以学习一下,源码定义常量的方式我们也可以学习一下,用位运算,还是不错的。

image.png

然后,博主的能力不够了 什么字节码对象 反正大家了解那么多就行了。

链表

链表是离散存储线性结构


image.png

n个节点离散分配,彼此通过指针相连,每个节点只有一个前驱节点,每个节点只有一个后续节点,首节点没有前驱节点,尾节点没有后续节点。


image.png

链表优点:

  • 空间没有限制
  • 插入删除元素很快

链表缺点:

  • 存取速度很慢

链表又分了好几类:

  • 单向链表
  • 一个节点指向下一个节点
  • 双向链表
  • 一个节点有两个指针域
  • 循环链表
  • 能通过任何一个节点找到其他所有的节点,将两种(双向/单向)链表的最后一个结点指向第一个结点从而实现循环

用Java手撕链表

节点(Node)是由一个需要储存的对象及对下一个节点的引用组成的。也就是说,节点拥有两个成员:储存的对象、对下一个节点的引用。下面图是具体的说明:

image.png

让我们来实现一个简单的单向链表吧

先定义一个Node

image.png

添加一个节点

image.png


遍历节点


image.png


获取链表的长度

image.png

下面的代码实现的 增 删 改 查 基本上都有了 大家可以自己参考一下(自己比较菜,就写了几个简单的 很多东西需要算法的支持,就没写那么多来了)

/**
 * 
 */
public class MyLink {
    Node head = null; 
    /**
     * 
     */
    public class Node {
        public Object data;
        public Node next;
        public Node(Node next) {
            this.data = data;
            this.next = next;
        }
        public Node(Object data) {
            this.data = data;
        }
    }
  /**
     * 添加一个节点
     *
     * @param data
     */
    public void addNode(Object data) {
        Node addNode = new Node(data); //实例化一个节点
        //判断是不是第一个节点,如果是的话,我就把这个加入到头节点,因为是头节点所有就没有前驱节点了
        if (head == null) {
            head = addNode;
        } else {
            //如果不是第一个节点 得找到当前链表的最后一个节点 把要加入的节点的当做最后一个节点的后继节点
            Node temp = head; //定义一个临时节点 把头节点赋值给他 然后一直找,直到找到最后一个节点
            while (temp.next != null) {
                temp = temp.next;
            }
            //找到当前链表最后一个节点,然后把要加入的这个节点当做尾节点的后继
            temp.next = addNode;
        }
    }
    /**
     * 遍历链表
     */
    public void traverse() {
        if (null == head) {
            System.out.println("链表的长度为0");
        } else {
            /**
             * 说明不是只有一个元素
             */
            Node temp = head;
            while (temp != null) {
                System.out.println(temp.data);
                temp = temp.next;
            }
        }
    }
    /**
     * 获取链表的长度
     *
     * @return
     */
    public int length() {
        int length = 0;
        Node temp = head;
        while (temp != null) {
            length++;
            temp = temp.next;
        }
        return length;
    }
    // 首先需要判断指定位置是否正确
    public void insertNode(int index, Object value) {
        if (index < 1 || index > length() + 1) {
            System.out.println("下标校验不通过。");
            return;
        }
        //临时节点,从头节点开始
        Node temp = head;
        //记录遍历的当前位置
        int currentPos = 0;
        //初始化要插入的节点
        Node insertNode = new Node(value);
        while (temp.next != null) {
            //找到上一个节点的位置了
            if ((index - 1) == currentPos) {
                //temp表示的是上一个节点
                //将原本由上一个节点的指向交由插入的节点来指向
                insertNode.next = temp.next;
                //将上一个节点的指针域指向要插入的节点
                temp.next = insertNode;
                return;
            }
            currentPos++;
            temp = temp.next;
        }
    }
    /**
     * 删除指定位置的节点
     *
     * @param index
     */
    public void delete(int index) {
        //首先需要判断指定位置是否正确,
        if (index < 1 || index > length() + 1) {
            System.out.println("下标校验不通过。");
            return;
        }
        //临时节点,从头节点开始
        Node temp = head;
        //记录遍历的当前位置
        int currentPos = 0;
        while (temp.next != null) {
            //先找到要删除节点 上一个节点的位置
            if (index - 1 == currentPos) {
                //temp 表示要删除的节点
                //temp.next表示的是想要删除的节点
                //将想要删除的节点存储一下
                Node deleteNode = temp.next;
                //把想要删除的下一个节点由上一个节点交互
                temp.next = deleteNode.next;
                return;
            }
            currentPos++;
            temp = temp.next;
        }
    }
    public Object getOne(int index){
        //首先需要判断指定位置是否正确,
        if (index < 1 || index > length()) {
            System.out.println("下标校验不通过");
            return null;
        }
        //临时节点
        Node temp = head;
        //记录遍历的当前位置
        int currentPos = 1;
        while (temp!=null){
            if (index==currentPos){
                return temp.data;
            }
            currentPos++;
            temp=temp.next;
        }
        return null;
    }
    public static void main(String[] args) {
        MyLink myLink = new MyLink();
        myLink.addNode("1");
        myLink.addNode("aaaa");
        myLink.traverse();
        System.out.println("-------------------------------------");
        myLink.delete(1);
        myLink.addNode("333");
        myLink.traverse();
        System.out.println("-------------------------------------");
        Object one = myLink.getOne(1);
        System.out.println(one);
    }
}
复制代码

链表的一点总结

操作一个链表只需要知道它的头指针就可以做任何操作了

  • 添加数据到链表中
  • 遍历找到尾节点,插入即可(只要while(temp.next != null),退出循环就会找到尾节点)
  • 遍历链表
  • 从首节点(有效节点)开始,只要不为null,就输出
  • 给定位置插入节点到链表中
  • 将原本由上一个节点的指向交由插入的节点来指向
  • 上一个节点指针域指向想要插入的节点
  • 首先判断该位置是否有效(在链表长度的范围内)
  • 找到想要插入位置的上一个节点
  • 获取链表的长度
  • 每访问一次节点,变量++操作即可
  • 删除给定位置的节点
  • 将原本由上一个节点的指向后面一个节点
  • 首先判断该位置是否有效(在链表长度的范围内)
  • 找到想要插入位置的上一个节点
  • 对链表进行排序
  • 使用冒泡算法对其进行排序
  • 删除链表重复数据
  • 操作跟冒泡排序差不多,只要它相等,就能删除了~

还有很多就不一一列举,各位大佬要深入的自己深入吧,感觉脑子不够用,哈哈

结尾

数据结构的基础就讲那么多,也就是入门,后面还是靠自己,接下来我们要进入正题,讲我们的容器集合了。

因为博主也是一个开发萌新 我也是一边学一边写 我有个目标就是一周 二到三篇 希望能坚持个一年吧 希望各位大佬多提意见,让我多学习,一起进步。


目录
相关文章
|
23天前
|
存储 Java
Java中的HashMap和TreeMap,通过具体示例展示了它们在处理复杂数据结构问题时的应用。
【10月更文挑战第19天】本文详细介绍了Java中的HashMap和TreeMap,通过具体示例展示了它们在处理复杂数据结构问题时的应用。HashMap以其高效的插入、查找和删除操作著称,而TreeMap则擅长于保持元素的自然排序或自定义排序,两者各具优势,适用于不同的开发场景。
38 1
|
25天前
|
存储 Java
告别混乱!用Java Map优雅管理你的数据结构
【10月更文挑战第17天】在软件开发中,随着项目复杂度增加,数据结构的组织和管理至关重要。Java中的Map接口提供了一种优雅的解决方案,帮助我们高效、清晰地管理数据。本文通过在线购物平台的案例,展示了Map在商品管理、用户管理和订单管理中的具体应用,有效提升了代码质量和维护性。
80 2
|
25天前
|
存储 Java 开发者
Java Map实战:用HashMap和TreeMap轻松解决复杂数据结构问题!
【10月更文挑战第17天】本文深入探讨了Java中HashMap和TreeMap两种Map类型的特性和应用场景。HashMap基于哈希表实现,支持高效的数据操作且允许键值为null;TreeMap基于红黑树实现,支持自然排序或自定义排序,确保元素有序。文章通过具体示例展示了两者的实战应用,帮助开发者根据实际需求选择合适的数据结构,提高开发效率。
57 2
|
8天前
|
缓存 算法 Java
本文聚焦于Java内存管理与调优,介绍Java内存模型、内存泄漏检测与预防、高效字符串拼接、数据结构优化及垃圾回收机制
在现代软件开发中,性能优化至关重要。本文聚焦于Java内存管理与调优,介绍Java内存模型、内存泄漏检测与预防、高效字符串拼接、数据结构优化及垃圾回收机制。通过调整垃圾回收器参数、优化堆大小与布局、使用对象池和缓存技术,开发者可显著提升应用性能和稳定性。
30 6
|
14天前
|
存储 Java 索引
Java中的数据结构:ArrayList和LinkedList的比较
【10月更文挑战第28天】在Java编程世界中,数据结构是构建复杂程序的基石。本文将深入探讨两种常用的数据结构:ArrayList和LinkedList,通过直观的比喻和实例分析,揭示它们各自的优势与局限,帮助你在面对不同的编程挑战时做出明智的选择。
|
16天前
|
存储 C语言
【数据结构】手把手教你单链表(c语言)(附源码)
本文介绍了单链表的基本概念、结构定义及其实现方法。单链表是一种内存地址不连续但逻辑顺序连续的数据结构,每个节点包含数据域和指针域。文章详细讲解了单链表的常见操作,如头插、尾插、头删、尾删、查找、指定位置插入和删除等,并提供了完整的C语言代码示例。通过学习单链表,可以更好地理解数据结构的底层逻辑,提高编程能力。
44 4
|
17天前
|
算法 安全 搜索推荐
2024重生之回溯数据结构与算法系列学习之单双链表精题详解(9)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
数据结构王道第2.3章之IKUN和I原达人之数据结构与算法系列学习x单双链表精题详解、数据结构、C++、排序算法、java、动态规划你个小黑子;这都学不会;能不能不要给我家鸽鸽丢脸啊~除了会黑我家鸽鸽还会干嘛?!!!
|
17天前
|
存储 Web App开发 算法
2024重生之回溯数据结构与算法系列学习之单双链表【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
数据结构之单双链表按位、值查找;[前后]插入;删除指定节点;求表长、静态链表等代码及具体思路详解步骤;举例说明、注意点及常见报错问题所对应的解决方法
|
22天前
|
存储 算法 Java
Java 中常用的数据结构
【10月更文挑战第20天】这些数据结构在 Java 编程中都有着广泛的应用,掌握它们的特点和用法对于提高编程能力和解决实际问题非常重要。
24 6
|
23天前
|
存储 Java 开发者
Java中的Map接口提供了一种优雅的方式来管理数据结构,使代码更加清晰、高效
【10月更文挑战第19天】在软件开发中,随着项目复杂度的增加,数据结构的组织和管理变得至关重要。Java中的Map接口提供了一种优雅的方式来管理数据结构,使代码更加清晰、高效。本文通过在线购物平台的案例,展示了Map在商品管理、用户管理和订单管理中的具体应用,帮助开发者告别混乱,提升代码质量。
26 1