415. Java 文件操作基础 - 精准读取压缩诗集:从二进制文件中高效提取指定十四行诗

415. Java 文件操作基础 - 精准读取压缩诗集:从二进制文件中高效提取指定十四行诗

📚 读取单个 Sonnet

上节课我们已经把所有 154 首 Sonnet 压缩存储在一个二进制文件里。 这节课我们来看看:如何只读取其中的一首(比如第 75 首)


🎯 核心思路

读取逻辑其实比写入简单:

  1. 读取文件头:获取总数、offset 和 length。
  2. 定位目标 Sonnet:根据 offset 跳过前面的字节。
  3. 读取压缩字节数组
  4. 解压缩 + 解码成文本

✅ 示例代码(读取文件头)

java 复制代码
Path path = Paths.get("files/sonnets.bin");

try (InputStream file = Files.newInputStream(path);
     BufferedInputStream bis = new BufferedInputStream(file);
     DataInputStream dis = new DataInputStream(file)) {

    int numberOfSonnets = dis.readInt();
    System.out.println("numberOfSonnets = " + numberOfSonnets);

    List<Integer> offsets = new ArrayList<>();
    List<Integer> lengths = new ArrayList<>();
    for (int i = 0; i < numberOfSonnets; i++) {
        offsets.add(dis.readInt());
        lengths.add(dis.readInt());
    }

    // 此时已经拿到 offsets 和 lengths
} catch (IOException e) {
    e.printStackTrace();
}

👉 运行后,你会得到类似输出:

java 复制代码
numberOfSonnets = 154

并且 offsetslengths 数组里保存了每首 Sonnet 的位置信息。


🔍 跳过和读取字节的工具方法

⚠️ 注意:

  • skip(n)read(n) 在流上操作时,可能不会一次完成任务(尤其是大文件)。
  • 所以我们要写工具方法,确保真的跳过/读取了指定字节数。

跳过固定字节数

java 复制代码
static long skip(BufferedInputStream bis, int offset) throws IOException {
    long skipped = 0L;
    while (skipped < offset) {
        skipped += bis.skip(offset - skipped);
    }
    return skipped;
}

读取固定字节数

java 复制代码
static byte[] readBytes(BufferedInputStream bis, int length) throws IOException {
    byte[] bytes = new byte[length];
    int copied = 0;

    while (copied < length) {
        int read = bis.read(bytes, copied, length - copied);
        if (read == -1) throw new EOFException("Unexpected end of file");
        copied += read;
    }
    return bytes;
}

💡 我稍微优化了原代码:不再额外使用中间 buffer,直接往目标数组里读,逻辑更直观。


✅ 示例代码(读取第 75 首 Sonnet)

java 复制代码
int sonnetIndex = 75; // 要读取的 Sonnet
int offset = offsets.get(sonnetIndex - 1);
int length = lengths.get(sonnetIndex - 1);

skip(bis, offset);                // 定位到 offset
byte[] bytes = readBytes(bis, length); // 读取压缩字节数组

try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
     GZIPInputStream gzis = new GZIPInputStream(bais);
     InputStreamReader isr = new InputStreamReader(gzis);
     BufferedReader reader = new BufferedReader(isr)) {

    List<String> sonnetLines = reader.lines().toList();
    sonnetLines.forEach(System.out::println);
}

🚀 输出结果

运行后,你会在控制台看到 第 75 首 Sonnet(解压缩后的文本):

java 复制代码
  So are you to my thoughts as food to life,
  Or as sweet-season'd showers are to the ground;
  And for the peace of you I hold such strife
  As 'twixt a miser and his wealth is found.
  Now proud as an enjoyer, and anon
  Doubting the filching age will steal his treasure;
  Now counting best to be with you alone,
  Then better'd that the world may see my pleasure:
  Sometime all full with feasting on your sight,
  And by and by clean starved for a look;
  Possessing or pursuing no delight,
  Save what is had, or must from you be took.
    Thus do I pine and surfeit day by day,
    Or gluttoning on all, or all away.

📌 总结

  1. 读取文件头 → 拿到目录表(offset & length)
  2. 用 skip() 精确跳过字节
  3. 用 readBytes() 保证完整读取
  4. 解压缩 + 转换为字符串行
  5. 最后打印目标 Sonnet

这就是从压缩二进制文件里 精确定位并读取单个 Sonnet 的完整流程。


要不要我帮你把 读取单个 Sonnet 的逻辑,封装成一个 SonnetFileReader 工具类?这样学员就可以像调用 API 一样使用,比如:

java 复制代码
SonnetFileReader sfr = new SonnetFileReader("files/sonnets.bin");
List<String> sonnet75 = sfr.readSonnet(75);
相关推荐
mldong25 分钟前
你的 Vue3 项目也能有钉钉同款审批流设计器:npm 装包,10 分钟画出第一条审批流
前端·vue.js
2分钟速写快排1 小时前
什么是 RAG?如何用 RAG 实现一个用户记忆?
前端·后端·ai编程
面向Google编程1 小时前
Apache Iceberg Variant 类型:v3 半结构化数据不再「二选一」
大数据·后端
passerby60612 小时前
如何自己造一个时间处理库
前端·javascript·github
jason成都2 小时前
Spring WebFlux 适配达梦新方案|dm‑r2dbc:Netty 异步传输的实验性 R2DBC 驱动
java·后端·spring
走到天涯海角3 小时前
react里面的长列表渲染优化
前端·react.js·前端框架
小羊没烦恼!3 小时前
Hello Web API系列教程——Web API与国际化
java·服务器·前端·javascript·php
北岛贰3 小时前
迷茫焦虑期,我做了一个带支付带官网的 AI 聊天虚拟恋人 App
前端·人工智能·后端
mayaairi5 小时前
Vue2 组件通讯(三):全局事件总线、PubSub、插槽与组件实例属性
前端·javascript·vue.js
kyriewen5 小时前
面试官问我:AI 都能写代码了,前端凭什么还值 25K
前端·javascript·人工智能