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

Java 文件分割與合併實戰:RandomAccessFile 高效處理大文件斷點續傳核心技術

     編輯:Java 相關知識

前言:為什麼選 RandomAccessFile?

處理 GB 級大文件分割、合併、斷點續傳,RandomAccessFile(RAF)是 Java IO 體系中的「特種兵」:

  • 隨機訪問:seek(pos) 直接定位文件指針,無需順序讀取
  • 雙向操作:單一對象同時支持 read()/write(),模式 "r"/"rw"/"rws"/"rwd"
  • 內存友好:不載入整個文件,按塊讀寫,常量內存佔用
  • 斷點續傳核心:記錄已讀寫位置,崩潰恢復只需 seek() 繼續

一、核心 API 速覽


// 創建
RandomAccessFile raf = new RandomAccessFile(file, "rw"); // 讀寫模式

// 定位
long pos = raf.getFilePointer(); // 當前指針位置
raf.seek(pos); // 移動指針
raf.seek(raf.length()); // 移動到末尾(追加模式)

// 讀寫
int b = raf.read(); // 讀取單字節,-1 表示 EOF
raf.read(byte[] buf); // 讀滿 buf 或 EOF
raf.read(byte[] buf, int off, int len);
raf.write(byte[] buf);
raf.write(byte[] buf, int off, int len);
raf.write(int b); // 寫入單字節

// 文件長度
long len = raf.length();

// 關閉
raf.close(); // 必須關閉,釋放文件鎖

二、文件分割:按指定大小切分

需求分析

  • 輸入:源文件路徑、分片大小(KB/MB)
  • 輸出:源文件名.001、源文件名.002... 固定大小分片(最後一片可能較小)
  • 約束:內存佔用 < 分片大小,支持超大文件(>4GB)

核心實現


public class FileSplitter {
    private static final int BUFFER_SIZE = 64 * 1024; // 64KB 緩衝區
    
    public void split(File source, long chunkSizeBytes, String destDir) throws IOException {
        if (!source.exists()) throw new FileNotFoundException(source.getAbsolutePath());
        if (chunkSizeBytes <= 0) throw new IllegalArgumentException("分片大小必須 > 0");
        
        File destDirFile = new File(destDir);
        destDirFile.mkdirs();
        
        try (RandomAccessFile in = new RandomAccessFile(source, "r")) {
            long fileSize = in.length();
            int totalChunks = (int) ((fileSize + chunkSizeBytes - 1) / chunkSizeBytes);
            byte[] buffer = new byte[BUFFER_SIZE];
            
            for (int chunk = 1; chunk <= totalChunks; chunk++) {
                long start = (chunk - 1) * chunkSizeBytes;
                long end = Math.min(start + chunkSizeBytes, fileSize);
                long remaining = end - start;
                
                File chunkFile = new File(destDirFile, source.getName() + "." + String.format("%03d", chunk));
                try (RandomAccessFile out = new RandomAccessFile(chunkFile, "rw")) {
                    in.seek(start);
                    while (remaining > 0) {
                        int readLen = (int) Math.min(BUFFER_SIZE, remaining);
                        int read = in.read(buffer, 0, readLen);
                        if (read == -1) break; // 理論上不會發生
                        out.write(buffer, 0, read);
                        remaining -= read;
                    }
                }
                System.out.printf("分片 %d/%d 完成: %s (%,d bytes)%n", chunk, totalChunks, chunkFile.getName(), end - start);
            }
        }
    }
}

關鍵點

  1. 緩衝區複用:單個 byte[] 循環使用,避免頻繁分配
  2. 精確控制讀取長度:最後一片可能不足分片大小,用 remaining 變量控制
  3. 文件命名規範:%03d 保證按名稱排序即為順序
  4. try-with-resources:保證異常時也能關閉文件句柄

三、文件合併:按序拼接分片

需求分析

  • 輸入:分片目錄、分片後綴/前綴規則、目標文件路徑
  • 輸出:完整文件,內容與原文件位級一致
  • 約束:分片順序正確、內存常量、校驗完整性

核心實現


public class FileMerger {
    private static final int BUFFER_SIZE = 64 * 1024;
    
    public void merge(File chunkDir, String chunkSuffix, File target) throws IOException {
        File[] chunks = chunkDir.listFiles((dir, name) -> name.endsWith(chunkSuffix));
        if (chunks == null || chunks.length == 0) {
            throw new IllegalArgumentException("未找到分片文件: " + chunkSuffix);
        }
        // 關鍵:按文件名排序保證順序
        Arrays.sort(chunks, Comparator.comparing(File::getName));
        
        try (RandomAccessFile out = new RandomAccessFile(target, "rw")) {
            byte[] buffer = new byte[BUFFER_SIZE];
            for (File chunk : chunks) {
                try (RandomAccessFile in = new RandomAccessFile(chunk, "r")) {
                    int read;
                    while ((read = in.read(buffer)) != -1) {
                        out.write(buffer, 0, read);
                    }
                }
                System.out.println("合併分片: " + chunk.getName());
            }
        }
        System.out.printf("合併完成: %s (%,d bytes)%n", target.getAbsolutePath(), target.length());
    }
}

完整性校驗(推薦)


// 分割時生成校驗文件
public void generateChecksum(File source, File checksumFile) throws IOException {
    try (InputStream in = new FileInputStream(source);
         MessageDigest md = MessageDigest.getInstance("SHA-256")) {
        byte[] buffer = new byte[8192];
        int read;
        while ((read = in.read(buffer)) != -1) md.update(buffer, 0, read);
        String hash = Hex.encodeHexString(md.digest());
        Files.writeString(checksumFile.toPath(), hash + "  " + source.getName());
    }
}

// 合併後驗證
public boolean verifyChecksum(File target, File checksumFile) throws IOException {
    String expected = Files.readString(checksumFile.toPath()).split("\\s+")[0];
    String actual = calculateSHA256(target);
    return expected.equalsIgnoreCase(actual);
}

四、斷點續傳核心邏輯

分割/合併是靜態操作,斷點續傳是動態過程,核心狀態:


public class TransferState {
    private final String fileId; // 文件唯一標識(如 SHA-256 + 大小)
    private final long fileSize;
    private final long chunkSize;
    private final BitSet completedChunks; // 或 boolean[]
    private long transferredBytes;
    private Instant lastUpdate;
    
    // 序列化為 JSON 保存進度
    // 恢復時讀取,跳過 completedChunks=true 的分片
}

下載端恢復邏輯


public void downloadWithResume(URL url, File target, TransferState state) throws IOException {
    try (RandomAccessFile raf = new RandomAccessFile(target, "rw")) {
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        conn.setRequestProperty("User-Agent", "JavaDownloader/1.0");
        
        for (int chunk = 0; chunk < state.totalChunks; chunk++) {
            if (state.completedChunks.get(chunk)) continue; // 跳過已完成
            
            long start = chunk * state.chunkSize;
            long end = Math.min(start + state.chunkSize, state.fileSize) - 1;
            conn.setRequestProperty("Range", "bytes=" + start + "-" + end);
            
            try (InputStream in = conn.getInputStream()) {
                raf.seek(start);
                byte[] buffer = new byte[8192];
                int read;
                while ((read = in.read(buffer)) != -1) {
                    raf.write(buffer, 0, read);
                    state.transferredBytes += read;
                }
            }
            state.completedChunks.set(chunk);
            saveState(state); // 定期保存進度
        }
    }
}

五、性能優化與生產級增強

優化點方案效果
系統調用開銷增加緩衝區(64KB-1MB),使用 BufferedInputStream/OutputStream 包裝 RAF減少上下文切換 90%+
磁盤順序寫入分片文件預分配空間 raf.setLength(chunkSize)減少文件碎片,提升寫入吞吐
零拷貝Java 21+ FileChannel.transferTo/From、Linux sendfile用戶態/內核態拷貝消除,CPU 佔用降 50%
並發分片線程池並行下載/寫入不同分片(需服務端支持 Range)帶寬利用率最大化
進度持久化SQLite/LevelDB 存儲狀態,避免單文件鎖競爭支持多任務、崩潰恢復可靠

六、NIO.2 現代替代方案(Java 7+)


// 使用 FileChannel + MappedByteBuffer(內存映射文件)
public void splitWithNIO(File source, long chunkSize, File destDir) throws IOException {
    try (FileChannel in = FileChannel.open(source.toPath(), StandardOpenOption.READ)) {
        long fileSize = in.size();
        int total = (int) ((fileSize + chunkSize - 1) / chunkSize);
        for (int i = 0; i < total; i++) {
            long start = i * chunkSize;
            long end = Math.min(start + chunkSize, fileSize);
            long size = end - start;
            
            File chunkFile = new File(destDir, source.getName() + "." + String.format("%03d", i+1));
            try (FileChannel out = FileChannel.open(chunkFile.toPath(), 
                    StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.READ)) {
                // 內存映射:大文件分片映射需注意內存壓力
                MappedByteBuffer mapped = in.map(FileChannel.MapMode.READ_ONLY, start, size);
                out.write(mapped); // 直接寫入,極快
            }
        }
    }
}

選擇建議:

  • RandomAccessFile:通用、兼容性好、精確控制、適合斷點續傳邏輯
  • FileChannel + MappedByteBuffer:大文件順序讀寫極快、但映射大小受限於虛擬內存、不適合高併發大量映射
  • Files.copy / transferTo:簡單合併/複製、代碼最少

總結

RandomAccessFile 是 Java 處理大文件隨機讀寫的基石,掌握其 seek()、read/write(byte[], off, len)、length() 核心 API,配合緩衝區、分片邏輯、校驗機制、狀態持久化,即可構建生產級的文件分割、合併、斷點續傳系統。現代 NIO.2 提供了更高性能的替代選項,但 RAF 的精確控制特性在斷點續傳等精細場景中仍不可替代。

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