1. Java输入输出基础概念
在Java编程中,输入输出(I/O)是程序与外界交互的基础手段。Java通过java.io包提供了一套完整的I/O系统,这套系统从Java 1.0版本就存在,经过多年发展已经非常成熟稳定。
Java的I/O系统主要分为两大类:字节流和字符流。字节流以InputStream和OutputStream为基类,处理8位字节的输入输出;字符流以Reader和Writer为基类,处理16位Unicode字符的输入输出。这种区分主要是为了处理不同数据类型的需要,字节流更适合处理二进制数据,而字符流更适合处理文本数据。
实际开发中常见误区:很多初学者会混淆字节流和字符流的使用场景,导致文本文件读取时出现乱码问题。正确的做法是处理文本数据时优先使用字符流。
1.1 字节流体系结构
Java的字节流类层次结构以InputStream和OutputStream这两个抽象类为顶层父类。InputStream定义了读取字节的基本方法,最重要的包括:
java复制public abstract int read() throws IOException;
public int read(byte b[]) throws IOException;
public int read(byte b[], int off, int len) throws IOException;
OutputStream则定义了写入字节的基本方法:
java复制public abstract void write(int b) throws IOException;
public void write(byte b[]) throws IOException;
public void write(byte b[], int off, int len) throws IOException;
基于这两个基类,Java提供了多种具体的实现类:
- FileInputStream/FileOutputStream:文件字节流
- ByteArrayInputStream/ByteArrayOutputStream:字节数组流
- PipedInputStream/PipedOutputStream:管道流
- ObjectInputStream/ObjectOutputStream:对象序列化流
- FilterInputStream/FilterOutputStream:过滤流(装饰器模式)
1.2 字符流体系结构
Java的字符流以Reader和Writer为顶层抽象类。Reader定义了读取字符的基本方法:
java复制public int read() throws IOException;
public int read(char cbuf[]) throws IOException;
public abstract int read(char cbuf[], int off, int len) throws IOException;
Writer定义了写入字符的基本方法:
java复制public void write(int c) throws IOException;
public void write(char cbuf[]) throws IOException;
public abstract void write(char cbuf[], int off, int len) throws IOException;
public void write(String str) throws IOException;
public void write(String str, int off, int len) throws IOException;
常见的字符流实现类包括:
- InputStreamReader/OutputStreamWriter:字节流与字符流的桥梁
- FileReader/FileWriter:文件字符流
- CharArrayReader/CharArrayWriter:字符数组流
- StringReader/StringWriter:字符串流
- BufferedReader/BufferedWriter:缓冲字符流
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 文件读写操作实战
文件操作是Java I/O最常见的应用场景。下面我们通过几个实际案例来演示如何进行文件读写操作。
2.1 使用字节流读写文件
最基本的文件读写可以使用FileInputStream和FileOutputStream:
java复制// 文件复制示例
try (InputStream in = new FileInputStream("source.txt");
OutputStream out = new FileOutputStream("target.txt")) {
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = in.read(buffer)) != -1) {
out.write(buffer, 0, bytesRead);
}
} catch (IOException e) {
e.printStackTrace();
}
实际开发技巧:使用try-with-resources语句可以自动关闭流,避免资源泄漏。这是Java 7引入的特性,比传统的try-catch-finally更简洁安全。
2.2 使用字符流读写文本文件
对于文本文件,使用字符流更为合适:
java复制// 文本文件读取示例
try (BufferedReader reader = new BufferedReader(new FileReader("text.txt"))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}
// 文本文件写入示例
try (BufferedWriter writer = new BufferedWriter(new FileWriter("output.txt"))) {
writer.write("Hello, Java I/O!");
writer.newLine();
writer.write("这是第二行");
} catch (IOException e) {
e.printStackTrace();
}
2.3 处理文件编码问题
字符流的一个重要优势是可以指定字符编码:
java复制// 指定UTF-8编码读取文件
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(
new FileInputStream("text.txt"), StandardCharsets.UTF_8))) {
// 读取内容
} catch (IOException e) {
e.printStackTrace();
}
常见的编码问题包括:
- 读取文件时未指定正确编码导致乱码
- 不同平台默认编码不一致(Windows通常是GBK,Linux/Mac通常是UTF-8)
- 文件保存编码与读取编码不一致
避坑指南:始终明确指定字符编码,推荐使用UTF-8。不要依赖平台默认编码,否则可能导致跨平台兼容性问题。
3. 高级I/O操作技巧
掌握了基本的文件读写后,我们来看一些更高级的I/O操作技巧。
3.1 使用缓冲提高性能
Java提供了缓冲流来减少实际I/O操作次数:
java复制// 使用缓冲流复制文件(性能更好)
try (BufferedInputStream in = new BufferedInputStream(new FileInputStream("source.jpg"));
BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream("target.jpg"))) {
byte[] buffer = new byte[8192]; // 8KB缓冲区
int bytesRead;
while ((bytesRead = in.read(buffer)) != -1) {
out.write(buffer, 0, bytesRead);
}
} catch (IOException e) {
e.printStackTrace();
}
缓冲流默认使用8KB的缓冲区,但可以根据实际情况调整大小。对于大文件操作,使用缓冲流可以显著提高性能。
3.2 对象序列化
Java的对象序列化机制允许将对象转换为字节流:
java复制// 对象序列化示例
class Person implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private int age;
// 构造方法、getter/setter省略
}
// 序列化对象到文件
try (ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream("person.dat"))) {
Person person = new Person("张三", 25);
oos.writeObject(person);
} catch (IOException e) {
e.printStackTrace();
}
// 从文件反序列化对象
try (ObjectInputStream ois = new ObjectInputStream(
new FileInputStream("person.dat"))) {
Person person = (Person) ois.readObject();
System.out.println(person.getName() + ", " + person.getAge());
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
注意事项:
- 序列化的类必须实现Serializable接口
- 建议显式声明serialVersionUID字段
- 敏感数据不应序列化(可标记为transient)
- 序列化有性能开销,不适合大数据量场景
3.3 NIO新特性
Java 1.4引入了NIO(New I/O)包,提供了更高效的I/O操作方式:
java复制// 使用NIO复制文件
try (FileChannel inChannel = new FileInputStream("source.txt").getChannel();
FileChannel outChannel = new FileOutputStream("target.txt").getChannel()) {
inChannel.transferTo(0, inChannel.size(), outChannel);
} catch (IOException e) {
e.printStackTrace();
}
// 使用Files工具类(Java 7+)
try {
Files.copy(Paths.get("source.txt"), Paths.get("target.txt"),
StandardCopyOption.REPLACE_EXISTING);
} catch (IOException e) {
e.printStackTrace();
}
NIO的主要优势:
- 通道(Channel)和缓冲区(Buffer)机制
- 非阻塞I/O支持
- 文件锁功能
- 内存映射文件
4. 常见问题与解决方案
在实际开发中,Java I/O会遇到各种问题。下面总结一些常见问题及其解决方案。
4.1 文件路径问题
java复制// 相对路径与绝对路径
File relativeFile = new File("data.txt"); // 相对于当前工作目录
File absoluteFile = new File("/path/to/data.txt");
// 跨平台路径处理
String path = Paths.get("dir", "subdir", "file.txt").toString();
// 在Windows上输出 dir\subdir\file.txt
// 在Linux/Mac上输出 dir/subdir/file.txt
常见路径问题:
- 相对路径的基准目录不明确
- 路径分隔符在不同系统上不一致
- 路径中包含特殊字符
解决方案:使用Paths工具类构建路径,或者明确使用File.separator获取系统分隔符。
4.2 资源泄漏问题
传统方式关闭资源:
java复制InputStream in = null;
try {
in = new FileInputStream("data.txt");
// 使用流
} catch (IOException e) {
e.printStackTrace();
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
使用try-with-resources简化:
java复制try (InputStream in = new FileInputStream("data.txt")) {
// 使用流
} catch (IOException e) {
e.printStackTrace();
}
资源泄漏是Java I/O中最常见的问题之一,可能导致文件句柄耗尽等严重问题。
4.3 大文件处理技巧
处理大文件时需要特别注意内存使用:
java复制// 逐行处理大文本文件
try (BufferedReader reader = new BufferedReader(new FileReader("large.txt"))) {
String line;
while ((line = reader.readLine()) != null) {
// 处理每一行
}
} catch (IOException e) {
e.printStackTrace();
}
// 使用NIO处理大文件
try (FileChannel channel = new FileInputStream("large.dat").getChannel()) {
ByteBuffer buffer = ByteBuffer.allocateDirect(8192); // 直接缓冲区
while (channel.read(buffer) != -1) {
buffer.flip();
// 处理缓冲区数据
buffer.clear();
}
} catch (IOException e) {
e.printStackTrace();
}
大文件处理的关键点:
- 避免一次性加载整个文件到内存
- 使用合适的缓冲区大小
- 考虑使用内存映射文件(MappedByteBuffer)
- 对于超大文件,可能需要分片处理
4.4 文件锁机制
Java提供了文件锁功能,可以防止多个进程同时修改同一个文件:
java复制try (RandomAccessFile raf = new RandomAccessFile("data.txt", "rw");
FileChannel channel = raf.getChannel();
FileLock lock = channel.lock()) {
// 独占访问文件
// 执行文件操作
} catch (IOException e) {
e.printStackTrace();
}
文件锁有两种类型:
- 独占锁:阻止其他进程获取任何类型的锁
- 共享锁:允许其他进程获取共享锁,但阻止获取独占锁
注意事项:文件锁的行为在不同操作系统上可能有所不同,特别是锁的范围(整个文件还是文件区域)和锁的释放时机。
