项目实战 — 消息队列(3){数据库操作}

目录

一、SQLite

🍅 1、添加依赖

🍅 2、修改配置文件后缀(properties -> yaml)

 🍅 3、编写配置文件

二、建立数据表

三、添加插入和删除方法

 四、整合数据库操作(DataBaseManger类)

🍅 1、初始化方法init()

🍅 2、编写代码

 五、对数据库操作进行单元测试

 🍅 1、“准备工作”和“收尾工作”

🍅 2、编写测试类进行用力测试

🎈测试init()方法

🎈测试 交换机(插入和删除)

 * Delete 

🎈  测试DataBaseManager的队列(插入和删除)

🎈测试Binding

六、小结

🍅 1、运行时可能会报错

🍅 2、已经完成的任务


一、SQLite

MySQL数据库本身是比较重量的,所以这里使用SQLite,SQLite是更轻量的数据库

SQLite的优点:

  • 服务器性能和内存要求低
  • 减少了能源消耗
  • 自成一体,便于携带
  • 默认包含在所有的PHP安装中

它是一个本地的数据库, 操作该数据库就相当与直接操作本地的硬盘文件。

SQLite应用是很广泛的,在一些性能不高的设备上,比如移动端和嵌入式设备,就可以使用SQLite。

而且,也可以通过mybatis来使用。这里创建一个mapper文件夹将有关mybatis的xml文件放到其中。

🍅 1、添加依赖

使用过SQLite不用额外安装,直接引入依赖即可。

<!-- https://mvnrepository.com/artifact/org.xerial/sqlite-jdbc -->
<dependency>
    <groupId>org.xerial</groupId>
    <artifactId>sqlite-jdbc</artifactId>
    <version>3.41.0.0</version>
</dependency>

引入以后,reload。

🍅 2、修改配置文件后缀(properties -> yaml)

这里的配置文件主要使用yaml格式。

 

 🍅 3、编写配置文件

对于SQLite文件来说,不需要指定用户名和密码,原因如下:

        首先,MySQL是一个客户端服务器结构的程序,一个数据库服务器,就会对应有很多个客户端来访问;

        但是,SQLite不同,它不是一个客户端服务器结构的程序,只有自己一个人能够访问,把数据放在本地文件上面,只有当前主机才能访问,和网络无关。

spring:
  datasource:
#    SQLite数据库是将数据存储在当前硬盘的某个指定的为文件中
#    这是一个相对路径,运行以后,这个文件就会出现在当前项目的目录中
    url: jdbc:sqlite:./data/meta.db
#    SQLite并不需要指定用户名和密码
    username:
    password:
    driver-class-name: org.sqlite.JDBC
  mybatis:
    mapper-location: classpath:mapper/**Mapper

二、建立数据表

SQLite没有建数据库的这概念,一个.db文件就相当于一个库,程序一启动就会自动建库。

主要建立以下几个表:

        * 交换机存储

        * 队列存储

        * 绑定存储

这里通过代码自动完成建表操作,使用Mybatis执行SQL语句。

MyBatis基本使用流程回顾:

        (1)创建一个interface,描述有哪些方法要给java代码使用

        (2)创建对应的xml,通过xml实现interface中的抽象方法 

 创建一个mapper包,放置interface。

 

 在MeteMapper接口中建立三个核心的建表方法:

@Mapper
public interface MetaMapper {
    //提供三个核心建表方法
    void createExchangeTable();
    void createQueueTable();
    void createBindingTable();
}

然后再Mapper文件夹中建立对应的xml文件,

编写myBatis文件,关于myBatis框架的使用,如果不了解,可以参考博客https://blog.csdn.net/qq_52136076/category_12392841.html?spm=1001.2014.3001.5482

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.tigermq.mqserver.mapper.MetaMapper">
    <update id="createExchangeTable">
        create table if not exists exchange(
            name varchar(50) primary key,
            type int,
            durable boolean,
        )
    </update>

    <update id="createQueueTable">
        create table if not exists queue(
            name varchar(50) primary key,
            durable boolean,
            exclusive boolean,
        )
    </update>

    <update id="createBindingTable">
        create table if not exists binding(
            exchangeName varchar(50),
            queueName varchar(50),
            bindingKey varchar(256)
        )
    </update>

</mapper>

三、添加插入和删除方法

 在MentaMapper接口中添加插入和删出方法,其中

  对于交换机和队列这两个表,由于使用name作为主键,所以直接按照name进行删除即可。

  而对于绑定来说,没有主键,删除操作其实是针对exchangeName和queueName两个维度进行筛选。

//    针对三个表,进行插入\删除\查找操作
    @Insert("insert into exchange values(#{name},#{type},#{durable})")
    void insertExchange(Exchange exchange);

    @Select("select * from exchange")
    List<Exchange> selectAllExchanges();

    @Delete("delete from exchange where name = #{exchangeName}")
    void deleteExchange(String exchangeName);

    @Insert("insert into queue values (#{name},#{durable},#{exclusive})")
    void insertQueue(MSGQueue queue);

    @Select("select * from queue")
    List<MSGQueue> selectAllQueues();

    @Delete("delete from queue where name = (#{queueName})")
    void deleteQueue(String queueName);

    @Insert("insert into binding values(#{exchangeName},#{queueName},#{bindingKey})")
    void insertBinding(Binding binding);

    @Select("select * from binding")
    List<Binding> selectAllBindings();

    @Delete("delete from binding where exchangeName = #{exchangeName} and queueName = #{queueName}")
    void deleteBinding(Binding binding);

 四、整合数据库操作(DataBaseManger类)

创建一个包datacenter .都是针对数据进行操作

 在包中创建一个DataBaseManger类

🍅 1、初始化方法init()

首先,由于构造方法初始化一般都不会带有很多逻辑(也可以用,但是一般习惯不适用构造方法),而这里初始化数据库需要很多业务逻辑,所以就自定义了一个init()方法。

数据库的初始化其实就是 建库建表 + 插入一些默认数据 

基本逻辑:

        如果数据已经存在了,就不做出任何操作

        如果数据不存在,则创 建库 + 表 + 构造默认数据

如何判断数据库是否存在?

        答:判断meta.db文件是否存在。

🍅 2、编写代码

根据前面的逻辑,编写如下代码:


/*
* 通过这个类,整合之前的数据库操作
* */
public class DataBaseManger {
//    从spring中拿到现成的对象
    private MetaMapper metaMapper;

//    针对数据库进行初始化
//    因为这里的的初始化需要带有业务逻辑,所以就不适用构造方法,因为构造方法一般不会涉及到很多的业务逻辑
    public void init(){
//          手动的获取到MetaMapper
        metaMapper = TigerMqApplication.context.getBean(MetaMapper.class);

        if(!checkDBExists()){

//            创建一个data目录
            File dataDir = new File("/data");
            dataDir.mkdirs();
//            数据库不存在,就进行建库建表操作
            createTable();
//            创建默认数据
            createDafaultData();
            System.out.println("[DataBaseManger]数据库初始化完成");

        }else{
//            数据库存在了
            System.out.println("[DataBaseManger]数据库已经存在");
        }

    }

    public void deleteDB(){
        File file = new File("./data/meta.db");
        boolean ret = file.delete();
        if (ret){
            System.out.println("[DataBaseManager]DB文件已经删除成功");
        }else{
            System.out.println("[DataBaseManager]DB文件删除失败");
        }


        //这个Delete只能删除空目录,所以删除的时候要保证目录是空的
        File dataDir = new File("./data");
        ret = dataDir.delete();
        if(ret){
            System.out.println("[DataBaseManger] 删除数据库目录成功");
        }else {
            System.out.println("[DataBaseManger] 删除数据库目录失败");
        }
    }

    //    判断文件是否存在
    private boolean checkDBExists() {
        File file = new File("./data/meta.db");
        if (file.exists()){
            return true;
        }
        return false;
    }

//    建表
//    建库操作不需要手动执行
//    首次执行,会自动创建出meta.db文件(mybatis会帮助我们完成)
    private void createTable() {
//        下面这些方法之前已经创建过了
        metaMapper.createExchangeTable();
        metaMapper.createQueueTable();
        metaMapper.createBindingTable();
        System.out.println("[DataBaseManger]创建表完成");
    }

//    创建默认数据
//    此处主要是添加一个默认的交换机:DIRECT
    private void createDafaultData() {
//        构造一个默认的交换机
        Exchange exchange = new Exchange();
        exchange.setName("");
        exchange.setType(ExchangeType.DIRECT);
        exchange.setDurable(true);
        exchange.setAutoDelete(false);
        metaMapper.insertExchange(exchange);
        System.out.println("[DataBaseManger]创建初始数据已经完成");
    }

//    其他的一些数据库操作
    public void  insertExchange(Exchange exchange){
        metaMapper.insertExchange(exchange);
    }

    public List<Exchange> selectAllExchanges(){
        return metaMapper.selectAllExchanges();
    }
    public void deleteExchange(String exchangeName){
        metaMapper.deleteExchange(exchangeName);
    }

    public void insertQueue(MSGQueue queue){
        metaMapper.insertQueue(queue);
    }

    public List<MSGQueue> selectAllQueues(){
        return metaMapper.selectAllQueues();
    }

    public void deleteQueue(String queueName){
        metaMapper.deleteQueue(queueName);
    }

    public void insertBinding(Binding binding){
        metaMapper.insertBinding(binding);
    }

    public List<Binding> selectAllBindings(){
        return metaMapper.selectAllBindings();
    }
    public void deleteBinding(Binding binding){
        metaMapper.deleteBinding(binding);
    }
}

但是,上面的metaMapper还没有进行构造,为空,直接运行就会报错,但是这里不能使用@Autowired进行对象注入,因为里不打算把DataBaseManager设置为一个Bean,所以我们就,我们可以在启动类中,对对象metaMapper进行构造。

在启动类中:

@SpringBootApplication
public class TigerMqApplication {
    public static ConfigurableApplicationContext context;

    public static void main(String[] args) {
        context = SpringApplication.run(TigerMqApplication.class, args);
    }

}

 在init()方法中手动获取到metaMapper对象:添加以下代码

//          手动的获取到MetaMapper
        metaMapper = TigerMqApplication.context.getBean(MetaMapper.class);

 五、对数据库操作进行单元测试

创建测试类DataBaseManagerTests

 🍅 1、“准备工作”和“收尾工作”

添加两个类,主要是为了放置每个测试用力之间不会收到干扰而创建的。

首先是“准备工作”setUp(),主要是为了调用init()方法,初始化数据库

然后是“收尾工作”tearDown(),主要是为了删除掉.db文件。

@BeforEach指的是每个用例执行前都会调用这个方法

@AfterEach指的是每个用力执行完后调用这个方法

编写代码:

@SpringBootTest
public class DataBaseMangerTests {
    private DataBaseManger dataBaseManger = new DataBaseManger();

//    编写多个方法,每个方法都是一组单元测试用例
//    编写两个方法,分别用于进行“准备工作”和收尾工作
//    这是为了让每个测试用力之间不会收到干扰而创建的

//使用该方法,进行准备工作,每个用力执行前都要调用这个方法
    @BeforeEach
    public void setUp(){
//        由于init中,需要经过context对象拿到metaMapper示例
//        所以需要先把context对象构造出来
        TigerMqApplication.context = SpringApplication.run(TigerMqApplication.class);
        dataBaseManger.init();
    }



//    该方法用来执行收尾工作,每个用例执行后,需要调用这个方法
    @AfterEach
    public void tearDown(){
//        及那个数据库清空,删掉.db文件
//        删除之前需要关闭context对象。
//        原因是因为context持有了MetaMapper类的实力对象,
//        而该对象打开了meta.db文件,而在打开的情况下,删除操作是不能进行的
        TigerMqApplication.context.close();
        dataBaseManger.deleteDB();
    }
}

🍅 2、编写测试类进行用力测试


        🎈测试init()方法

 @Test
    public void  testInitTable(){
//        由于init()方法已经被调用过了,直接在测试用力代码中检查当前数据库状态
//        从数据库中查询数据是否符合预期
//        查交换机表,会有一个匿名exchange数据
        List<Exchange> exchangeList = dataBaseManger.selectAllExchanges();
        List<MSGQueue> queueList = dataBaseManger.selectAllQueues();
        List<Binding> bindingList = dataBaseManger.selectAllBindings();

//       使用断言
//       判断1和exchangeList是否相等
//       assertEquals(预期值,实际值)
        Assertions.assertEquals(1,exchangeList.size());
        Assertions.assertEquals("",exchangeList.get(0).getName());
        Assertions.assertEquals(ExchangeType.DIRECT,exchangeList.get(0).getType());
        Assertions.assertEquals(0,queueList.size());
        Assertions.assertEquals(0,bindingList.size());
    }

       


🎈测试 交换机(插入和删除)

* Insert

private Exchange createTestExchange(String exchangeName){
        Exchange exchange = new Exchange();
        exchange.setName(exchangeName);
        exchange.setType(ExchangeType.FANOUT);
        exchange.setDurable(true);
        return exchange;
    }


@Test
    public void testInsertExchange(){
//        构造一个Exchange对象,插入到数据库中,再查询出来,看是否符合预期
        Exchange exchange = createTestExchange("testExchange");
        dataBaseManger.insertExchange(exchange);
        List<Exchange> exchangeList = dataBaseManger.selectAllExchanges();
        Assertions.assertEquals(2,exchangeList.size());
        Exchange newExchange = exchangeList.get(1);
        Assertions.assertEquals("testExchange",newExchange.getName());
        Assertions.assertEquals(ExchangeType.FANOUT,newExchange.getType());
        Assertions.assertEquals(true,newExchange.isDurable());
    }

 

 * Delete 

@Test
    public void testDeleteExchange(){
//        先构造一个交换机
        Exchange exchange = createTestExchange("testExchange");
        dataBaseManger.insertExchange(exchange);
        List<Exchange> exchangeList = dataBaseManger.selectAllExchanges();
        Assertions.assertEquals(2,exchangeList.size());
        Assertions.assertEquals("testExchange",exchangeList.get(1).getName());

//        进行删除操作
        dataBaseManger.deleteExchange("testExchange");
//        再次查询
        exchangeList = dataBaseManger.selectAllExchanges();
        Assertions.assertEquals(1,exchangeList.size());
        Assertions.assertEquals("",exchangeList.get(0).getName());
    }


🎈  测试DataBaseManager的队列(插入和删除)

* Insert

private MSGQueue createTestQueue(String queueName){
        MSGQueue queue = new MSGQueue();
        queue.setName(queueName);
        queue.setDurable(true);
        queue.setExclusive(false);
        return queue;
    }
    @Test
    public void testInsertQueue(){
        MSGQueue queue = createTestQueue("testQueue");
        dataBaseManger.insertQueue(queue);

        List<MSGQueue> queueList = dataBaseManger.selectAllQueues();

        Assertions.assertEquals(1,queueList.size());
        MSGQueue newQueue = queueList.get(0);
        Assertions.assertEquals("testQueue",newQueue.getName());
    }

* Delete 

 @Test
    public void testDeleteQueue(){
        MSGQueue queue = createTestQueue("testQueue");
        dataBaseManger.insertQueue(queue);

        List<MSGQueue> queueList = dataBaseManger.selectAllQueues();

        Assertions.assertEquals(1,queueList.size());

//        删除
        dataBaseManger.deleteQueue("testQueue");
        queueList = dataBaseManger.selectAllQueues();
        Assertions.assertEquals(0,queueList.size());
    }

 

         


🎈测试Binding

* Insert

public Binding createTestBinding(String exchangeName,String queueName){
        Binding binding = new Binding();
        binding.setExchangeName(exchangeName);
        binding.setQueueName(queueName);
        binding.setBindingKey("testBindingKey");
        return binding;
    }
    @Test
    public void testInsertBinding(){
        Binding binding = createTestBinding("testExchange","tesQueue");
        dataBaseManger.insertBinding(binding);

        List<Binding> bindingList = dataBaseManger.selectAllBindings();
        Assertions.assertEquals(1,bindingList.size());
        Assertions.assertEquals("testExchange",bindingList.get(0).getExchangeName());
        Assertions.assertEquals("tesQueue",bindingList.get(0).getQueueName());
        Assertions.assertEquals("testBindingKey",bindingList.get(0).getBindingKey());
    }

*Delete 

   @Test
    public void testDeleteBinding(){
        Binding binding = createTestBinding("testExchange","testQueue");
        dataBaseManger.insertBinding(binding);

        List<Binding> bindingList = dataBaseManger.selectAllBindings();
        Assertions.assertEquals(1,bindingList.size());

//        删除操作
        Binding toDeleteBinding = createTestBinding("testExchange","testQueue");
        dataBaseManger.deleteBinding(toDeleteBinding);
        bindingList = dataBaseManger.selectAllBindings();
        Assertions.assertEquals(0,bindingList.size());
    }


六、小结

🍅 1、运行时可能会报错

在刚开始测试运行的过程中,代码可能会报错。我在运行的时候就遇到了以下的错误,你们可能也会遇见

解决方案我是上网查到的:

Error creating bean with name ‘dataSource‘ defined in class path resource解决办法_张道长的博客-CSDN博客

🍅 2、已经完成的任务

(1)项目需求分析:项目实战 — 消息队列(1) {需求分析}_‍️藿香正气水的博客-CSDN博客

(2)设计核心类: 项目实战 — 消息队列(2){创建核心类}_‍️藿香正气水的博客-CSDN博客

(3)设计数据库,并且针对数据库代码进行了单元测试

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/60658.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

go Channel

channel 单纯地将函数并发执行是没有意义的。函数与函数之间需要交换数据才能体现出并发执行函数的意义。 虽然可以使用共享内存进行数据交换&#xff0c;但是共享内存在不同的goroutine中很容易发生竞态问题。为了保证数据交换的准确性&#xff0c;必须使用互斥量对内存进行…

STM32基础入门学习笔记:开发板 电路原理与驱动编程

文章目录&#xff1a; 一&#xff1a;触摸按键 1.触摸按键驱动程序&#xff08;点击&#xff09; touch_key.h touch_key.c main.c 2.按键双击和长按程序 touch_key.h touch_key.c main.c 3.触摸按键滑动程序 main.c 二&#xff1a;数码管显示 1.数码管RTC时钟LE…

MCUXpresso for VS Code -- 基于VSCode开发RT1176

MCUXpresso for VS Code 是nxp推出插件&#xff0c;旗下MCX LPC, Kinetis和i.MX rt等MCU&#xff0c;都能在VS Code平台进行嵌入式开发。功能框图如下&#xff1a; 前期准备&#xff1a; 软件环境: windows(实际可以跨系统&#xff0c;linux和mac没有测试) VS Code ninja CMa…

git之reflog分析

写在前面 本文一起看下reflog命令。 1&#xff1a;场景描述 在开发的过程中&#xff0c;因为修改错误&#xff0c;想要通过git reset命令恢复到之前的某个版本&#xff0c;但是选择提交ID错误&#xff0c;导致多恢复了一个版本&#xff0c;假定&#xff0c;该版本对应的内容…

【Linux】进程间通信——system V共享内存

目录 写在前面的话 System V共享内存原理 System V共享内存的建立 代码实现System V共享内存 创建共享内存shmget() ftok() 删除共享内存shmctl() 挂接共享内存shmat() 取消挂接共享内存shmdt() 整体通信流程的实现 写在前面的话 上一章我们讲了进程间通信的第一种方式…

【2023年电赛国一必备】A题报告模板--可直接使用

任务 图1 任务内容 要求 图2 基本要求内容 图3 发挥部分内容 说明 图4 说明内容 评分标准 图5 评分内容 正文 &#xff08;部分&#xff09; 摘要 本实验旨在设计和制作一个由两个单相逆变器组成的并联系统&#xff0c;用于为电阻负载供电或并入220V电网。采用基于STM…

[openCV]基于拟合中线的智能车巡线方案V2

import cv2 as cv import os import numpy as np# 遍历文件夹函数 def getFileList(dir, Filelist, extNone):"""获取文件夹及其子文件夹中文件列表输入 dir&#xff1a;文件夹根目录输入 ext: 扩展名返回&#xff1a; 文件路径列表"""newDir d…

ospf于mgre中应用(直连与星型拓扑)

题目 地址配置 R1&#xff1a; R2&#xff1a; R3&#xff1a; R4&#xff1a; R5&#xff1a; ISP&#xff1a; R1/2/3的星型拓扑结构 R1配置&#xff1a; interface Tunnel0/0/0 ip address 192.168.6.1 255.255.255.0 tunnel-protocol gre p2mp source 200.1.1.1 ospf …

Docker网络模型使用详解(2)Docker网络模式

安装Docker时会自动创建3个网络&#xff0c;可以使用docker network ls命令列出这些网络。 [rootlocalhost ~]# docker network ls NETWORK ID NAME DRIVER SCOPE ebcfad6f4255 bridge bridge local b881c67f8813 compose_lnmp_lnmp…

blender凹凸感和置换形变

一、怎么做出凹凸感 需要三个部分的内容&#xff1a; 1、一个基础的纹理&#xff1a;告诉计算机需要用一个什么样的纹理做凹凸&#xff0c;纹理一般采用黑白&#xff0c;在计算机里面&#xff0c;从 0 - 1之间的值可以用从黑到白之间不同的灰度来表示因此&#xff0c;有一张黑白…

linuxARM裸机学习笔记(3)----主频和时钟配置实验

引言&#xff1a;本文主要学习当前linux该如何去配置时钟频率&#xff0c;这也是重中之重。 系统时钟来源&#xff1a; 32.768KHz 晶振是 I.MX6U 的 RTC 时钟源&#xff0c; 24MHz 晶振是 I.MX6U 内核 和其它外设的时钟源 1. 7路PLL时钟源【都是从24MHZ的晶振PLL而来…

大数据教材推荐|Python数据挖掘入门、进阶与案例分析

主 编&#xff1a; 卢滔&#xff0c;张良均&#xff0c;戴浩&#xff0c;李曼&#xff0c;陈四德 出版社&#xff1a; 机械工业出版社 内容提要 本书从实践出发&#xff0c;结合11个“泰迪杯”官方推出的赛题&#xff0c;按照赛题的难易程度进行排序&#xff0c;由浅入深…

20天突破英语四级高频词汇——第①天

2&#xfeff;0天突破英语四级高频词汇~第一天加油(ง •_•)ง&#x1f4aa; &#x1f433;博主&#xff1a;命运之光 &#x1f308;专栏&#xff1a;英语四级高频词汇速记 &#x1f30c;博主的其他文章&#xff1a;点击进入博主的主页 目录 2&#xfeff;0天突破英语四级…

无涯教程-Lua - for语句函数

for 循环是一种重复控制结构&#xff0c;可让您有效地编写需要执行特定次数的循环。 for loop - 语法 Lua编程语言中 for 循环的语法如下- for init,max/min value, increment dostatement(s) end 这是 for 循环中的控制流程- 首先执行 init 步骤&#xff0c;并且仅执行一…

LeetCode-654-最大二叉树

一&#xff1a;题目描述 给定一个不重复的整数数组 nums 。 最大二叉树 可以用下面的算法从 nums 递归地构建: 创建一个根节点&#xff0c;其值为 nums 中的最大值。递归地在最大值 左边 的 子数组前缀上 构建左子树。递归地在最大值 右边 的 子数组后缀上 构建右子树。 返回…

开源免费用|Apache Doris 2.0 推出跨集群数据复制功能

随着企业业务的发展&#xff0c;系统架构趋于复杂、数据规模不断增大&#xff0c;数据分布存储在不同的地域、数据中心或云平台上的现象越发普遍&#xff0c;如何保证数据的可靠性和在线服务的连续性成为人们关注的重点。在此基础上&#xff0c;跨集群复制&#xff08;Cross-Cl…

从零开始:手把手搭建 RocketMQ 单节点、集群节点实例

&#x1f52d; 嗨&#xff0c;您好 &#x1f44b; 我是 vnjohn&#xff0c;在互联网企业担任 Java 开发&#xff0c;CSDN 优质创作者 &#x1f4d6; 推荐专栏&#xff1a;Spring、MySQL、Nacos、Java&#xff0c;后续其他专栏会持续优化更新迭代 &#x1f332;文章所在专栏&…

javaAPI(二):String、StringBuffer、StringBuilder

String、StringBuffer、StringBuilder的异同&#xff1f; String&#xff1a;不可变字符序列&#xff1b;底层结构使用char[]存储&#xff1b; StringBuffer&#xff1a;可变字符序列&#xff1b;线程安全的&#xff0c;效率低&#xff1b;底层结构使用char[]存储&#xff1b; …

LangChain+ChatGLM大模型应用落地实践(一)

LLMs的落地框架&#xff08;LangChain&#xff09;&#xff0c;给LLMs套上一层盔甲&#xff0c;快速构建自己的新一代人工智能产品。 一、简介二、LangChain源码三、租用云服务器实例四、部署实例 一、简介 LangChain是一个近期非常活跃的开源代码库&#xff0c;目前也还在快速…

(树) 剑指 Offer 32 - I. 从上到下打印二叉树 ——【Leetcode每日一题】

❓剑指 Offer 32 - I. 从上到下打印二叉树 难度&#xff1a;中等 从上到下打印出二叉树的每个节点&#xff0c;同一层的节点按照从左到右的顺序打印。 例如: 给定二叉树: [3,9,20,null,null,15,7], 3/ \9 20/ \15 7返回&#xff1a; [3,9,20,15,7]提示&#xff1a; 节…