首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java RandomAccessFile 詳解:隨機讀寫、斷點續傳、大文件處理與 MappedByteBuffer 對比

     編輯:Java 相關知識

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)

類型寫入讀取字節數
bytewrite(int)read() → int1
booleanwriteBoolean(boolean)readBoolean()1
charwriteChar(int)readChar()2
shortwriteShort(int)readShort()2
intwriteInt(int)readInt()4
longwriteLong(long)readLong()8
floatwriteFloat(float)readFloat()4
doublewriteDouble(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 內存映射)

維度RandomAccessFileFileChannel + 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。無論哪種,資源關閉、併發控制、持久化語義 是不可妥協的工程底線。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved