415. Java 文件操作基礎 - 精準讀取壓縮詩集:從二進制文件中高效提取指定十四行詩
?? 讀取單個 Sonnet
上節(jié)課我們已經把所有 154 首 Sonnet 壓縮存儲在一個二進制文件里。
這節(jié)課我們來看看:如何只讀取其中的一首(比如第 75 首)。
?? 核心思路
讀取邏輯其實比寫入簡單:
- 讀取文件頭:獲取總數、offset 和 length。
- 定位目標 Sonnet:根據 offset 跳過前面的字節(jié)。
- 讀取壓縮字節(jié)數組。
- 解壓縮 + 解碼成文本。
? 示例代碼(讀取文件頭)
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();
}
?? 運行后,你會得到類似輸出:
numberOfSonnets = 154
并且 offsets、lengths 數組里保存了每首 Sonnet 的位置信息。
?? 跳過和讀取字節(jié)的工具方法
?? 注意:
-
skip(n)和read(n)在流上操作時,可能不會一次完成任務(尤其是大文件)。 - 所以我們要寫工具方法,確保真的跳過/讀取了指定字節(jié)數。
跳過固定字節(jié)數
static long skip(BufferedInputStream bis, int offset) throws IOException {
long skipped = 0L;
while (skipped < offset) {
skipped += bis.skip(offset - skipped);
}
return skipped;
}
讀取固定字節(jié)數
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;
}
?? 我稍微優(yōu)化了原代碼:不再額外使用中間 buffer,直接往目標數組里讀,邏輯更直觀。
? 示例代碼(讀取第 75 首 Sonnet)
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); // 讀取壓縮字節(jié)數組
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(解壓縮后的文本):
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.
?? 總結
- 讀取文件頭 → 拿到目錄表(offset & length)
- 用 skip() 精確跳過字節(jié)
- 用 readBytes() 保證完整讀取
- 解壓縮 + 轉換為字符串行
- 最后打印目標 Sonnet
這就是從壓縮二進制文件里 精確定位并讀取單個 Sonnet 的完整流程。
要不要我?guī)湍惆?讀取單個 Sonnet 的邏輯,封裝成一個 SonnetFileReader 工具類?這樣學員就可以像調用 API 一樣使用,比如:
SonnetFileReader sfr = new SonnetFileReader("files/sonnets.bin");
List<String> sonnet75 = sfr.readSonnet(75);