隨機存取允許任意位置讀寫,不必順序掃描;壓縮則節省儲存與傳輸頻寬。本文詳細講解 RandomAccessFile 定位讀寫、檔案鎖、記錄式存取模式,以及 java.util.zip 套件的 ZIP/GZIP 壓縮解壓實戰範例。
DataInput/DataOutput,**非**繼承 InputStream/OutputStream。"r"(唯讀)、"rw"(讀寫)、"rws"/"rwd"(同步寫入)。seek(pos) 任意跳轉。| 方法 | 說明 |
|---|---|
seek(long pos) | 移動指標(0 = 檔案開頭) |
getFilePointer() | 取得當前指標位置 |
length() | 檔案長度(位元組) |
readInt()/writeInt() 等 | 基本型別讀寫(大端序) |
read(byte[])/write(byte[]) | 批次位元組讀寫 |
setLength(long) | 截斷/擴展檔案 |
getChannel() | 取得 FileChannel 進行 NIO 操作/鎖 |
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");
}
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);
}
}
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();
}
}
}
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 無檔案目錄結構,適合單一大檔(日誌、資料庫備份、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);
}
}
BufferedInputStream/OutputStream 包裹節點流,8KB~64KB。FileChannel.transferTo 零拷貝壓縮/解壓更快。DeflaterOutputStream 可設 level (0-9),ZIP/GZIP 預設 6。RandomAccessFile.getChannel().map(MapMode.READ_WRITE, 0, size) 適用頻繁隨機修改的大檔。RandomAccessFile 非執行緒安全,多執行緒需外部同步或用 FileChannel + 鎖。RandomAccessFile 解決「任意位置讀寫」與「檔案鎖」需求,是嵌入式資料庫、斷點續傳的基石;ZipOutputStream/GZIPOutputStream 則是資料打包傳輸的標準工具。掌握指標控制、記錄對齊、Zip Slip 防禦、緩衝組合,即可靈活應對檔案級持久化場景。