Java IO 文件复制完全指南:从基础到高效(附完整代码实操)
文件复制是 Java IO 最核心的实战场景之一,无论是文本文件、图片、视频还是压缩包,都可以通过 IO 流实现复制。核心原则是:用字节流处理所有文件类型(万能方案),用缓冲流提升效率,用 NIO 简化代码。本文将分 4 种主流实现方式,从基础到进阶,带你彻底掌握文件复制。
一、核心前提(避免踩坑)
- 必须用字节流:字符流仅适用于文本文件,复制图片、视频等二进制文件会导致文件损坏,因此所有复制方案均基于字节流;
- 缓冲区是效率关键:直接读写单个字节效率极低,用字节数组(缓冲区)批量读写可大幅提升性能;
- 资源必须关闭:IO 流是稀缺资源,需用
try-with-resources自动关闭(Java 7+ 推荐),避免资源泄露; - 路径处理:需区分相对路径和绝对路径,避免“文件找不到”错误(如
test.jpg是项目根目录,src/main/resources/test.jpg是资源目录)。
二、4 种文件复制实现方案(从基础到高效)
方案 1:基础字节流(FileInputStream + FileOutputStream)
最原始的实现方式,直接操作基础字节流,适合理解复制原理(新手入门必学)。
实现代码
importjava.io.FileInputStream;importjava.io.FileOutputStream;importjava.io.IOException;/** * 基础字节流文件复制(无缓冲,适合小文件) */publicclassBasicFileCopy{publicstaticvoidmain(String[]args){// 源文件路径(必须存在)、目标文件路径(不存在会自动创建)StringsourcePath="source.jpg";// 可替换为绝对路径:C:/source.jpgStringtargetPath="target_basic.jpg";longstartTime=System.currentTimeMillis();// 计时开始// try-with-resources 自动关闭流,无需手动 close()try(FileInputStreamfis=newFileInputStream(sourcePath);FileOutputStreamfos=newFileOutputStream(targetPath)){intsingleByte;// 存储每次读取的单个字节(0-255)// 循环读取:read() 返回 -1 表示读取完毕while((singleByte=fis.read())!=-1){fos.write(singleByte);// 逐个字节写入}fos.flush();// 强制刷新缓冲区,确保数据写入longcostTime=System.currentTimeMillis()-startTime;System.out.println("基础字节流复制完成!耗时:"+costTime+"ms");}catch(IOExceptione){System.err.println("复制失败:"+e.getMessage());e.printStackTrace();}}}代码说明
- 核心逻辑:
fis.read()逐个读取字节,fos.write()逐个写入,本质是“字节级拷贝”; - 优点:代码简单,易理解,无需额外包装;
- 缺点:效率极低(频繁磁盘 IO),仅适合100KB 以下的小文件,大文件(如 100MB 视频)会卡顿。
方案 2:基础字节流 + 缓冲区(推荐入门使用)
在方案 1 基础上添加字节数组缓冲区,批量读写字节,效率比方案 1 提升 10-100 倍,是日常开发的“基础优选方案”。
实现代码
importjava.io.FileInputStream;importjava.io.FileOutputStream;importjava.io.IOException;/** * 基础字节流 + 缓冲区(适合中、小文件,效率较高) */publicclassBufferedArrayFileCopy{publicstaticvoidmain(String[]args){StringsourcePath="source.mp4";// 测试大文件(如 100MB 视频)StringtargetPath="target_buffered_array.mp4";longstartTime=System.currentTimeMillis();try(FileInputStreamfis=newFileInputStream(sourcePath);FileOutputStreamfos=newFileOutputStream(targetPath)){// 缓冲区:4KB(常用最优大小,可调整为 8192、16384 等 2^n 数值)byte[]buffer=newbyte[4096];intreadLen;// 记录每次实际读取的字节数(最多为缓冲区大小)// 批量读取:read(buffer) 填充缓冲区,返回实际读取长度while((readLen=fis.read(buffer))!=-1){// 写入实际读取的字节(避免写入缓冲区中未使用的部分)fos.write(buffer,0,readLen);}fos.flush();longcostTime=System.currentTimeMillis()-startTime;System.out.println("缓冲区字节流复制完成!耗时:"+costTime+"ms");}catch(IOExceptione){System.err.println("复制失败:"+e.getMessage());e.printStackTrace();}}}代码说明
- 核心优化:
byte[] buffer = new byte[4096]一次性读取 4KB 数据,减少磁盘 IO 次数(IO 是磁盘操作,耗时远高于内存操作); readLen关键作用:最后一次读取可能未满缓冲区,write(buffer, 0, readLen)仅写入实际读取的字节,避免文件末尾出现垃圾数据;- 优点:效率高,代码简洁,无额外依赖,适合10MB-1GB 的中大型文件;
- 注意:缓冲区大小并非越大越好(如 1GB 缓冲区会占用过多内存),4KB/8KB/16KB 是平衡内存和效率的最优选择。
方案 3:缓冲流包装(BufferedInputStream + BufferedOutputStream)
Java 提供的专用缓冲流,内部自带缓冲区(默认 8KB),无需手动定义字节数组,是“高效简洁方案”。
实现代码
importjava.io.BufferedInputStream;importjava.io.BufferedOutputStream;importjava.io.FileInputStream;importjava.io.FileOutputStream;importjava.io.IOException;/** * 缓冲流包装(推荐生产环境使用,高效且简洁) */publicclassBufferedStreamFileCopy{publicstaticvoidmain(String[]args){StringsourcePath="source.zip";// 压缩包等二进制文件StringtargetPath="target_buffered_stream.zip";longstartTime=System.currentTimeMillis();// 缓冲流包装基础字节流:BufferedInputStream 自带 8KB 缓冲区try(BufferedInputStreambis=newBufferedInputStream(newFileInputStream(sourcePath));BufferedOutputStreambos=newBufferedOutputStream(newFileOutputStream(targetPath))){byte[]buffer=newbyte[4096];// 可手动指定缓冲区大小(建议与内部缓冲区匹配)intreadLen;while((readLen=bis.read(buffer))!=-1){bos.write(buffer,0,readLen);}bos.flush();// 缓冲流需手动刷新,确保数据写入longcostTime=System.currentTimeMillis()-startTime;System.out.println("缓冲流复制完成!耗时:"+costTime+"ms");}catch(IOExceptione){System.err.println("复制失败:"+e.getMessage());e.printStackTrace();}}}代码说明
- 设计思想:装饰器模式(缓冲流包装基础流,增强缓冲功能);
- 优势:
- 内部优化:
BufferedInputStream会预读取数据到缓冲区,减少磁盘 IO; - 代码简洁:无需关心缓冲区底层实现,仅需调用基础读写方法;
- 效率最优:比方案 2 略快(内部有额外优化),是生产环境首选方案;
- 内部优化:
- 注意:缓冲流必须调用
flush()或等待缓冲区满,数据才会写入目标文件,try-with-resources关闭流时会自动刷新,但建议手动调用更稳妥。
方案 4:Java 8+ NIO.2(Files 工具类)
Java 7 引入的 NIO.2 提供了Files工具类,一行代码即可实现文件复制,底层优化充分,适合简洁场景。
实现代码
importjava.nio.file.Files;importjava.nio.file.Paths;importjava.nio.file.StandardCopyOption;importjava.io.IOException;/** * NIO.2 Files 工具类(Java 8+ 推荐,一行代码复制) */publicclassNioFilesCopy{publicstaticvoidmain(String[]args){StringsourcePath="source.pdf";StringtargetPath="target_nio.pdf";longstartTime=System.currentTimeMillis();try{// 核心方法:Files.copy(源路径, 目标路径, 复制选项)Files.copy(Paths.get(sourcePath),// 源文件路径(Path 对象)Paths.get(targetPath),// 目标文件路径StandardCopyOption.REPLACE_EXISTING// 选项:目标文件存在则覆盖);longcostTime=System.currentTimeMillis()-startTime;System.out.println("NIO Files 复制完成!耗时:"+costTime+"ms");}catch(IOExceptione){System.err.println("复制失败:"+e.getMessage());e.printStackTrace();}}}代码说明
- 核心优点:
- 代码极简:一行代码完成复制,无需手动处理流;
- 底层高效:内部使用 NIO 通道(Channel)和缓冲区,性能不逊于缓冲流;
- 功能强大:支持多种复制选项(如覆盖、原子操作等);
- 常用复制选项:
StandardCopyOption.REPLACE_EXISTING:目标文件存在则覆盖(默认不覆盖,会抛异常);StandardCopyOption.COPY_ATTRIBUTES:复制文件属性(如创建时间、权限);StandardCopyOption.ATOMIC_MOVE:原子操作(仅适用于同一文件系统);
- 适用场景:快速实现复制,无需自定义缓冲区或流,适合日常开发、脚本工具等场景。
三、常见问题与避坑指南
1. 文件找不到异常(FileNotFoundException)
- 原因:源文件路径错误,或源文件不存在;
- 解决方案:
- 用绝对路径测试(如
C:/Users/xxx/source.jpg); - 读取资源目录文件(如
src/main/resources)时,用类加载器获取路径:// 获取 resources 下的文件路径StringsourcePath=NioFilesCopy.class.getClassLoader().getResource("source.jpg").getPath();
- 用绝对路径测试(如
2. 目标文件被占用(IOException: 另一个程序正在使用此文件)
- 原因:目标文件已被其他程序打开(如图片被图片查看器占用);
- 解决方案:关闭占用目标文件的程序,或更换目标文件名称。
3. 复制大文件内存溢出(OutOfMemoryError)
- 原因:方案 1 中逐个字节读取效率低,或缓冲区设置过大(如 1GB);
- 解决方案:使用方案 2/3/4,缓冲区设置为 4KB-16KB,避免一次性加载大量数据到内存。
4. 复制后文件损坏
- 原因:误用字符流复制二进制文件(如
FileReader/FileWriter); - 解决方案:所有文件复制均使用字节流(
InputStream/OutputStream)或 NIOFiles工具类。
四、4 种方案对比与选择建议
| 方案 | 核心类 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| 基础字节流 | FileInputStream + FileOutputStream | 代码简单,易理解 | 效率极低 | 新手学习,小文件(<100KB) |
| 基础字节流+缓冲区 | FileInputStream + FileOutputStream + 字节数组 | 效率高,无额外依赖 | 需手动管理缓冲区 | 中大型文件(10MB-1GB),需自定义缓冲区 |
| 缓冲流包装 | BufferedInputStream + BufferedOutputStream | 效率最优,代码简洁 | 需手动刷新 | 生产环境首选,各类文件(推荐) |
| NIO.2 Files | Files.copy() | 一行代码,底层优化 | 灵活性低(难自定义) | 日常开发、脚本工具,无需自定义逻辑 |
最终选择建议:
- 学习阶段:先掌握方案 1(理解原理)→ 方案 2(掌握缓冲区);
- 开发阶段:优先使用方案 3(缓冲流)或方案 4(NIO Files),兼顾效率和简洁性;
- 大文件(>1GB):方案 3(缓冲流)或方案 4(NIO),避免内存溢出。
总结
Java 文件复制的核心是“字节流 + 缓冲区”,无论哪种方案,本质都是“读取源文件字节 → 写入目标文件字节”。新手建议从基础方案入手,理解流的读写逻辑和缓冲区原理;实际开发中优先使用缓冲流或 NIO Files 工具类,兼顾效率和代码简洁性。记住:复制时始终用字节流,关闭资源用try-with-resources,缓冲区设置为 4KB-16KB,即可避免绝大多数问题。