作为一名Java开发者,文件操作和IO流是我们日常开发中不可或缺的核心技能。记得我刚入行时,第一次处理文件上传功能就因为对IO流理解不深,导致服务器内存溢出。今天我就结合多年实战经验,带大家系统掌握Java文件操作的精髓。
File类是java.io包下的重要类,它代表的是文件或目录的抽象路径,而不是文件内容本身。这里有个关键点需要理解:File对象可能对应着物理磁盘上真实存在的文件,也可能只是一个逻辑上的路径表示。
java复制// 创建File对象的三种典型方式
File file1 = new File("test.txt"); // 相对路径
File file2 = new File("/usr/local/test"); // 绝对路径
File file3 = new File(file2, "config.ini"); // 父目录+子文件
路径处理注意事项:
java复制File file = new File("demo.txt");
System.out.println("文件名:" + file.getName());
System.out.println("绝对路径:" + file.getAbsolutePath());
System.out.println("文件大小:" + file.length() + "字节");
System.out.println("最后修改时间:" + new Date(file.lastModified()));
特别提醒:length()方法对目录无效,返回结果是不确定的。实际项目中如果需要获取目录大小,需要递归计算所有文件大小之和。
java复制File dir = new File("mydir");
// 创建单级目录
boolean created = dir.mkdir();
// 创建多级目录
boolean createdAll = dir.mkdirs();
// 列出目录内容
String[] children = dir.list();
File[] files = dir.listFiles();
常见坑点:
java复制File source = new File("source.txt");
File dest = new File("backup.txt");
// 文件重命名/移动
boolean renamed = source.renameTo(dest);
// 删除文件(永久删除,不进回收站)
boolean deleted = dest.delete();
// 创建新文件
boolean created = new File("new.txt").createNewFile();
安全建议:
Java IO流按照不同维度可以分为多种类型,选择合适的流对性能影响很大:
| 分类维度 | 类型 | 特点 | 典型实现类 |
|---|---|---|---|
| 数据流向 | 输入流 | 从外部到内存 | FileInputStream |
| 输出流 | 从内存到外部 | FileOutputStream | |
| 数据处理单位 | 字节流 | 8位字节操作,适合二进制文件 | InputStream/OutputStream |
| 字符流 | 16位字符操作,适合文本文件 | Reader/Writer | |
| 功能角色 | 节点流 | 直接连接数据源的基础流 | FileReader |
| 处理流 | 对现有流封装增强功能的流 | BufferedReader |
选型建议:
java复制// 方式1:单字符读取(适合小文件)
try (FileReader fr = new FileReader("test.txt")) {
int data;
while ((data = fr.read()) != -1) {
System.out.print((char) data);
}
}
// 方式2:缓冲区读取(推荐)
try (FileReader fr = new FileReader("large.txt")) {
char[] buffer = new char[8192]; // 8K缓冲区
int len;
while ((len = fr.read(buffer)) != -1) {
String content = new String(buffer, 0, len);
System.out.print(content);
}
}
性能对比:
java复制try (FileWriter fw = new FileWriter("output.txt", true)) { // 追加模式
fw.write("Hello World\n");
fw.flush(); // 手动刷新缓冲区
// 不需要每次write都flush,但关键数据建议立即刷新
}
关键点:
二进制文件处理必须使用字节流,如图片、视频、压缩包等:
java复制// 图片拷贝示例
try (FileInputStream fis = new FileInputStream("source.jpg");
FileOutputStream fos = new FileOutputStream("copy.jpg")) {
byte[] buffer = new byte[8192];
int len;
while ((len = fis.read(buffer)) != -1) {
fos.write(buffer, 0, len);
}
}
性能优化技巧:
缓冲流(BufferedInputStream/BufferedReader等)通过减少实际IO操作次数大幅提升性能:
java复制// 无缓冲 vs 有缓冲性能对比
long start = System.currentTimeMillis();
try (FileInputStream fis = new FileInputStream("large.bin");
BufferedInputStream bis = new BufferedInputStream(fis)) {
// 使用bis读取...
}
long end = System.currentTimeMillis();
System.out.println("缓冲流耗时:" + (end - start) + "ms");
实测数据:
特殊方法:
当文件编码与系统默认编码不一致时,转换流(InputStreamReader/OutputStreamWriter)是救星:
java复制// 读取GBK编码文件
try (InputStreamReader isr = new InputStreamReader(
new FileInputStream("gbk.txt"), "GBK")) {
// 处理中文内容...
}
// 写入UTF-8文件
try (OutputStreamWriter osw = new OutputStreamWriter(
new FileOutputStream("utf8.txt"), StandardCharsets.UTF_8)) {
osw.write("中文内容");
}
编码处理要点:
对象序列化允许将Java对象转换为字节流,便于存储或网络传输:
java复制// 可序列化对象
class User implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private transient String password; // 不被序列化
}
// 序列化
try (ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream("user.dat"))) {
oos.writeObject(new User("张三", "123456"));
}
// 反序列化
try (ObjectInputStream ois = new ObjectInputStream(
new FileInputStream("user.dat"))) {
User user = (User) ois.readObject();
}
关键注意事项:
IO资源必须正确关闭,否则会导致内存泄漏和文件锁定:
java复制// 传统try-catch-finally方式
FileInputStream fis = null;
try {
fis = new FileInputStream("file.txt");
// 使用流...
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
// try-with-resources方式(推荐)
try (FileInputStream fis = new FileInputStream("file.txt");
BufferedInputStream bis = new BufferedInputStream(fis)) {
// 自动关闭资源
}
关闭原则:
处理GB级别大文件时需要特殊技巧:
java复制int bufferSize = 8 * 1024 * 1024; // 8MB
byte[] buffer = new byte[bufferSize];
int bytesRead;
while ((bytesRead = input.read(buffer)) != -1) {
// 处理当前块
}
java复制try (RandomAccessFile raf = new RandomAccessFile("huge.bin", "r");
FileChannel channel = raf.getChannel()) {
MappedByteBuffer buffer = channel.map(
FileChannel.MapMode.READ_ONLY, 0, channel.size());
// 直接操作buffer...
}
问题1:文件读取乱码
问题2:文件被锁定无法删除
问题3:内存溢出
问题4:跨平台路径问题
Java NIO(New IO)提供了更高效的IO处理方式:
| 特性 | 传统IO | NIO |
|---|---|---|
| 数据流方式 | 流式(Stream) | 块式(Block) |
| 缓冲策略 | 需手动缓冲 | 内置缓冲 |
| 非阻塞支持 | 不支持 | 支持 |
| 选择器 | 无 | 有 |
| 适用场景 | 连接数少、数据量小 | 高并发、大数据量 |
Java 7引入的Files类极大简化了文件操作:
java复制// 读取所有行
List<String> lines = Files.readAllLines(Paths.get("data.txt"));
// 写入文件
Files.write(Paths.get("output.txt"), "content".getBytes());
// 文件拷贝
Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING);
// 递归遍历目录
Files.walk(Paths.get("/path")).forEach(System.out::println);
Java的异步IO能力在不断进化:
在实际项目中,我通常会根据具体需求选择技术方案。对于简单的配置文件读写,传统IO就足够;处理大文件或需要高性能时,NIO是更好的选择;而在现代微服务架构中,响应式IO正变得越来越重要。