一次關於返回值封裝的代碼審查引發了對 InputStream 實現的深入探索:為何領導堅持用 StringBuilder 而非 StringBuffer?答案藏在 FileInputStream 的 native 實現裡——本文帶你穿透 Java API,直達 JNI 與 HotSpot VM 層。
InputStream 只有一個核心抽象方法:
public abstract int read() throws IOException;
其餘兩個 read(byte[]) 與 read(byte[], int, int) 均為模板方法,預設委託給最底層的三參數版本:
public int read(byte b[]) throws IOException {
return read(b, 0, b.length);
}
public int read(byte b[], int off, int len) throws IOException {
// 子類需覆寫此方法以獲得高效能
}
private FileDescriptor fd; // 文件描述符,對應 C 語言的檔案句柄
// Native 方法宣告
private native void open(String name) throws FileNotFoundException;
public native int read() throws IOException;
private native int readBytes(byte b[], int off, int len) throws IOException;
fd 是通往作業系統層的鑰匙,所有實際 IO 操作最終都透過它完成。
public int read(byte b[]) throws IOException {
return readBytes(b, 0, b.length);
}
public int read(byte b[], int off, int len) throws IOException {
return readBytes(b, off, len); // 核心入口
}
這裡確立了 readBytes 為唯一效能關鍵路徑。
jfieldID fis_fd; // 全局緩存字段 ID
JNIEXPORT void JNICALL
Java_java_io_FileInputStream_initIDs(JNIEnv *env, jclass fdClass) {
fis_fd = (*env)->GetFieldID(env, fdClass, "fd", "Ljava/io/FileDescriptor;");
}
#define BUF_SIZE 8192 // 棧分配緩衝區上限
JNIEXPORT jint JNICALL
Java_java_io_FileInputStream_readBytes(JNIEnv *env, jobject this,
jbyteArray bytes, jint off, jint len) {
return readBytes(env, this, bytes, off, len, fis_fd);
}
int readBytes(JNIEnv *env, jobject this, jbyteArray bytes,
jint off, jint len, jfieldID fid) {
// 1. 參數校驗:空指釥、越界、長度為 0
if (IS_NULL(bytes)) { throwNPE(); return -1; }
int datalen = (*env)->GetArrayLength(env, bytes);
if (off < 0 || len < 0 || off + len > datalen) { throwIOOBE(); return -1; }
if (len == 0) return 0;
// 2. 緩衝區策略:小於 8KB 用棧,大於用堆 malloc
char *buf = (len > BUF_SIZE) ? malloc(len) : stackBuf;
// 3. 獲取文件描述符並調用底層讀取
FD fd = GET_FD(this, fid);
int nread = IO_Read(fd, buf, len); // 宏展開為 JVM_Read
// 4. 結果處理:複製回 Java 字節數組、異常轉換、內存釋放
if (nread > 0) {
(*env)->SetByteArrayRegion(env, bytes, off, nread, (jbyte*)buf);
} else if (nread == JVM_IO_ERR) { throwIOException(); }
else if (nread == JVM_IO_INTR) { throwInterruptedIOException(); }
else { nread = -1; } // EOF
if (buf != stackBuf) free(buf);
return nread;
}
// io_util_md.h (平台相關頭文件)
#define IO_Read JVM_Read
// HotSpot JVM 實現 (hpi.hpp)
static inline size_t read(int fd, void *buf, unsigned int nBytes) {
return (size_t)hpi::read(fd, buf, nBytes); // 委託給 HPI 層
}
// HPI (HotSpot Platform Interface) 定義
struct HPI_FileInterface {
static inline size_t read(int fd, void *buf, unsigned int nBytes);
// ... open, close, lseek, write 等
};
Windows 下通過 HPIDECL 宏綁定到具體實現;Linux/Solaris 則對應各自系統調用封裝。
readBytes(byte[], int, int),子類覆寫此方法即可實現高效能讀取(如 BufferedInputStream)。SetByteArrayRegion 直接將 C 緩衝區內容複製進 Java 堆,無中間對象分配。errno)在 JNI 層統一轉換為 Java IO 異常體系。理解此機制後,我們明白為何:
read() 極其低效(每字節一次 JNI 往返 + 系統調用)。BufferedInputStream 以 8KB 為預設緩衝區大小,正是呼應此處 BUF_SIZE 設計。FileChannel + MappedByteBuffer(記憶體映射)可繞過此路徑,實現零拷貝。源碼不僅解釋「如何實現」,更揭示「為何如此設計」——這正是從 API 使用者進階為系統設計者的關鍵一步。