当前位置: 首页 > news >正文

根据源码,模拟实现 RabbitMQ - 实现消息持久化,统一硬盘操作(3)

目录

一、实现消息持久化

1.1、消息的存储设定

1.1.1、存储方式

1.1.2、存储格式约定

1.1.3、queue_data.txt 文件内容

 1.1.4、queue_stat.txt 文件内容

1.2、实现 MessageFileManager 类

1.2.1、设计目录结构和文件格式

1.2.2、实现消息的写入

1.2.3、实现消息的删除(随机访问文件)

1.2.4、获取队列文件中所有有效消息

1.2.5、GC 机制

1.2.6、GC 拓展

二、统一硬盘操作


一、实现消息持久化


1.1、消息的存储设定

1.1.1、存储方式

传输的 Message 消息因该如何在硬盘上存储?我们应当考虑一下几点:

  1. 消息操作并不涉及到复杂的增删改查.
  2. 消息数量可能会非常多,数据库访问的效率不是很高.

因此这里不使用数据库进行存储,而是把消息存储在文件中~

1.1.2、存储格式约定

消息是依附于队列的,因此存储的时候,就把消息按照 队列 维度展开.

根据上一章我们讲到数据库的存储,因此我们已经有了 data 目录(meta.db 就在这个目录中),这里我们约定 —— 一个队列就是一个文件目录,每个对列的文件目录下有两个文件,来存储消息,例如下图:

  1. 第一个文件 queue_data.txt:用来保存消息的内容;
  2. 第二个文件 queue_stat.txt:用来保存消息的统计信息;

1.1.3、queue_data.txt 文件内容

这里约定,queue_data.txt 文件中包含若干个消息,每个消息都以二进制的方式存储,每个消息由两个部分构成,

  1. 第一个部分约定占用 4 个字节,用来保存消息的长度(防止粘包问题).
  2. 第二个部分为具体的二进制消息数据(Message 对象序列化后的数据).

如下图:

 1.1.4、queue_stat.txt 文件内容

使用这个文件,来保存消息的统计信息。

这里只存一行文本格式的数据,并且只有两列:

  1. 第一列是 queue_data.txt 中总的消息数目.
  2. 第二列是 queue_data.txt 中的有效消息数目.

这两者使用 \t 分割,形如:2000\t1500

1.2、实现 MessageFileManager 类

1.2.1、设计目录结构和文件格式

定义一个内部类,表示队列的统计信息(优先考虑 static,和外类解耦合).

    static public class Stat {//对于这样的简单类定义成 public 就不用 get set 方法了,类似于 C 的结构体public int totalCount;public int validCount;}

通过以下方法获取队列对应消息文件的路径,以及队列 数据/统计 文件的路径.

    /*** 用来获取指定队列对应的消息文件所在路径* @param queueName* @return*/private String getQueueDir(String queueName) {return "./data/" + queueName;}/*** 用来获取该队列的消息数据文件路径* 此处使用 txt 文件,存储二进制数据,实际上不太合适,但也先这样吧~* 跟适合使用 .bin / .dat* @param queueName* @return*/private String getQueueDataPath(String queueName) {return getQueueDir(queueName) + "/queue_data.txt";}/*** 用来获取该队列的消息统计文件路径* @param queueName* @return*/private String getQueueStatPath(String queueName) {return getQueueDir(queueName) + "/queue_stat.txt";}

通过以下方法实现队列 统计 文件的读写(便于后续创建文件时对 统计文件 的初始化).

    /*** 从文件中读取队列消息统计信息* @param queueName* @return*/private Stat readStat(String queueName) {Stat stat = new Stat();try(InputStream inputStream = new FileInputStream(getQueueStatPath(queueName))) {Scanner scanner = new Scanner(inputStream);stat.totalCount = scanner.nextInt();stat.validCount = scanner.nextInt();return stat;} catch (IOException e) {throw new RuntimeException(e);}}/*** 将队列消息统计信息写入文件* @param queueName* @param stat*/private void writeStat(String queueName, Stat stat) {try(OutputStream outputStream = new FileOutputStream(getQueueStatPath(queueName))) {PrintWriter printWriter = new PrintWriter(outputStream);printWriter.write(stat.totalCount + "\t" + stat.validCount);printWriter.flush();} catch (IOException e) {throw new RuntimeException(e);}}

通过以下方法来创建和销毁文件和目录

    /*** 创建队列对应的文件和目录* @param queueName*/public void createQueueFiles(String queueName) throws IOException {//1.创建队列对应的消息目录File baseDir = new File(getQueueDir(queueName));if(!baseDir.exists()) {//不存在,就创建这个目录boolean ok = baseDir.mkdirs();if (!ok) {throw new IOException("创建目录失败!baseDir=" + baseDir.getAbsolutePath());}}//2.创建队列数据文件File queueDataFile = new File(getQueueDataPath(queueName));if(!queueDataFile.exists()) {boolean ok = queueDataFile.createNewFile();if(!ok) {throw new IOException("创建文件失败! queueDataFile=" + queueDataFile.getAbsolutePath());}}//3.创建消息统计文件File queueStatFile = new File(getQueueStatPath(queueName));if(!queueStatFile.exists()) {boolean ok = queueStatFile.createNewFile();if(!ok) {throw new IOException("创建文件失败! queueStatFile=" + queueStatFile.getAbsolutePath());}}//4.给消息统计文件,设定初始值. 0\t0Stat stat = new Stat();stat.totalCount = 0;stat.validCount = 0;writeStat(queueName, stat);}/*** 删除队列的目录和文件* 此方法的用处:队列也是可以被删除的,队列删除之后,就需要调用此方法,删除对应的消息文件之类的* @param queueName* @throws IOException*/public void destroyQueueFiles(String queueName) throws IOException {//先删除里面的文件,再删除目录File queueDataFile = new File(getQueueDataPath(queueName));boolean ok1 = queueDataFile.delete();File queueStatFile = new File(getQueueStatPath(queueName));boolean ok2 = queueStatFile.delete();File baseDir = new File(getQueueDir(queueName));boolean ok3 = baseDir.delete();if(!ok1 || !ok2 || !ok3) {//任意一个失败,都算整体删除失败throw new IOException("删除队列目录和文件失败! baseDir=" + baseDir.getAbsolutePath());}}

1.2.2、实现消息的写入

消息写入主要分为以下四步:

  1. 先检查当前文件是否存在
  2. 把 Message 对象进行序列化,转化成 二进制 字节数组
  3. 根据当前队列文件长度,计算出 Message 对象的 offsetBeg 和 offsetEnd
  4. 将 message 数据追加到文件末尾
  5. 更新消息统计文件内容
    /*** 检查队列的目录和文件是否存在* 如果后续有生产者 broker server 生产消息了,这个消息就需要被记录到文件上(持久化的前提是文件必须要存在)* @param queueName* @return*/public boolean checkFilesExits(String queueName) {//数据文件和统计文件都判断存在File queueDataFile = new File(getQueueDataPath(queueName));if(!queueDataFile.exists()) {return false;}File queueStatFile = new File(getQueueStatPath(queueName));if(!queueStatFile.exists()) {return false;}return true;}/*** 将一个新的消息(message)放到队列文件中(queue)* @param queue* @param message*/public void sendMessage(MSGQueue queue, Message message) throws MqException, IOException {//1.先检查当前文件是否存在if(!checkFilesExits(queue.getName())) {throw new MqException("[MessageFileManager] 队列对应的文件不存在! queueName=" + queue.getName());}//2.把 Message 对象进行序列化,转化成 二进制 字节数组byte[] messageBinary = BinaryTool.toBytes(message);//3.根据当前队列文件长度,计算出 Message 对象的 offsetBeg 和 offsetEnd//将新的 Message 数据,写入到文件的末尾,那么此时 offsetBeg = 4 + 当前文件总长度 (4 个字节是我们约定好用来表示信息长度的)// offsetEnd = 当前文件总长度 + 4 + message 长度//这里为了避免写操作引发线程安全问题synchronized(queue) {File queueDataFile = new File(getQueueDataPath(queue.getName()));message.setOffsetBeg(queueDataFile.length() + 4);message.setOffsetEnd(queueDataFile.length() + 4 + messageBinary.length);//4.将 message 数据追加到文件末尾try(OutputStream outputStream = new FileOutputStream(queueDataFile, true)) { //这里 true 表示追加到文件末尾try(DataOutputStream dataOutputStream = new DataOutputStream(outputStream)) {//这里用 writeInt 来写 message 长度是为了保证占 4 个字节(直接用 write 只会写一个字节)dataOutputStream.writeInt(messageBinary.length);//写入消息体dataOutputStream.write(messageBinary);dataOutputStream.flush();}}//5.更新消息统计文件内容Stat stat = readStat(queue.getName());stat.validCount += 1;stat.totalCount += 1;writeStat(queue.getName(), stat);}}

1.2.3、实现消息的删除(随机访问文件)

这里的删除逻辑实际上就是把硬盘中存储的这个数据里面的 isValid 属性,设置成 0,然后再写入硬盘.

  1. 先把文件中这段数据读出来,还原回 Message 对象
  2. 把 isValid 改成 0
  3. 把上述数据重新写回到文件中
  4. 更新统计文件

为什么这里采用这样的删除方式?

新增消息可以直接把消息追加到文件末尾,而删除消息不好弄~  因为文件可以视为是一个 “顺序表” 的结构,因此如果直接删除中间的元素,就需要设计到 “顺序表搬运” 这样的操作,效率是非常低的.

因此这里使用逻辑删除的方式比较合适~~

  • 当  isValid 为 1,表示有效消息.
  • 当 isValid 为 0 ,表示无效消息

随着时间的推移文件可能会越来越大,并且可能存在大量的无效消息,针对这种情况,就需要对当前消息数据文件进行垃圾回收机制(后续会讲到).

    public void deleteMessage(MSGQueue queue, Message message) throws IOException, ClassNotFoundException {//读写文件注意线程安全问题synchronized(queue) {try (RandomAccessFile randomAccessFile = new RandomAccessFile(getQueueDataPath(queue.getName()), "rw")) {//1.先从文件中读出对应的 Message 数据byte[] bufferSrc = new byte[(int) (message.getOffsetEnd() - message.getOffsetBeg())];randomAccessFile.seek(message.getOffsetBeg());randomAccessFile.read(bufferSrc); //类似于食堂打饭//2.把当前读出来的二进制数据,反序列化成 Message 对象Message diskMessage = (Message) BinaryTool.fromBytes(bufferSrc);//3.把 isValid 设置成无效diskMessage.setIsValid((byte) 0x0);//此处不用把形参中的 message 的 isValid 设为 0,因为这个参数代表内存中管理的 Message 对象//这个对象马上就会被从内存中删除//4.重新写入文件byte[] bufferDest = BinaryTool.toBytes(diskMessage);//这里还需要将光标移动到最初这个消息的位置,因为 read 操作也会挪动光标randomAccessFile.seek(message.getOffsetBeg());randomAccessFile.write(bufferDest);// 通过上述折腾,对于文件来说,只有一个字节发生改变了而已}//更新统计文件,消息无效了,消息个数就需要 -1Stat stat = readStat(queue.getName());if(stat.validCount > 0) {stat.validCount -= 1;}writeStat(queue.getName(), stat);}}

Ps:此处这个参数中的 message 对象,必须得包含有效的 offsetBeg 和 offsetEnd

1.2.4、获取队列文件中所有有效消息

读取文件中有效的(isValid = 1)消息内容加载到内存中(此方法准备在程序启动的时候进行调用,因此也不需要加锁)

Ps:
queueName 这里只用这一个参数就够了,不需要 MSGQueue 对象
使用 LinkedList 主要是为了后续进行头删的操作

    public LinkedList<Message> loadAllMessageFromQueue(String queueName) throws IOException, MqException, ClassNotFoundException {LinkedList<Message> messages = new LinkedList<>();try (InputStream inputStream = new FileInputStream(getQueueDataPath(queueName))) {try (DataInputStream dataInputStream = new DataInputStream(inputStream)) {//记录当前光标位置long currentOffset = 0;while(true) {//1.读取当前消息的长度int messageSize = dataInputStream.readInt();//2.按照长度获取消息内容byte[] buffer = new byte[messageSize];int actualSize = inputStream.read(buffer);//比较理论和实际消息长度if(messageSize != actualSize) {//如果不匹配说明文件出问题了throw new MqException("[MessageFileManager] 文件格式错误! queueName=" + queueName);}//3.把读到的二进制数据反序列化成 Message 对象Message message = (Message) BinaryTool.fromBytes(buffer);//4.判断这个消息是否是无效对象if(message.getIsValid() != 0x1) {//无效消息直接跳过//虽然是无效数据,但是 offset 不要忘记更新currentOffset += (4 + messageSize);continue;}//5.有效数据就加入到链表中,加入前计算一下 offsetBeg 和 offsetEnd//这个位置需要知道当前文件光标的位置,由于当下使用的 DataInputStream 不方便直接获取文件光标位置, 因此需要使用 currentOffset 手动记录一下message.setOffsetBeg(currentOffset + 4);message.setOffsetEnd(currentOffset + 4 + messageSize);currentOffset += (4 + messageSize);//6.最后加入到链表当中messages.add(message);}} catch (EOFException e) {//这个 catch 并非真的用来处理 ”异常“ ,而是 ”正常“ 业务逻辑,这是为了当消息读完了能得到一个反馈(有点顺水推舟的感觉)//因为,当消息读取到文件末尾,readInt 就会引发异常(EOF异常)System.out.println("[MessageFileManager] 恢复 Message 数据完成");}}return messages;}

1.2.5、GC 机制

这里我们使用 复制算法 对消息数据文件中的垃圾进行回收.

具体的,我们直接遍历原有的消息数据文件,把所有的有效数据拷贝到一个新的文件中,再把之前整个旧的文件都删除,然后将新文件的名字改为旧文件的名字.

什么时候触发一次 GC ?

复制算法比较合适的前提是,当前空间里,有效的数据不多,大部分是无效的数据(减少搬运数据的开销)

因此这里我们约定:当总的消息数目超过 2000 ,并且有效消息的数目低于总消息数目的 50%,就触发一次 GC (避免 GC 太频繁,比如一共 4 个消息,其中 2 个消息无效了,就触发 GC).

Ps:这里的两个数字都是自定义的,关注一定是 策略、思想、方法 ,而不是具体的数字.

    /*** 检查是否要针对队列的消息数据文件进行 GC* @param queueName* @return*/public boolean checkGC(String queueName) {Stat stat = readStat(queueName);if(stat.totalCount > 2000 && (double)stat.validCount / (double)stat.totalCount < 0.5) {return true;}return false;}/*** 获取新文件* @param queueName* @return*/public String getQueueDataNewPath(String queueName) {return getQueueDir(queueName) + "/queue_data_new.txt";}/*** 执行真正的 gc 操作* 使用复制算法完成* 创建一个新的文件,名字叫做 queue_data_new.txt* 把之前消息数据文件中的有效消息都读出来,写道新的文件中* 删除旧的文件,再把新的文件改名回 queue_data.txt* 同时要记得更新消息统计文件* @param queue*/public void gc(MSGQueue queue) throws MqException, IOException, ClassNotFoundException {//gc 意味着 "大洗牌" ,这个过程中其他线程不得干预synchronized(queue) {//由于 gc 操作可能回比较耗时,此处统计一下执行耗时的时间long gcBeg = System.currentTimeMillis();//1.创建一个新文件File queueDataNewFile = new File(getQueueDataNewPath(queue.getName()));if(queueDataNewFile.exists()) {//正常情况下,这个文件是不存在的,如果存在就是以外,说明上次 gc 了一半,中途发生了以外throw new MqException("[MessageFileManager] gc 时发现该队列的 queue_data_new 已经存在! " +"queueName=" + queue.getName());}boolean ok = queueDataNewFile.createNewFile();if(!ok) {throw new MqException("[MessageFileManager] 创建文件失败! queueDataNewFile=" +queueDataNewFile.getName());}//2.从旧文件中读出所有的有效消息LinkedList<Message> messages = loadAllMessageFromQueue(queue.getName());//3.把有效消息写入新的文件try(OutputStream outputStream = new FileOutputStream(queueDataNewFile)) {try(DataOutputStream dataOutputStream = new DataOutputStream(outputStream)) {for(Message message : messages) {byte[] buffer = BinaryTool.toBytes(message);//先写消息长度dataOutputStream.writeInt(buffer.length);//再写消息内容dataOutputStream.write(buffer);}}}//4.删除旧文件File queueDataOldFile = new File(getQueueDataPath(queue.getName()));ok = queueDataOldFile.delete();if(!ok) {throw new MqException("[MessageFileManager] 删除旧的文件失败! queueDataOldFile=" + queueDataOldFile.getName());}//把 queue_data_new.txt 重命名成 queue_data.txtok = queueDataNewFile.renameTo(queueDataOldFile);if(!ok) {throw new MqException("[MessageFileManager] 文件重命名失败! queueDataNewFile=" + queueDataNewFile.getAbsolutePath() +", queueDataOldFile=" + queueDataOldFile.getAbsolutePath());}//5.跟新统计文件Stat stat = readStat(queue.getName());stat.totalCount = messages.size();stat.validCount = messages.size();writeStat(queue.getName(), stat);long gcEnd = System.currentTimeMillis();System.out.println("[MessageFileManager] gc 执行完毕!queueName=" +queue.getName() + "time=" + (gcEnd + gcBeg) + "ms");}}

1.2.6、GC 拓展

当某个队列中,消息特别多,并且很多都是有效的消息,就会导致后续对这个文件操作的成本上升很多,例如文件大小是 10G,此时如果触发一次 GC ,整体的耗时就会非常高了.

对于 RabbitMQ 来说,解决方案就是把一个大的文件拆分成若干个小文件.

  1. 文件拆分:当单个文件长度到达一定阈值以后,就会拆分成两个文件.(拆着拆着,就成了很多文件).
  2. 文件合并:每个单独的文件都会进行 GC ,如果 GC 之后发现文件变小了很多,就可能会和其他相邻的文件合并.

具体实现思路:

  1. 需要专门的数据结构,来存储当前队列中有多少个数据文件,每个文件大小是多少,消息数目是多少,无效消息是多少.
  2. 设计策略,什么时候触发消息拆分,什么时候触发文件合并.

Ps:这里可以先不给出具体实现,需要的可以私信我(前提是备注微信号).

二、统一硬盘操作


使用这个类来管理所有硬盘上的数据

  1. 数据库:交换机、绑定、队列
  2. 数据文件:消息

上层逻辑需要操作硬盘,统一通过这个类来操作(上层代码不关心当前数据是存储再数据库还是文件中的),提高了代码的内聚,可维护性.

public class DiskDataCenter {//这个实例用来管理数据库中的数据private DataBaseManager dataBaseManager = new DataBaseManager();//这个实例用来管理数据文件中的数据private MessageFileManager messageFileManager = new MessageFileManager();/*** 针对上面两个实例进行初始化*/public void init() {dataBaseManager.init();// messageFileManager 中 init 是一个空方法,只是先列在这里,一旦后续需要扩展,就在这里进行初始化即可messageFileManager.init();}//封装交换机操作public void insertExchange(Exchange exchange) {dataBaseManager.insertExchange(exchange);}public void deleteExchange(String exchangeName) {dataBaseManager.deleteExchange(exchangeName);}public List<Exchange> selectAllExchanges() {return dataBaseManager.selectAllExchanges();}//封装队列操作public void insertQueue(MSGQueue queue) throws IOException {dataBaseManager.insertQueue(queue);//创建队列的同时,不仅需要把队列写入到数据库中,还需要创建出对应的目录和文件messageFileManager.createQueueFiles(queue.getName());}public void deleteQueue(String queueName) throws IOException {dataBaseManager.deleteQueue(queueName);//删除队列的同时,不仅需要把队列从数据库中删除,还需要把对应的文件和目录删除messageFileManager.destroyQueueFiles(queueName);}public List<MSGQueue> selectAllQueue() {return dataBaseManager.selectAllQueues();}//封装绑定操作public void insertBinding(Binding binding) {dataBaseManager.insertBinding(binding);}public void deleteBinding(Binding binding) {dataBaseManager.deleteBinding(binding);}public List<Binding> selectAllBindings() {return dataBaseManager.selectAllBindings();}//封装消息操作public void sendMessage(MSGQueue queue, Message message) throws IOException, MqException {messageFileManager.sendMessage(queue, message);}public void deleteMessage(MSGQueue queue, Message message) throws IOException, ClassNotFoundException, MqException {messageFileManager.deleteMessage(queue, message);//这里删除消息以后还需要看以下文件中是否有太多的无效文件需要进行清除if(messageFileManager.checkGC(queue.getName())) {messageFileManager.gc(queue);}}public List<Message> selectAllMessagesFromQueue(String queueName) throws IOException, MqException, ClassNotFoundException {return messageFileManager.loadAllMessageFromQueue(queueName);}}

Ps:这里对队列和消息的封装都是具有一定的逻辑的!

队列:

  1. 创建队列的同时,不仅需要把队列写入到数据库中,还需要创建出对应的目录和文件
  2. 删除队列的同时,不仅需要把队列从数据库中删除,还需要把对应的文件和目录删除

 消息:

  1. 删除消息以后还需要看以下文件中是否有太多的无效文件需要进行清除(GC)

相关文章:

根据源码,模拟实现 RabbitMQ - 实现消息持久化,统一硬盘操作(3)

目录 一、实现消息持久化 1.1、消息的存储设定 1.1.1、存储方式 1.1.2、存储格式约定 1.1.3、queue_data.txt 文件内容 1.1.4、queue_stat.txt 文件内容 1.2、实现 MessageFileManager 类 1.2.1、设计目录结构和文件格式 1.2.2、实现消息的写入 1.2.3、实现消息的删除…...

找到所有数组中消失的数(C语言详解)

题目&#xff1a;找到所有数组中消失的数 题目详情&#xff1a; 给你一个含 n 个整数的数组 nums &#xff0c;其中 nums[i] 在区间 [1,n] 内。请你找出所以在 [1,n] 范围内但没有出现在 nums 中的数字&#xff0c;并以数组的形式返回结果。 示例1&#xff1a; 输入&#xf…...

计算机毕设项目之基于django+mysql的疫情实时监控大屏系统(前后全分离)

系统阐述的是一款新冠肺炎疫情实时监控系统的设计与实现&#xff0c;对于Python、B/S结构、MySql进行了较为深入的学习与应用。主要针对系统的设计&#xff0c;描述&#xff0c;实现和分析与测试方面来表明开发的过程。开发中使用了 django框架和MySql数据库技术搭建系统的整体…...

Unity UI内存泄漏优化

项目一运行&#xff0c;占用的内存越来越多&#xff0c;不会释放&#xff0c;导致GC越来越频繁&#xff0c;越来越慢&#xff0c;这些都是为什么呢&#xff0c;今天从UI方面谈起。 首先让我们来聊聊什么是内存泄漏呢&#xff1f; 一般来讲内存泄漏就是指我们的应用向内存申请…...

学习笔记:Opencv实现图像特征提取算法SIFT

2023.8.19 为了在暑假内实现深度学习的进阶学习&#xff0c;特意学习一下传统算法&#xff0c;分享学习心得&#xff0c;记录学习日常 SIFT的百科&#xff1a; SIFT Scale Invariant Feature Transform, 尺度不变特征转换 全网最详细SIFT算法原理实现_ssift算法_Tc.小浩的博客…...

【golang】接口类型(interface)使用和原理

接口类型的类型字面量与结构体类型的看起来有些相似&#xff0c;它们都用花括号包裹一些核心信息。只不过&#xff0c;结构体类型包裹的是它的字段声明&#xff0c;而接口类型包裹的是它的方法定义。 接口类型声明中的这些方法所代表的就是该接口的方法集合。一个接口的方法集…...

【Linux操作系统】Linux系统编程中的共享存储映射(mmap)

在Linux系统编程中&#xff0c;进程之间的通信是一项重要的任务。共享存储映射&#xff08;mmap&#xff09;是一种高效的进程通信方式&#xff0c;它允许多个进程共享同一个内存区域&#xff0c;从而实现数据的共享和通信。本文将介绍共享存储映射的概念、原理、使用方法和注意…...

2235.两整数相加:19种语言解法(力扣全解法)

【LetMeFly】2235.两整数相加&#xff1a;19种语言解法&#xff08;力扣全解法&#xff09; 力扣题目链接&#xff1a;https://leetcode.cn/problems/add-two-integers/ 给你两个整数 num1 和 num2&#xff0c;返回这两个整数的和。 示例 1&#xff1a; 输入&#xff1a;num…...

中国剩余定理及扩展

目录 中国剩余定理解释 中国剩余定理扩展——求解模数不互质情况下的线性方程组&#xff1a; 代码实现&#xff1a; 互质&#xff1a; 非互质&#xff1a; 中国剩余定理解释 在《孙子算经》中有这样一个问题&#xff1a;“今有物不知其数&#xff0c;三三数之剩二&#x…...

数据在内存中的存储(deeper)

数据在内存中的存储&#xff08;deeper&#xff09; 一.数据类型的详细介绍二.整形在内存中的存储三.浮点型在内存中的存储 一.数据类型的详细介绍 类型的意义&#xff1a; 使用这个类型开辟内存空间的大小&#xff08;大小决定了使用范围&#xff09;如何看待内存空间的视角…...

算法修炼Day52|● 300.最长递增子序列 ● 674. 最长连续递增序列 ● 718. 最长重复子数组

LeetCode:300.最长递增子序列 300. 最长递增子序列 - 力扣&#xff08;LeetCode&#xff09; 1.思路 dp[i]的状态表示以nums[i]为结尾的最长递增子序列的个数。 dp[i]有很多个&#xff0c;选择其中最大的dp[i]Math.max(dp[j]1,dp[i]) 2.代码实现 1class Solution {2 pub…...

使用 HTML、CSS 和 JavaScript 创建实时 Web 编辑器

使用 HTML、CSS 和 JavaScript 创建实时 Web 编辑器 在本文中&#xff0c;我们将创建一个实时网页编辑器。这是一个 Web 应用程序&#xff0c;允许我们在网页上编写 HTML、CSS 和 JavaScript 代码并实时查看结果。这是学习 Web 开发和测试代码片段的绝佳工具。我们将使用ifram…...

百望云联合华为发布票财税链一体化数智解决方案 赋能企业数字化升级

随着数据跃升为数字经济关键生产要素&#xff0c;数据安全成为整个数字化建设的重中之重。为更好地帮助企业发展&#xff0c;中央及全国和地方政府相继出台了多部与数据相关的政策法规&#xff0c;鼓励各领域服务商提供具有自主创新的软件产品与服务&#xff0c;帮助企业在合规…...

实现两个栈模拟队列

实现两个栈模拟队列 思路&#xff1a;可以想象一下左手和右手&#xff0c;两个栈&#xff1a;stack1&#xff08;数据所在的栈&#xff09; &#xff0c;stack2&#xff08;临时存放&#xff09;。 入队&#xff1a;需要将入队 num 加在 stack1 的栈顶即可&#xff1b; 出队&am…...

无涯教程-TensorFlow - 单词嵌入

Word embedding是从离散对象(如单词)映射到向量和实数的概念&#xff0c;可将离散的输入对象有效地转换为有用的向量。 Word embedding的输入如下所示: blue: (0.01359, 0.00075997, 0.24608, ..., -0.2524, 1.0048, 0.06259) blues: (0.01396, 0.11887, -0.48963, ..., 0.03…...

Facebook AI mBART:巴别塔的硅解

2018年&#xff0c;谷歌发布了BERT&#xff08;来自transformers的双向编码器表示&#xff09;&#xff0c;这是一种预训练的语言模型&#xff0c;在一系列自然语言处理&#xff08;NLP&#xff09;任务中对SOTA结果进行评分&#xff0c;并彻底改变了研究领域。类似的基于变压器…...

BDA初级分析——SQL清洗和整理数据

一、数据处理 数据处理之类型转换 字符格式与数值格式存储的数据&#xff0c;同样是进行大小排序&#xff0c; 会有什么区别&#xff1f; 以rev为例&#xff0c;看看字符格式与数值格式存储时&#xff0c;排序会有什么区别&#xff1f; 用cast as转换为字符后进行排序 SEL…...

汽车后视镜反射率测定仪

后视镜是驾驶员坐在驾驶室座位上直接获取汽车后方、侧方和下方等外部信息的工具。它起着“第三只眼睛”的作用。后视镜按安装位置划分通常分为车外后视镜、监视镜和内后视镜。外后视镜观察汽车后侧方监视镜观察汽车前下方内后视镜观察汽车后方及车内情况。用途不一样镜面结构也…...

Redis学习笔记

redis相关内容 默认端口6379 默认16个数据库&#xff0c;初始默认使用0号库 使用select 切换数据库 统一密码管理&#xff0c;所有库密码相同 dbsize&#xff1a;查看当前库key的数量 flushdb&#xff1a;清空当前库 flushall&#xff1a;清空全部库 redis是单线程 多路…...

韩顺平Linux 四十四--

四十四、rwx权限 权限的基本介绍 输入指令 ls -l 显示的内容如下 -rwxrw-r-- 1 root 1213 Feb 2 09:39 abc0-9位说明 第0位确定文件类型&#xff08;d , - , l , c , b) l 是链接&#xff0c;相当于 windows 的快捷方式- 代表是文件是普通文件d 是目录&#xff0c;相…...

【支付宝小程序】分包优化教程

&#x1f996;我是Sam9029&#xff0c;一个前端 Sam9029的CSDN博客主页:Sam9029的博客_CSDN博客-JS学习,CSS学习,Vue-2领域博主 &#x1f431;‍&#x1f409;&#x1f431;‍&#x1f409;恭喜你&#xff0c;若此文你认为写的不错&#xff0c;不要吝啬你的赞扬&#xff0c;求收…...

语言基础2 矩阵和数组

语言基础2 矩阵和数组 矩阵和数组是matlab中信息和数据的基本表示形式 可以创建常用的数组和网格 合并现有的数组 操作数组的形状和内容 以及使用索引访问数组元素 用到的函数列表如下 一 创建 串联和扩展矩阵 矩阵时按行和列排列的数据元素的二维数据元素的二维矩…...

springMVC中过滤器抛出异常,自定义异常捕获

在过滤器中引入org.springframework.web.servlet.HandlerExceptionResolver AutowiredQualifier("handlerExceptionResolver")private HandlerExceptionResolver resolver; // doFilter中处理if (条件1) {if (条件2) {resolver.resolveException(request, response, …...

图像检索技术研究:深度度量与深度散列在相似性学习中的应用比较与实践 - 使用Python与Jupyter环境

引言 在计算机视觉领域&#xff0c;图像检索是一个长期存在并持续受到研究者关注的重要话题。随着大数据时代的到来&#xff0c;如何高效、准确地从海量数据中检索到相似的图像成为一个巨大的挑战。传统的检索方法在大数据环境下表现不佳&#xff0c;而深度学习技术的崛起为图…...

CSS加载失败的6个原因

有很多刚刚接触 CSS 的新手有时会遇到 CSS 加载失败这个问题&#xff0c;但测试时&#xff0c;网页上没有显示该样式的问题&#xff0c;这就说明 CSS 加载失败了。出现这种状况一般是因为的 CSS 路径书写错&#xff0c;或者是在浏览器中禁止掉了 CSS 的加载&#xff0c;可以重新…...

react之路由的安装与使用

一、路由安装 路由官网2021.11月初&#xff0c;react-router 更新到 v6 版本。使用最广泛的 v5 版本的使用 npm i react-router-dom5.3.0二、路由使用 2.1 路由的简单使用 第一步 在根目录下 创建 views 文件夹 ,用于放置路由页面 films.js示例代码 export default functio…...

基于RoCE的应用程序的MTU注意事项

目录 基于RoCE的应用程序的MTU注意事项 探测网络中的MTU设置 概要 原文 MTU测试结果 DOC: CentOS安装tshark抓包工具 基于RoCE的应用程序的MTU注意事项 原文&#xff1a;https://support.mellanox.com/s/article/MLNX2-117-1682kn InfiniBand协议最大传输单元&#xff…...

springboot集成Graphql相关问题汇总

1、idea在debug运行时出现java.lang.NoClassDefFoundError:kotlin/collections/AbstractMutableMap 解决&#xff1a;禁用idea dubugger中kotlin coroutine agent 见&#xff1a;https://stackoverflow.com/questions/70796177/after-the-spring-boot-source-code-is-compile…...

Angular16的路由守卫基础使用

Angular16的路由守卫基础使用 使用ng generate guard /guard/login命令生成guard文件因新版Angular取消了CanActivate的使用&#xff0c;改用CanActivateFn&#xff0c;因此使用router跳转需要通过inject的方式导入。 import { inject } from angular/core; import { CanActi…...

leetcode228. 汇总区间

题目 给定一个 无重复元素 的 有序 整数数组 nums 。 返回 恰好覆盖数组中所有数字 的 最小有序 区间范围列表 。也就是说&#xff0c;nums 的每个元素都恰好被某个区间范围所覆盖&#xff0c;并且不存在属于某个范围但不属于 nums 的数字 x 。 列表中的每个区间范围 [a,b]…...

删除有序链表中重复的元素-II(链表)

乌&#xff01;蒙&#xff01;山&#xff01;连&#xff01;着&#xff01;山&#xff01;外&#xff01;山&#xff01; 题目&#xff1a; 思路&#xff1a; 双指针&#xff0c;slow和fast&#xff0c;并且增加标记flag初始为1。 如果slow指向节点值等于fast指向节点值&…...

element单独检验form表单中的一项

<el-form-item prop"limitDays" style"margin-left: 5px;"><el-input v-model"ruleForm.limitDays" placeholder"天数" style"width: 100px;" /> </el-form-item> <el-form-item prop"limitCount…...

Webpack node、output.jsonpFunction 配置详解

Webpack node、output.jsonpFunction 配置详解 最近尝试给一些用到 webpack 的项目升级到最新 webpack5 版本&#xff0c;其中遇到了一些问题&#xff0c;我挑了两个比较典型的问题&#xff0c;其中主要涉及到了 webpack 的 node 属性跟 output.jsonpFunction &#xff08;web…...

要跟静音开关说再见了!iPhone15新变革,Action按钮引领方向

有很多传言称iPhone 15 Pro会有很多变化&#xff0c;但其中一个变化可能意味着iPhone体验从第一天起就有的一项功能的终结。我说的是静音开关&#xff0c;它可以让你轻松地打开或关闭iPhone的铃声。 根据越来越多的传言&#xff0c;iPhone 15 Pro和iPhone 15 Pro Max将拆除静音…...

论文笔记 Graph Attention Networks

2018 ICLR 1 intro 1.1. GCN的不足 无法完成inductive任务 inductive任务是指&#xff1a; 训练阶段与测试阶段需要处理的graph不同。通常是训练阶段只是在子图上进行&#xff0c;测试阶段需要处理未知的顶点。GGN 的参数依赖于邻接矩阵A/拉普拉斯矩阵L&#xff0c;所以换了…...

看上去就很像的agree和degree有什么联系

“Agree”&#xff08;同意&#xff09;和 “degree”&#xff08;程度&#xff09;这两个词在语义上没有直接的联系&#xff0c;它们代表不同的概念。 “Agree” 意味着在意见、观点或立场上达成共识或一致。它表示同意或同意某人或某事。 例如&#xff1a; “We all agree…...

2023前端面试题第二弹(真实,一般人我还不给看)

为什么要初始化css&#xff1f; 避免浏览器差异&#xff0c;解决兼容问题 网格布局 display: grid; grid-template-columns: 1fr 1fr 1fr less的优点 可以兼容&#xff0c;可以嵌套&#xff0c;循环&#xff0c;运算&#xff0c;定义变量和继承样式&#xff08;extend&#xff…...

零基础如何学习 Web 安全,如何让普通人快速入门网络安全?

前言 网络安全现在是朝阳行业&#xff0c;缺口是很大。不过网络安全行业就是需要技术很多的人达不到企业要求才导致人才缺口大 【一一帮助安全学习&#xff08;网络安全面试题学习路线视频教程工具&#xff09;一一】 初级的现在有很多的运维人员转网络安全&#xff0c;初级…...

安全学习DAY18_信息打点-APP资产搜集

信息打点-APP资产&静态提取&动态抓包&动态调试 文章目录 信息打点-APP资产&静态提取&动态抓包&动态调试本节知识&思维导图本节使用到的链接&工具 如何获取目标APP从名称中获取APP从URL获取APP APP搜集资产信息APP提取信息分类信息提取方式信息…...

react 矩形波浪

"矩形波浪"&#xff08;Square Wave&#xff09;在信号处理和波形生成中是一种特殊类型的波形&#xff0c;通常由两个不同的值交替组成&#xff0c;一个是高电平&#xff0c;另一个是低电平&#xff0c;形成类似方波的波形。在 React 中创建一个矩形波浪的效果可以通…...

【GitHub】Pycharm本地项目打包上传到Github仓库的操作步骤

文章目录 1、Pycharm端的设置操作2、Github端的设置操作3、Pycharm上配置Github4、Git本地项目至GitHub仓库5、前往Github中查看确认6、常见报错 1、Pycharm端的设置操作 通过CtrlAltS快捷组合键的方式&#xff0c;打开设置&#xff0c;导航到版本控制一栏中的Git&#xff0c;…...

计算机网络基础

前言 在你立足处深挖下去,就会有泉水涌出!别管蒙昧者们叫嚷:“下边永远是地狱!” 博客主页&#xff1a;KC老衲爱尼姑的博客主页 博主的github&#xff0c;平常所写代码皆在于此 共勉&#xff1a;talk is cheap, show me the code 作者是爪哇岛的新手&#xff0c;水平很有限&…...

【图像分类】基于LIME的CNN 图像分类研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…...

回归预测 | MATLAB实现TSO-SVM金枪鱼群算法优化支持向量机多输入单输出回归预测(多指标,多图)

回归预测 | MATLAB实现TSO-SVM金枪鱼群算法优化支持向量机多输入单输出回归预测&#xff08;多指标&#xff0c;多图&#xff09; 目录 回归预测 | MATLAB实现TSO-SVM金枪鱼群算法优化支持向量机多输入单输出回归预测&#xff08;多指标&#xff0c;多图&#xff09;效果一览基…...

Pixar、Adobe 和苹果等成立 OpenUSD 联盟推行 3D 内容开放标准

导读Pixar、Adobe、Apple、Autodesk 与 NVIDIA 联手 Linux 基金会旗下的联合开发基金会&#xff08;JDF&#xff09;宣布建立 OpenUSD 联盟&#xff08;AOUSD&#xff09;以推行 Pixar 创建的通用场景描述技术的标准化、开发、进化和发展。 联盟寻求通过推进开放式通用场景描述…...

ansible剧本之role角色模块

role角色 一&#xff1a;Roles 模块1.roles 的目录结构&#xff1a;2.roles 内各目录含义解释3.在一个 playbook 中使用 roles 的步骤&#xff1a;&#xff08;1&#xff09;创建以 roles 命名的目录&#xff08;2&#xff09;创建全局变量目录&#xff08;可选&#xff09;&am…...

网络安全领域的常见攻击方式及防御手段

目录 重放攻击&#xff08;Replay Attack&#xff09;防御手段 SQL 注入&#xff08;SQL Injection&#xff09;防御手段 跨站脚本攻击&#xff08;Cross-Site Scripting&#xff0c;XSS&#xff09;防御手段 跨站请求伪造&#xff08;Cross-Site Request Forgery&#xff0c;C…...

Python应用工具-Jupyter Notebook

工具简介 Jupyter Notebook是 基于 网页的用于交互计算的 应用程序&#xff0c;以网页的形式打开&#xff0c;可以在网页页面中直接编写代码和运行代码&#xff0c;代码的运行结果也会直接在代码块下 显示&#xff0c;文档是保存为后缀名为 . ipynb 的 JSON 格式文件。 操作指令…...

音视频 FFmpeg如何查询命令帮助文档

FFmpeg如何查询命令帮助文档 一、ffmpeg/ffplay/ffprobe区别二、ffmpeg命令查看帮助文档三、ffplay命令查看帮助文档四、ffprobe命令查看帮助文档注意 一、ffmpeg/ffplay/ffprobe区别 ffmpeg:超快音视频编码器ffplay:简单媒体播放器ffprobe:简单多媒体流分析器 二、ffmpeg命令…...

回归预测 | MATLAB实现CSO-SVM布谷鸟优化算法优化支持向量机多输入单输出回归预测(多指标,多图)

回归预测 | MATLAB实现CSO-SVM布谷鸟优化算法优化支持向量机多输入单输出回归预测&#xff08;多指标&#xff0c;多图&#xff09; 目录 回归预测 | MATLAB实现CSO-SVM布谷鸟优化算法优化支持向量机多输入单输出回归预测&#xff08;多指标&#xff0c;多图&#xff09;效果一…...