RandomAccessFile (RAF) 是 Java 唯一支援「隨機訪問」文件的類,結合了輸入/輸出流功能,適用於大文件處理、斷點續傳、索引文件等場景。本文深度解析其核心 API、文件指針機制、實戰模式與 NIO 映射文件對比。
核心特性
- 隨機訪問:可移動文件指針到任意位置讀寫
- 雙向操作:同時支援讀 (
read) 和寫 (write)
- 模式:
"r" (只讀)、"rw" (讀寫)、"rws" (同步元數據+內容)、"rwd" (同步內容)
- 非 NIO:屬於
java.io 阻塞 I/O,不支援 Channel/Selector
構造方法與文件指針
// 兩種構造方式
RandomAccessFile raf = new RandomAccessFile(new File("data.dat"), "rw");
RandomAccessFile raf = new RandomAccessFile("data.dat", "rw");
// 文件指針:long 類型,0 表示文件開頭
long pointer = raf.getFilePointer(); // 獲取當前位置
raf.seek(100); // 移動到第 100 字節
raf.seek(raf.length()); // 移動到文件末尾 (追加模式)
核心讀寫 API
基本類型讀寫 (自動處理字節序:Big-Endian)
| 類型 | 寫入 | 讀取 | 字節數 |
| byte | write(int) | read() → int | 1 |
| boolean | writeBoolean(boolean) | readBoolean() | 1 |
| char | writeChar(int) | readChar() | 2 |
| short | writeShort(int) | readShort() | 2 |
| int | writeInt(int) | readInt() | 4 |
| long | writeLong(long) | readLong() | 8 |
| float | writeFloat(float) | readFloat() | 4 |
| double | writeDouble(double) | readDouble() | 8 |
| UTF-8 字符串 | writeUTF(String) | readUTF() | 長度前綴(2字節)+內容 |
字節數組讀寫
// 寫入
raf.write(byte[] b);
raf.write(byte[] b, int off, int len);
// 讀取 (返回實際讀取字節數,-1 表示 EOF)
int read = raf.read(byte[] b);
int read = raf.read(byte[] b, int off, int len);
raf.readFully(byte[] b); // 阻塞直到讀滿或 EOF
raf.readFully(byte[] b, int off, int len);
實戰場景一:斷點續傳下載 (核心邏輯)
public class ResumeDownload {
public static void download(String urlStr, File dest) throws IOException {
long downloaded = dest.exists() ? dest.length() : 0;
URL url = new URL(urlStr);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
// 關鍵:Range 請求頭
if (downloaded > 0) {
conn.setRequestProperty("Range", "bytes=" + downloaded + "-");
}
// 檢查服務端支援
if (conn.getResponseCode() == 206) { // Partial Content
try (InputStream in = conn.getInputStream();
RandomAccessFile raf = new RandomAccessFile(dest, "rw")) {
raf.seek(downloaded); // 移動到斷點位置
byte[] buffer = new byte[8192];
int len;
while ((len = in.read(buffer)) != -1) {
raf.write(buffer, 0, len);
}
}
} else if (conn.getResponseCode() == 200) {
// 不支援斷點,重新下載
dest.delete();
download(urlStr, dest); // 遞歸或循環
}
}
}
實戰場景二:大文件索引與快速查找
public class FileIndexer {
// 構建行偏移量索引
public static List<Long> buildLineIndex(File file) throws IOException {
List<Long> offsets = new ArrayList<>();
try (RandomAccessFile raf = new RandomAccessFile(file, "r")) {
offsets.add(0L); // 第 1 行偏移量
long pos = 0;
int b;
while ((b = raf.read()) != -1) {
pos++;
if (b == '\n') {
offsets.add(pos); // 下一行起始位置
}
}
}
return offsets;
}
// O(1) 定位第 N 行
public static String readLine(File file, List<Long> index, int lineNum) throws IOException {
if (lineNum < 1 || lineNum > index.size()) return null;
try (RandomAccessFile raf = new RandomAccessFile(file, "r")) {
raf.seek(index.get(lineNum - 1));
return raf.readLine(); // 讀取一行 (以 \n/\r\n 結尾)
}
}
}
實戰場景三:固定長度記錄文件 (類數據庫)
public class FixedRecordStore {
private static final int RECORD_SIZE = 64; // 固定記錄長度
private final RandomAccessFile raf;
public FixedRecordStore(File file) throws IOException {
this.raf = new RandomAccessFile(file, "rw");
}
// 寫入第 n 條記錄
public void writeRecord(int index, byte[] data) throws IOException {
if (data.length > RECORD_SIZE) throw new IllegalArgumentException("數據超長");
raf.seek((long) index * RECORD_SIZE);
raf.write(data);
// 補零
if (data.length < RECORD_SIZE) {
raf.write(new byte[RECORD_SIZE - data.length]);
}
}
// 讀取第 n 條記錄
public byte[] readRecord(int index) throws IOException {
raf.seek((long) index * RECORD_SIZE);
byte[] data = new byte[RECORD_SIZE];
raf.readFully(data);
return data;
}
public long recordCount() throws IOException {
return raf.length() / RECORD_SIZE;
}
public void close() throws IOException { raf.close(); }
}
RandomAccessFile vs FileChannel + MappedByteBuffer (NIO 內存映射)
| 維度 | RandomAccessFile | FileChannel + MappedByteBuffer |
| API 風格 | 流式 (read/write/seek) | 緩衝區 (get/put/position/limit) |
| 內存模型 | 堆外緩衝區 (用戶態拷貝) | 內存映射文件 (OS 頁面緩存,零拷貝) |
| 大文件性能 | 頻繁 seek/syscall 開銷大 | 極高 (OS 虛擬內存管理、預讀) |
| 最大映射限制 | 無 (受長度限制) | 2GB (32位) / 理論無限 (64位,但建議分段) |
| 持久化保證 | rws/rwd 模式強制同步 | force() 手動刷新、OS 定期刷新 |
| 併發安全 | 需外部同步 | 需外部同步 (建議 FileLock) |
| 適用場景 | 中小文件、斷點續傳、簡單索引 | 超大文件 (GB+)、高頻隨機讀寫、日誌分析 |
MappedByteBuffer 使用示例
try (FileChannel channel = FileChannel.open(Paths.get("big.dat"),
StandardOpenOption.READ, StandardOpenOption.WRITE)) {
// 映射前 100MB
MappedByteBuffer mbb = channel.map(FileChannel.MapMode.READ_WRITE, 0, 100_000_000);
// 直接像操作內存一樣操作文件
mbb.putInt(0, 12345);
int val = mbb.getInt(0);
// 強制刷新到磁盤
mbb.force();
}
關鍵注意點與避坑
| 問題 | 原因 | 解決 |
| 文件指針越界 | seek 超過 length、read 到 EOF | 檢查 pointer < length()、捕獲 EOFException |
| UTF 字符串讀寫不匹配 | writeUTF/readUTF 使用修改版 UTF-8 (雙字節長度前綴) | 統一用 RAF 讀寫、或手動 writeInt(len) + write(bytes) |
| 多線程併發損壞 | RAF 非線程安全 | 外部鎖、或每線程獨立 RAF 實例 (同一文件) |
| 未關閉導致數據丟失 | 緩衝區未刷新、文件鎖未釋放 | try-with-resources、finally close() |
| rws/rwd 性能極差 | 每次寫入強制磁盤同步 | 批量寫入後手動 getFD().sync()、或用 MappedByteBuffer.force() |
FileDescriptor.sync() vs force()
// RAF 強制同步
raf.getFD().sync(); // 同步數據+元數據 (相當於 rws)
// MappedByteBuffer 強制同步
mbb.force(); // 僅同步數據 (相當於 rwd)
mbb.force(true); // 同步數據+元數據
總結
RandomAccessFile 是 Java IO 體系中獨特的「隨機訪問」工具,以文件指針 + 基本類型讀寫 API 解決斷點續傳、索引查找、固定記錄存儲等場景。對於 GB 級以上大文件高頻隨機讀寫,NIO FileChannel + MappedByteBuffer 利用 OS 內存映射實現零拷貝、虛擬內存管理,性能碾壓 RAF。選型原則:中小文件/簡單邏輯用 RAF,大文件/高性能用 MappedByteBuffer。無論哪種,資源關閉、併發控制、持久化語義 是不可妥協的工程底線。