InputStream 核心讀取方法對比
| 方法 | 返回值 | 阻塞行為 | 適用場景 |
| read() | 單字節 int (-1 表示 EOF) | 阻塞直到至少 1 字節可用 | 極小數據、教學 |
| read(byte[]) | 實際讀取字節數 | 阻塞直到至少 1 字節可用 | 通用塊讀取 |
| read(byte[], off, len) | 實際讀取字節數 | 阻塞直到至少 1 字節可用 | 精確控制、高性能 |
| readNBytes(byte[], off, len) (Java 9+) | 實際讀取字節數 | 阻塞直到滿 len 或 EOF | 精確讀取指定長度 |
| readAllBytes() (Java 9+) | byte[] 所有數據 | 阻塞直到 EOF | 小文件一次性讀取 |
| transferTo(OutputStream) (Java 9+) | 傳輸字節數 | 流式傳輸,零拷貝優化 | 流複製、下載 |
致命陷阱:available() 誤用
// ❌ 錯誤用法:網絡流/管道流導致讀取不完整
int count = in.available(); // 可能返回 0 或部分數據
byte[] buf = new byte[count];
in.read(buf); // 只讀到 available() 返回的數據
// ✅ 正確讀取模式:循環讀取直到 -1
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[8192];
int len;
while ((len = in.read(buffer)) != -1) {
baos.write(buffer, 0, len);
}
byte[] data = baos.toByteArray();
available() 正確用途
- 檢查本地文件剩餘字節數 (FileInputStream 實現較準確)
- 非阻塞檢查是否有數據可讀 (配合 Selector/NIO 使用)
- 絕不用於分配緩衝區大小或作為循環終止條件
網絡流讀取最佳實踐
// HTTP 響應體讀取
try (InputStream in = response.getEntity().getContent();
ByteArrayOutputStream out = new ByteArrayOutputStream()) {
byte[] buffer = new byte[16 * 1024]; // 16KB 緩衝區
int len;
while ((len = in.read(buffer)) != -1) {
out.write(buffer, 0, len);
}
String body = out.toString(StandardCharsets.UTF_8);
}
// 帶超時的讀取
Socket socket = new Socket();
socket.connect(new InetSocketAddress(host, port), 5000); // 連接超時 5s
socket.setSoTimeout(30000); // 讀取超時 30s
try (InputStream in = socket.getInputStream()) {
// 讀取邏輯...
}
PPT 文件解析實戰 (Apache POI)
public String extractPptText(File pptFile) throws IOException {
StringBuilder text = new StringBuilder();
try (FileInputStream fis = new FileInputStream(pptFile)) {
// 自動識別 .ppt 或 .pptx
SlideShow ppt = SlideShowFactory.create(fis);
for (Slide slide : ppt.getSlides()) {
// 獲取幻燈片標題
text.append(slide.getTitle()).append("\n");
// 獲取文本運行
for (TextRun run : slide.getTextRuns()) {
for (RichTextRun rtr : run.getRichTextRuns()) {
text.append(rtr.getText()).append(" ");
}
}
text.append("\n---\n");
}
}
return text.toString();
}
// 依賴
// poi-scratchpad (HSLF .ppt)
// poi-ooxml (XSLF .pptx)
高性能流處理模式
1. 緩衝包裝 (必須)
// 原始流包裝 BufferedInputStream,減少系統調用
try (BufferedInputStream bis = new BufferedInputStream(rawIn, 64 * 1024)) {
// 讀取...
}
2. 內存映射文件 (大文件隨機訪問)
try (FileChannel channel = FileChannel.open(path, StandardOpenOption.READ)) {
MappedByteBuffer buffer = channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size());
// 直接內存訪問,OS 負責頁面調度
}
3. 零拷貝傳輸 (Java 9+ / NIO)
// InputStream -> OutputStream 零拷貝 (內核態傳輸)
in.transferTo(out); // Java 9+
// 或 NIO Channel
channel.transferTo(0, channel.size(), targetChannel);
4. 異步非阻塞 (NIO.2)
AsynchronousFileChannel channel = AsynchronousFileChannel.open(path);
CompletionHandler handler = new CompletionHandler<>() {
@Override public void completed(Integer result, ByteBuffer attachment) {
// 讀取完成回調
}
@Override public void failed(Throwable exc, ByteBuffer attachment) {
// 失敗處理
}
};
ByteBuffer buffer = ByteBuffer.allocate(16 * 1024);
channel.read(buffer, 0, buffer, handler);
常見異常與排查
| 異常 | 原因 | 解決 |
| SocketTimeoutException | 讀取超時 | 調大 setSoTimeout、檢查網絡/服務端 |
| EOFException | 預期長度未讀滿流結束 | 檢查協議長度字段、對端是否提前關閉 |
| StreamClosedException | 流已關閉後再讀取 | 檢查 try-with-resources 作用域、多線程共享流 |
| OutOfMemoryError | readAllBytes() 讀超大文件 | 流式處理、分塊讀取、避免全量載入內存 |
資源管理鐵律
- 所有 InputStream 使用
try-with-resources 自動關閉
- 裝飾器流 (BufferedInputStream 等) 關閉時會自動關閉底層流
- 異常情況下也要保證關閉,防止文件句柄洩漏
- 網絡流關閉前確保數據讀完,避免連接重用污染
掌握 InputStream 正確讀取模式、避開 available() 陷阱、善用緩衝/零拷貝/內存映射/異步 NIO,構建高性能、穩健的 Java 流處理能力。