文件拷貝是 I/O 入門必修課。本文對比傳統字節流逐字節拷貝、緩衝流塊拷貝、NIO transferTo 零拷貝、Files.copy 高級 API 的性能與適用場景,附完整可運行 Demo。
| 方案 | 核心 API | 機制 | 適合場景 | 相對耗時(1GB) |
|---|---|---|---|---|
| 字節流單字節 | FileInputStream.read() / FileOutputStream.write(int) | 用戶態逐字節拷貝,系統調用極多 | 教學演示 | ~120s |
| 字節數組緩衝 | read(byte[]) / write(byte[], 0, len) | 用戶態 4KB/8KB 塊拷貝 | 小文件、舊版本兼容 | ~2.5s |
| BufferedInput/OutputStream | BufferedInputStream / BufferedOutputStream | 內部 8KB 緩衝,減少系統調用 | 通用文本/二進制拷貝 | ~2.2s |
| NIO Channel.transferTo | FileChannel.transferTo(position, count, target) | 內核態零拷貝(sendfile/mmap),無用戶態緩衝區 | 大文件、高性能要求 | ~0.8s |
| Files.copy (JDK 7+) | Files.copy(src, dst, REPLACE_EXISTING) | 底層自動選擇最優策略(transferTo/直接緩衝區) | 現代項目首選,代碼最簡潔 | ~0.8s |
public void copyWithBuffer(File src, File dst) throws IOException {
try (FileInputStream in = new FileInputStream(src);
FileOutputStream out = new FileOutputStream(dst);
BufferedInputStream bin = new BufferedInputStream(in);
BufferedOutputStream bout = new BufferedOutputStream(out)) {
byte[] buf = new byte[8192];
int len;
while ((len = bin.read(buf)) != -1) {
bout.write(buf, 0, len);
}
}
}
public void copyWithTransferTo(File src, File dst) throws IOException {
try (FileChannel in = FileChannel.open(src.toPath(), StandardOpenOption.READ);
FileChannel out = FileChannel.open(dst.toPath(), StandardOpenOption.WRITE, StandardOpenOption.CREATE)) {
long size = in.size();
long transferred = 0;
while (transferred < size) {
transferred += in.transferTo(transferred, size - transferred, out);
}
}
}
Files.copy(src.toPath(), dst.toPath(), StandardCopyOption.REPLACE_EXISTING);
public void copyWithProgress(File src, File dst, Consumer<Double> progress) throws IOException {
try (FileChannel in = FileChannel.open(src.toPath(), StandardOpenOption.READ);
FileChannel out = FileChannel.open(dst.toPath(), StandardOpenOption.WRITE, StandardOpenOption.CREATE)) {
long size = in.size();
long copied = 0;
long chunk = 1024 * 1024; // 1MB
ByteBuffer buf = ByteBuffer.allocateDirect((int) chunk);
while (copied < size) {
long remain = size - copied;
long toRead = Math.min(chunk, remain);
buf.limit((int) toRead);
int read = in.read(buf);
if (read == -1) break;
buf.flip();
out.write(buf);
buf.clear();
copied += read;
progress.accept((double) copied / size);
}
}
}
Files.copy 默認拋異常,需加 REPLACE_EXISTING。transferTo 可能不支援,需降級緩衝拷貝。transferTo 會實體化空洞,導致目標文件膨脹。Files.copy 不複製屬性,需 COPY_ATTRIBUTES 或手動 Files.setAttribute。Files.copy**,簡潔且底層自動優化。read()/write(int) 用於生產環境文件拷貝。