Java I/O(输入/输出)系统提供了强大的数据读写能力,支持从各种数据源(文件、网络、内存等)进行数据传输。本教程将系统介绍Java I/O体系,包括基础流类、NIO、文件操作和实用技巧。
一、I/O流基础概念
1. 流的概念
流(Stream)是Java I/O的核心抽象,代表数据的流动:
- 方向:输入流(读取数据)、输出流(写入数据)
- 单位:字节流(8位字节)、字符流(16位Unicode字符)
- 功能:节点流(直接连接数据源)、处理流(包装其他流增强功能)
2. 核心类层次结构
字节流:
InputStream (抽象类)
├── FileInputStream
├── ByteArrayInputStream
├── PipedInputStream
├── FilterInputStream
│ ├── BufferedInputStream
│ ├── DataInputStream
│ └── PushbackInputStream
└── ObjectInputStream
OutputStream (抽象类)
├── FileOutputStream
├── ByteArrayOutputStream
├── PipedOutputStream
├── FilterOutputStream
│ ├── BufferedOutputStream
│ ├── DataOutputStream
│ └── PrintStream
└── ObjectOutputStream
字符流:
Reader (抽象类)
├── InputStreamReader
│ └── FileReader
├── BufferedReader
├── CharArrayReader
└── StringReader
Writer (抽象类)
├── OutputStreamWriter
│ └── FileWriter
├── BufferedWriter
├── PrintWriter
├── CharArrayWriter
└── StringWriter
二、文件读写操作
1. 传统IO文件操作
字节流文件复制:
public static void copyFile(String src, String dest) throws IOException {
try (InputStream in = new FileInputStream(src);
OutputStream out = new FileOutputStream(dest)) {
byte[] buffer = new byte[1024];
int length;
while ((length = in.read(buffer)) > 0) {
out.write(buffer, 0, length);
}
}
}
字符流读取文本文件:
public static String readTextFile(String filename) throws IOException {
StringBuilder content = new StringBuilder();
try (BufferedReader reader = new BufferedReader(new FileReader(filename))) {
String line;
while ((line = reader.readLine()) != null) {
content.append(line).append("\n");
}
}
return content.toString();
}
2. NIO文件操作(Java 7+)
Files工具类:
// 读取所有行
List<String> lines = Files.readAllLines(Paths.get("file.txt"), StandardCharsets.UTF_8);
// 写入文件
Files.write(Paths.get("output.txt"), content.getBytes(), StandardOpenOption.CREATE);
// 文件复制
Files.copy(Paths.get("src.txt"), Paths.get("dest.txt"), StandardCopyOption.REPLACE_EXISTING);
// 获取文件属性
long size = Files.size(Paths.get("file.txt"));
boolean isHidden = Files.isHidden(Paths.get("file.txt"));
FileChannel高效文件复制:
public static void fastCopy(String src, String dest) throws IOException {
try (FileChannel inChannel = FileChannel.open(Paths.get(src), StandardOpenOption.READ);
FileChannel outChannel = FileChannel.open(Paths.get(dest),
StandardOpenOption.WRITE, StandardOpenOption.CREATE)) {
inChannel.transferTo(0, inChannel.size(), outChannel);
}
}
三、常见流操作示例
1. 缓冲流使用
// 缓冲字节流
try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream("input.dat"));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("output.dat"))) {
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = bis.read(buffer)) != -1) {
bos.write(buffer, 0, bytesRead);
}
}
// 缓冲字符流
try (BufferedReader reader = new BufferedReader(new FileReader("input.txt"));
BufferedWriter writer = new BufferedWriter(new FileWriter("output.txt"))) {
String line;
while ((line = reader.readLine()) != null) {
writer.write(line);
writer.newLine();
}
}
2. 数据流操作基本数据类型
// 写入基本数据类型
try (DataOutputStream dos = new DataOutputStream(new FileOutputStream("data.bin"))) {
dos.writeInt(100);
dos.writeDouble(3.14);
dos.writeBoolean(true);
dos.writeUTF("Hello");
}
// 读取基本数据类型
try (DataInputStream dis = new DataInputStream(new FileInputStream("data.bin"))) {
int i = dis.readInt();
double d = dis.readDouble();
boolean b = dis.readBoolean();
String s = dis.readUTF();
System.out.printf("%d, %f, %b, %s", i, d, b, s);
}
3. 对象序列化
class Person implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private transient int age; // 不会被序列化
// 构造方法、getter/setter
}
// 序列化对象
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("person.dat"))) {
oos.writeObject(new Person("张三", 25));
}
// 反序列化对象
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("person.dat"))) {
Person p = (Person) ois.readObject();
System.out.println(p.getName()); // 张三
System.out.println(p.getAge()); // 0 (transient字段)
}
四、NIO通道与缓冲区
1. Buffer基本操作
// 创建缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024); // 堆缓冲区
// ByteBuffer.allocateDirect(1024); // 直接缓冲区(更高性能)
// 写入数据
buffer.put("Hello".getBytes());
// 切换为读模式
buffer.flip();
// 读取数据
byte[] data = new byte[buffer.limit()];
buffer.get(data);
System.out.println(new String(data)); // Hello
// 清空缓冲区(可重用)
buffer.clear();
2. Channel通信示例
文件Channel:
try (RandomAccessFile raf = new RandomAccessFile("data.txt", "rw");
FileChannel channel = raf.getChannel()) {
// 写入
ByteBuffer writeBuffer = ByteBuffer.wrap("New Content".getBytes());
channel.write(writeBuffer);
// 读取
ByteBuffer readBuffer = ByteBuffer.allocate(1024);
channel.position(0); // 重置位置
channel.read(readBuffer);
readBuffer.flip();
byte[] data = new byte[readBuffer.limit()];
readBuffer.get(data);
System.out.println(new String(data));
}
Socket Channel:
// 服务端
try (ServerSocketChannel serverChannel = ServerSocketChannel.open()) {
serverChannel.bind(new InetSocketAddress(8080));
SocketChannel clientChannel = serverChannel.accept();
ByteBuffer buffer = ByteBuffer.allocate(1024);
clientChannel.read(buffer);
buffer.flip();
clientChannel.write(ByteBuffer.wrap("Response".getBytes()));
}
// 客户端
try (SocketChannel channel = SocketChannel.open(new InetSocketAddress("localhost", 8080))) {
channel.write(ByteBuffer.wrap("Request".getBytes()));
ByteBuffer buffer = ByteBuffer.allocate(1024);
channel.read(buffer);
buffer.flip();
System.out.println(new String(buffer.array(), 0, buffer.limit()));
}
五、实用工具与技巧
1. 使用Scanner读取输入
try (Scanner scanner = new Scanner(new File("input.txt"))) {
while (scanner.hasNextLine()) {
String line = scanner.nextLine();
// 处理每行内容
}
}
// 从控制台读取
Scanner consoleScanner = new Scanner(System.in);
System.out.print("请输入: ");
String input = consoleScanner.nextLine();
2. 使用Files工具类简化操作
// 读取小文件
byte[] bytes = Files.readAllBytes(Paths.get("small.bin"));
String text = Files.readString(Paths.get("text.txt"), StandardCharsets.UTF_8);
// 遍历目录
Files.walk(Paths.get("/path/to/dir"))
.filter(Files::isRegularFile)
.forEach(System.out::println);
// 监视目录变化
WatchService watcher = FileSystems.getDefault().newWatchService();
Path dir = Paths.get("/path/to/watch");
dir.register(watcher, StandardWatchEventKinds.ENTRY_MODIFY);
while (true) {
WatchKey key = watcher.take();
for (WatchEvent<?> event : key.pollEvents()) {
System.out.println("文件修改: " + event.context());
}
key.reset();
}
3. 内存流操作
// 字节内存流
ByteArrayOutputStream baos = new ByteArrayOutputStream();
baos.write("Hello".getBytes());
byte[] data = baos.toByteArray();
ByteArrayInputStream bais = new ByteArrayInputStream(data);
int ch;
while ((ch = bais.read()) != -1) {
System.out.print((char)ch);
}
// 字符内存流
StringWriter sw = new StringWriter();
sw.write("Hello");
String content = sw.toString();
StringReader sr = new StringReader(content);
// 读取操作...
六、性能优化建议
- 使用缓冲流:总是包装FileInputStream/FileOutputStream
- 合理设置缓冲区大小:通常8KB-32KB效果最佳
- NIO提升性能:大文件操作使用FileChannel
- 直接缓冲区:频繁IO操作使用ByteBuffer.allocateDirect()
- 及时关闭资源:使用try-with-resources确保资源释放
- 减少系统调用:批量读写而非单字节操作
- 异步NIO:高并发场景使用AsynchronousFileChannel
七、常见问题解决方案
1. 中文乱码问题
// 指定字符集读写
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(new FileInputStream("file.txt"), "GBK"))) {
// 读取GBK编码文件
}
try (BufferedWriter writer = new BufferedWriter(
new OutputStreamWriter(new FileOutputStream("file.txt"), StandardCharsets.UTF_8))) {
// 写入UTF-8文件
}
2. 大文件处理
// 分块读取大文件
try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream("large.bin"))) {
byte[] buffer = new byte[8192]; // 8KB缓冲区
int bytesRead;
while ((bytesRead = bis.read(buffer)) != -1) {
// 处理每个块
}
}
// 使用内存映射文件(超大文件)
try (RandomAccessFile raf = new RandomAccessFile("huge.bin", "r");
FileChannel channel = raf.getChannel()) {
MappedByteBuffer mbb = channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size());
// 直接操作内存映射区域
}
3. 文件锁定
// 独占锁
try (FileOutputStream fos = new FileOutputStream("locked.txt");
FileLock lock = fos.getChannel().lock()) {
// 执行排他操作
}
// 共享锁
try (RandomAccessFile raf = new RandomAccessFile("locked.txt", "rw");
FileLock lock = raf.getChannel().lock(0, Long.MAX_VALUE, true)) {
// 执行共享读取
}
八、Java I/O发展路线
- Java 1.0:基本InputStream/OutputStream体系
- Java 1.1:引入Reader/Writer字符流
- Java 1.4:NIO (New I/O) 通道和缓冲区
- Java 7:NIO.2 (Path, Files, WatchService)
- Java 11:直接添加读写文件的便捷方法
九、总结
Java I/O系统提供了丰富的API来处理各种数据源:
- 基础流类:适合简单IO操作
- 字节流:InputStream/OutputStream体系
- 字符流:Reader/Writer体系
- 装饰器模式:BufferedXXX、DataXXX等
- NIO:高性能IO操作
- Channel和Buffer机制
- 非阻塞IO支持
- 内存映射文件
- NIO.2:现代文件操作
- Path接口替代File类
- Files工具类简化常见操作
- 目录监视服务
- 最佳实践:
- 总是使用缓冲流
- 确保资源释放(try-with-resources)
- 大文件使用NIO处理
- 注意字符编码问题
掌握Java I/O系统需要理解不同场景下的适用API,并根据性能需求选择合适的实现方式。对于现代Java开发,建议优先考虑NIO.2的Files和Paths工具类,它们在简洁性和功能性上都有显著优势。