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

Java 隨機存取檔案與壓縮操作:RandomAccessFile、ZIP/GZIP 完整實戰

     編輯:Java 相關知識

隨機存取允許任意位置讀寫,不必順序掃描;壓縮則節省儲存與傳輸頻寬。本文詳細講解 RandomAccessFile 定位讀寫、檔案鎖、記錄式存取模式,以及 java.util.zip 套件的 ZIP/GZIP 壓縮解壓實戰範例。

一、RandomAccessFile:唯一的「雙向」檔案類

1. 核心特性

  • 實作 DataInput/DataOutput,**非**繼承 InputStream/OutputStream。
  • 支援 "r"(唯讀)、"rw"(讀寫)、"rws"/"rwd"(同步寫入)。
  • 內部維護**檔案指標**,seek(pos) 任意跳轉。
  • 可直接覆寫、截斷、追加,適合**固定長度記錄**、**資料庫索引**、**大檔案斷點續傳**。

2. 關鍵 API

方法說明
seek(long pos)移動指標(0 = 檔案開頭)
getFilePointer()取得當前指標位置
length()檔案長度(位元組)
readInt()/writeInt() 等基本型別讀寫(大端序)
read(byte[])/write(byte[])批次位元組讀寫
setLength(long)截斷/擴展檔案
getChannel()取得 FileChannel 進行 NIO 操作/鎖

3. 實戰:模擬系統日誌追加(定位檔尾)


public class LogAppender {
    private final RandomAccessFile raf;
    public LogAppender(String path) throws IOException {
        this.raf = new RandomAccessFile(path, "rw");
    }
    public synchronized void append(String line) throws IOException {
        raf.seek(raf.length()); // 跳至檔尾
        raf.writeBytes(line + System.lineSeparator());
    }
    public void close() throws IOException { raf.close(); }
}

// 使用
try (LogAppender log = new LogAppender("app.log")) {
    log.append("[INFO] Service started");
    log.append("[WARN] High memory usage");
}

4. 實戰:固定長度記錄讀寫(如員工編號 8 位、姓名 20 字元、薪資 8 位雙精度)


public class FixedRecordDB {
    private static final int RECORD_SIZE = 8 + 20*2 + 8; // 40 bytes (char=2 bytes)
    private final RandomAccessFile raf;
    
    public FixedRecordDB(String path) throws IOException { raf = new RandomAccessFile(path, "rw"); }
    
    public void writeRecord(long id, String name, double salary, long recNo) throws IOException {
        raf.seek(recNo * RECORD_SIZE);
        raf.writeLong(id);
        char[] nameBuf = new char[20];
        name.getChars(0, Math.min(name.length(), 20), nameBuf, 0);
        raf.writeChars(new String(nameBuf)); // 固定 20 char
        raf.writeDouble(salary);
    }
    
    public Record readRecord(long recNo) throws IOException {
        raf.seek(recNo * RECORD_SIZE);
        long id = raf.readLong();
        char[] nameBuf = new char[20];
        for (int i = 0; i < 20; i++) nameBuf[i] = raf.readChar();
        double salary = raf.readDouble();
        return new Record(id, new String(nameBuf).trim(), salary);
    }
}

二、ZIP 壓縮與解壓:多檔案打包標準

1. 壓縮目錄樹(遞迴)


public class ZipUtil {
    public static void zipDirectory(File srcDir, File zipFile) throws IOException {
        try (ZipOutputStream zos = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(zipFile)))) {
            zipRecursive(srcDir, srcDir.getName(), zos);
        }
    }
    private static void zipRecursive(File file, String entryBase, ZipOutputStream zos) throws IOException {
        if (file.isDirectory()) {
            zos.putNextEntry(new ZipEntry(entryBase + "/"));
            zos.closeEntry();
            for (File child : file.listFiles()) {
                zipRecursive(child, entryBase + "/" + child.getName(), zos);
            }
        } else {
            zos.putNextEntry(new ZipEntry(entryBase));
            try (FileInputStream fis = new FileInputStream(file);
                 BufferedInputStream bis = new BufferedInputStream(fis)) {
                byte[] buf = new byte[8192];
                int len;
                while ((len = bis.read(buf)) != -1) zos.write(buf, 0, len);
            }
            zos.closeEntry();
        }
    }
}

2. 解壓至目標目錄(防 Zip Slip 攻擊)


public static void unzip(File zipFile, File destDir) throws IOException {
    try (ZipInputStream zis = new ZipInputStream(new FileInputStream(zipFile))) {
        ZipEntry entry;
        while ((entry = zis.getNextEntry()) != null) {
            File outFile = new File(destDir, entry.getName()).getCanonicalFile();
            // 安全檢查:確保解壓路徑在目標目錄內
            if (!outFile.getPath().startsWith(destDir.getCanonicalPath() + File.separator)) {
                throw new SecurityException("Zip Slip 攻擊嘗試: " + entry.getName());
            }
            if (entry.isDirectory()) { outFile.mkdirs(); continue; }
            outFile.getParentFile().mkdirs();
            try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(outFile))) {
                byte[] buf = new byte[8192];
                int len;
                while ((len = zis.read(buf)) != -1) bos.write(buf, 0, len);
            }
            zis.closeEntry();
        }
    }
}

三、GZIP:單檔案流壓縮

GZIP 無檔案目錄結構,適合單一大檔(日誌、資料庫備份、HTTP 傳輸)。


// 壓縮
public static void gzipCompress(File src, File dst) throws IOException {
    try (FileInputStream fis = new FileInputStream(src);
         FileOutputStream fos = new FileOutputStream(dst);
         GZIPOutputStream gzos = new GZIPOutputStream(new BufferedOutputStream(fos))) {
        byte[] buf = new byte[8192];
        int len;
        while ((len = fis.read(buf)) != -1) gzos.write(buf, 0, len);
        gzos.finish(); // 關鍵:寫入尾部 CRC
    }
}

// 解壓
public static void gzipDecompress(File src, File dst) throws IOException {
    try (GZIPInputStream gis = new GZIPInputStream(new FileInputStream(src));
         FileOutputStream fos = new FileOutputStream(dst);
         BufferedOutputStream bos = new BufferedOutputStream(fos)) {
        byte[] buf = new byte[8192];
        int len;
        while ((len = gis.read(buf)) != -1) bos.write(buf, 0, len);
    }
}

四、效能與最佳實踐

  1. 緩衝必加:BufferedInputStream/OutputStream 包裹節點流,8KB~64KB。
  2. 大檔案用 NIO:FileChannel.transferTo 零拷貝壓縮/解壓更快。
  3. 壓縮級別:DeflaterOutputStream 可設 level (0-9),ZIP/GZIP 預設 6。
  4. 記憶體映射:RandomAccessFile.getChannel().map(MapMode.READ_WRITE, 0, size) 適用頻繁隨機修改的大檔。
  5. 執行緒安全:RandomAccessFile 非執行緒安全,多執行緒需外部同步或用 FileChannel + 鎖。

結語

RandomAccessFile 解決「任意位置讀寫」與「檔案鎖」需求,是嵌入式資料庫、斷點續傳的基石;ZipOutputStream/GZIPOutputStream 則是資料打包傳輸的標準工具。掌握指標控制、記錄對齊、Zip Slip 防禦、緩衝組合,即可靈活應對檔案級持久化場景。

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