🍃前言
本次开发目标实现内存加载
我们在硬盘与内存中都存入了我们的消息,但是呢,当程序重启后,内存中的消息就会丢失,这时候我们就需要将硬盘中的数据写入内存中
🌳实现思路
首先我们定义两个变量
一个是使用一个LinkedList的数组接收硬盘里面存储的数据。
除此之外我们还定义一个变量currentOffset用来记录我们读写文件的光标
接下来我们开始进行实现,分为5步实现
🚩读取消息长度
根据我们定义存入硬盘的消息结构
先读取最前面的四个字节,这四个字节里面的内容代表的是整个消息的长度
🚩读取相应长度的消息
构造相应长度的字节数组,进行读取
并且进行判断,实际读取消息的长度是否符合
若不符合,我们需要抛出我们自定义的异常
🚩进行反序列化
🚩判定是否有效
我们需要对反序列化后的对象进行判断
若该Message对象已近无效,那么我们就可以直接跳过了
需要注意的是,这时候我们也需要将我们的currentOffset变量进行更新
🚩加入有效消息
若为有效数据, 则需要把这个 Message 对象加入到链表中. 加入之前还需要填写 offsetBeg 和 offsetEnd
进行计算 offset 的时候, 需要知道当前文件光标的位置的.
而我们的currentOffset变量正记录着我们当前的位置
🚩收尾工作
由于我们不知道消息有多长,所以我们将上述操作放入一个while(true)的循环里进行读取
但是呢,我们应该怎么判断是否读取结束呢?
其实我们使用读取四个字节的方法readInt()
当后面没有数据时,它便会抛出异常,这里我们利用这个抛出的异常,我们在最后进行捕获,但是呢。
这个抛出的“异常”其实是一个正常的数据
🚩代码实现
// 使用这个方法, 从文件中, 读取出所有的消息内容, 加载到内存中(具体来说是放到一个链表里) // 这个方法, 准备在程序启动的时候, 进行调用. // 这里使用一个 LinkedList, 主要目的是为了后续进行头删操作. // 这个方法的参数, 只是一个 queueName 而不是 MSGQueue 对象. 因为这个方法不需要加锁, 只使用 queueName 就够了. // 由于该方法是在程序启动时调用, 此时服务器还不能处理请求呢~~ 不涉及多线程操作文件. 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. 读取当前消息的长度, 这里的 readInt 可能会读到文件的末尾(EOF) // readInt 方法, 读到文件末尾, 会抛出 EOFException 异常. 这一点和之前的很多流对象不太一样. int messageSize = dataInputStream.readInt(); // 2. 按照这个长度, 读取消息内容 byte[] buffer = new byte[messageSize]; int actualSize = dataInputStream.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. 有效数据, 则需要把这个 Message 对象加入到链表中. 加入之前还需要填写 offsetBeg 和 offsetEnd // 进行计算 offset 的时候, 需要知道当前文件光标的位置的. 由于当下使用的 DataInputStream 并不方便直接获取到文件光标位置 // 因此就需要手动计算下文件光标. message.setOffsetBeg(currentOffset + 4); message.setOffsetEnd(currentOffset + 4 + messageSize); currentOffset += (4 + messageSize); messages.add(message); } } catch (EOFException e) { // 这个 catch 并非真是处理 "异常", 而是处理 "正常" 的业务逻辑. 文件读到末尾, 会被 readInt 抛出该异常. // 这个 catch 语句中也不需要做啥特殊的事情 System.out.println("[MessageFileManager] 恢复 Message 数据完成!"); } } return messages; }
⭕总结
关于《【消息队列开发】 实现内存加载》就讲解到这儿,感谢大家的支持,欢迎各位留言交流以及批评指正,如果文章对您有帮助或者觉得作者写的还不错可以点一下关注,点赞,收藏支持一下