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

Java RandomAccessFile 完全指南:隨機讀寫、記憶體映射與多線程下載實戰

     編輯:Java 相關知識

RandomAccessFile 是 Java I/O 體系中獨特的存在:它不屬於流體系,直接繼承 Object,實現 DataInput/DataOutput 接口,支持隨機定位讀寫。本文系統講解核心 API、典型應用場景、記憶體映射文件進階用法,並給出多線程斷點下載、文件插入數據的生產級代碼範例。

一、核心特性與定位

  • 模式:"r"(只讀)、"rw"(讀寫)
  • 關鍵方法:seek(long pos) 移動文件指針、getFilePointer() 獲取當前位置、length() 文件長度、skipBytes(int n) 跳過字節
  • 支持基本類型直接讀寫:readInt()、writeDouble()、readUTF() 等
  • 適用場景:斷點續傳、大文件隨機訪問、數據庫索引文件、日誌切片分析

二、基礎操作範例

public class RAFBasicDemo {
    public static void main(String[] args) throws IOException {
        // 寫入基本類型數據
        try (RandomAccessFile raf = new RandomAccessFile("data.dat", "rw")) {
            for (int i = 0; i < 10; i++) raf.writeDouble(i * 1.414);
        }
        
        // 隨機修改第 6 個 double(索引 5,偏移 5*8=40 字節)
        try (RandomAccessFile raf = new RandomAccessFile("data.dat", "rw")) {
            raf.seek(40);
            raf.writeDouble(47.0001);
        }
        
        // 讀取驗證
        try (RandomAccessFile raf = new RandomAccessFile("data.dat", "r")) {
            for (int i = 0; i < 10; i++) {
                System.out.println("Value " + i + ": " + raf.readDouble());
            }
        }
    }
}

三、記憶體映射文件 —— 大文件處理利器

通過 FileChannel.map() 將文件區域直接映射到虛擬記憶體,MappedByteBuffer 繼承 ByteBuffer,操作即記憶體操作,OS 負責頁面調度。

映射 128 MB 文件並讀寫

public class LargeMappedFileDemo {
    static final int SIZE = 0x8000000; // 128 MB
    
    public static void main(String[] args) throws Exception {
        try (FileChannel fc = new RandomAccessFile("big.dat", "rw").getChannel()) {
            MappedByteBuffer buffer = fc.map(FileChannel.MapMode.READ_WRITE, 0, SIZE);
            
            // 寫入
            for (int i = 0; i < SIZE; i++) buffer.put((byte) 'x');
            System.out.println("寫入完成");
            
            // 讀取中間 6 字節
            for (int i = SIZE/2; i < SIZE/2 + 6; i++) 
                System.out.print((char) buffer.get(i));
        }
    }
}

優勢:無需一次性載入整個文件,虛擬記憶體按需分頁加載,支持 TB 級文件(64 位 JVM)。

四、實戰 1:文件指定位置插入數據

public static void insertAt(long pos, String content, String filePath) throws IOException {
    try (RandomAccessFile raf = new RandomAccessFile(filePath, "rw")) {
        if (pos < 0 || pos > raf.length()) throw new IllegalArgumentException("位置非法");
        
        byte[] data = content.getBytes(StandardCharsets.UTF_8);
        long newLength = raf.length() + data.length;
        raf.setLength(newLength);  // 擴展文件
        
        // 從後向前移動數據,騰出空間
        for (long i = newLength - 1; i >= pos + data.length; i--) {
            raf.seek(i - data.length);
            byte b = raf.readByte();
            raf.seek(i);
            raf.writeByte(b);
        }
        
        // 寫入新數據
        raf.seek(pos);
        raf.write(data);
    }
}

五、實戰 2:多線程斷點下載核心邏輯

public class MultiThreadDownloader {
    public static void download(String url, File target, int threadCount) throws Exception {
        // 1. 預分配文件空間
        try (RandomAccessFile raf = new RandomAccessFile(target, "rw")) {
            raf.setLength(getContentLength(url));
        }
        
        long partSize = target.length() / threadCount;
        CountDownLatch latch = new CountDownLatch(threadCount);
        
        for (int i = 0; i < threadCount; i++) {
            long start = i * partSize;
            long end = (i == threadCount - 1) ? target.length() : (i + 1) * partSize - 1;
            
            new Thread(() -> {
                try (RandomAccessFile raf = new RandomAccessFile(target, "rw");
                     InputStream in = new URL(url).openStream()) {
                    
                    in.skip(start);
                    raf.seek(start);
                    
                    byte[] buf = new byte[8192];
                    int len;
                    long downloaded = 0;
                    while (downloaded < end - start + 1 && (len = in.read(buf)) != -1) {
                        int toWrite = (int) Math.min(len, end - start + 1 - downloaded);
                        raf.write(buf, 0, toWrite);
                        downloaded += toWrite;
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                } finally {
                    latch.countDown();
                }
            }).start();
        }
        
        latch.await(); // 等待所有線程完成
        System.out.println("下載完成:" + target.getAbsolutePath());
    }
    
    private static long getContentLength(String urlStr) throws IOException {
        HttpURLConnection conn = (HttpURLConnection) new URL(urlStr).openConnection();
        conn.setRequestMethod("HEAD");
        return conn.getContentLengthLong();
    }
}

六、最佳實踐與避坑指南

  1. 編碼處理:讀寫字符串時顯式指定 StandardCharsets.UTF_8,避免平台預設編碼不一致
  2. 資源關閉:使用 try-with-resources 確保 RandomAccessFile、FileChannel 及時關閉
  3. 併發安全:多線程寫同一文件不同區域安全;同一區域需外部同步
  4. 大文件映射:32 位 JVM 單次映射 ≤ 1.5 GB,大文件分段映射(如 100 MB 一段)
  5. 強制刷新:關鍵數據寫入後調用 buffer.force() 或 raf.getFD().sync()

七、總結

RandomAccessFile 賦予 Java 類 C 語言 fseek 的隨機訪問能力,配合 NIO 記憶體映射,是構建高性能文件處理、下載工具、嵌入式存儲引擎的基石。掌握 seek、map、setLength 核心操作,能解決 90% 的非順序文件訪問需求。

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