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

并且 offsets、lengths 数组里保存了每首 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);
相关推荐
Csvn8 小时前
并发模式:让渲染学会排队、插队和让路
前端
可乐鸡翅yeah_8 小时前
新手梳理:M3U8 线上问题,哪些是前端锅,哪些是后端锅
前端·ios·音视频·实时音视频·m3u8·音视频在线播放
Flynt8 小时前
Linear 用 1000 个 PR 换掉 styled-components,我写了 200 个按钮,把这笔账复现了一遍
前端·css·preact
波力海苔夹心脆6758 小时前
C# 序列化与反序列化详解:System.Text.Json、Newtonsoft.Json、XmlSerializer 用法、特性选项与安全实践
经验分享·后端·c#·json·.net
JavaGuide9 小时前
NVIDIA 又开源了!这次给 AI Agent 加上权限管控
前端·后端
excel9 小时前
prisma 如何处理数据库竞态
前端·数据库·后端
莪_幻尘10 小时前
Skill 体检:30 个 Skill 全凭感觉?体检器先自曝了 8 个“假 0 分
前端·人工智能·llm
风骏时光牛马10 小时前
AI模型综合能力评测:性能、指令遵循与多场景实测对比
前端
Frag0ut10 小时前
Chrome与Chromium内核浏览器在Windows 11上的新特性全景解析
前端·chrome·windows·web安全·chromium·gemini ai·playready drm