RandomAccessFile 是 Java IO 體系中獨特的存在:它不屬於標準流體系,但封裝了輸入/輸出流,支持隨機讀寫、文件指針定位、權限模式控制。本文通過代碼演示其核心 API,並揭示多線程斷點下載的底層實現原理。
new RandomAccessFile(File/String name, String mode),mode:"r" (只讀)、"rw" (讀寫)、"rws" (同步寫入)、"rwd" (同步寫入內容)。seek(long pos) 移動指針、skipBytes(int n) 相對跳過、getFilePointer() 獲取當前指針、length() 文件長度、read/write 系列。public class RandomAccessFileDemo {
public static void main(String[] args) throws Exception {
// 寫入
try (RandomAccessFile raf = new RandomAccessFile("test2.txt", "rw")) {
raf.write("陶士涵李小明".getBytes(StandardCharsets.UTF_8));
}
// 讀取:從位置 6 開始(跳過 "陶士涵" 3 個字符 * 2-3 字節)
readFile();
}
public static void readFile() throws Exception {
try (RandomAccessFile raf = new RandomAccessFile("test2.txt", "rw")) {
raf.seek(6); // 移動指針到第 6 字節
byte[] buf = new byte[1024];
int len = raf.read(buf);
System.out.println(new String(buf, 0, len, StandardCharsets.UTF_8)); // 輸出: 李小明
}
}
}
HTTP 支持 Range: bytes=start-end 請求頭,服務器返回 206 Partial Content。RandomAccessFile 使多線程並發寫入不同區塊成為可能:
// 線程任務
class DownloadTask implements Runnable {
private final String url;
private final RandomAccessFile raf;
private final long startPos;
private final long endPos;
public void run() {
try {
HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
conn.setRequestProperty("Range", "bytes=" + startPos + "-" + endPos);
try (InputStream in = conn.getInputStream()) {
raf.seek(startPos); // 關鍵:定位到各自負責的起始位置
byte[] buf = new byte[8192];
int len;
while ((len = in.read(buf)) != -1) {
raf.write(buf, 0, len);
}
}
} catch (Exception e) { e.printStackTrace(); }
}
}
// 主程序
public void multiThreadDownload(String url, String destFile, int threadCount) throws Exception {
URL u = new URL(url);
HttpURLConnection conn = (HttpURLConnection) u.openConnection();
long fileSize = conn.getContentLengthLong();
conn.disconnect();
try (RandomAccessFile raf = new RandomAccessFile(destFile, "rw")) {
raf.setLength(fileSize); // 預分配文件大小
}
long blockSize = fileSize / threadCount;
Thread[] threads = new Thread[threadCount];
for (int i = 0; i < threadCount; i++) {
long start = i * blockSize;
long end = (i == threadCount - 1) ? fileSize - 1 : (i + 1) * blockSize - 1;
threads[i] = new Thread(new DownloadTask(url, new RandomAccessFile(destFile, "rw"), start, end));
threads[i].start();
}
for (Thread t : threads) t.join();
}
seek() 讓每個線程精確寫入各自區塊,無需內存緩衝再合併。setLength() 避免文件擴展碎片,提升寫入性能。seek(pos) 續傳。"rws"/"rwd" 確保關鍵數據(如下載進度元數據)實時落盤。RandomAccessFile 實例需外部同步(示例中各線程創建獨立實例打開同一文件,依賴 OS 文件鎖)。FileChannel.map() (MappedByteBuffer) 獲得更高性能。try-with-resources 或 finally 中關閉,防止文件句柄洩漏。