06-Java IO流

Java IO体系庞大,分为字节流和字符流,输入流和输出流。NIO提供非阻塞IO,适合高性能场景。

IO流体系

字节流(8位)
├── InputStream(输入)
│   ├── FileInputStream
│   ├── ByteArrayInputStream
│   ├── BufferedInputStream
│   └── ObjectInputStream
└── OutputStream(输出)
    ├── FileOutputStream
    ├── ByteArrayOutputStream
    ├── BufferedOutputStream
    └── ObjectOutputStream

字符流(16位,Unicode)
├── Reader(输入)
│   ├── FileReader
│   ├── BufferedReader
│   ├── InputStreamReader
│   └── StringReader
└── Writer(输出)
    ├── FileWriter
    ├── BufferedWriter
    ├── OutputStreamWriter
    └── PrintWriter

文件操作

字节流读写

import java.io.*;

// 写文件
try (FileOutputStream fos = new FileOutputStream("output.txt")) {
    fos.write("Hello".getBytes());
} catch (IOException e) {
    e.printStackTrace();
}

// 读文件
try (FileInputStream fis = new FileInputStream("input.txt")) {
    int data;
    while ((data = fis.read()) != -1) {
        System.out.print((char) data);
    }
} catch (IOException e) {
    e.printStackTrace();
}

// 缓冲流(性能更好)
try (BufferedOutputStream bos = new BufferedOutputStream(
        new FileOutputStream("output.txt"))) {
    bos.write("Buffered write".getBytes());
} catch (IOException e) {
    e.printStackTrace();
}

字符流读写

// 写文件
try (FileWriter fw = new FileWriter("output.txt")) {
    fw.write("Hello, World!");
} catch (IOException e) {
    e.printStackTrace();
}

// 读文件
try (BufferedReader br = new BufferedReader(new FileReader("input.txt"))) {
    String line;
    while ((line = br.readLine()) != null) {
        System.out.println(line);
    }
} catch (IOException e) {
    e.printStackTrace();
}

// PrintWriter(推荐,功能丰富)
try (PrintWriter pw = new PrintWriter("output.txt")) {
    pw.println("Line 1");
    pw.printf("Number: %d\n", 42);
} catch (IOException e) {
    e.printStackTrace();
}

对象序列化

将对象转为字节流,用于持久化或网络传输。

import java.io.*;

// 可序列化类
public class Student implements Serializable {
    private static final long serialVersionUID = 1L;  // 版本号
    
    private String name;
    private int age;
    private transient String password;  // transient字段不序列化
    
    // 构造函数、getter/setter...
}

// 序列化
try (ObjectOutputStream oos = new ObjectOutputStream(
        new FileOutputStream("student.dat"))) {
    Student student = new Student("Alice", 25);
    oos.writeObject(student);
} catch (IOException e) {
    e.printStackTrace();
}

// 反序列化
try (ObjectInputStream ois = new ObjectInputStream(
        new FileInputStream("student.dat"))) {
    Student student = (Student) ois.readObject();
    System.out.println(student.getName());
} catch (IOException | ClassNotFoundException e) {
    e.printStackTrace();
}

注意:

  • 实现 Serializable 接口

  • 定义 serialVersionUID(避免版本不兼容)

  • transient 字段不序列化

  • 静态字段不序列化

Files类(Java 7+)

现代文件操作API,简洁高效。

import java.nio.file.*;
import java.util.List;

// 读取所有行
List<String> lines = Files.readAllLines(Paths.get("file.txt"));

// 读取所有字节
byte[] bytes = Files.readAllBytes(Paths.get("file.txt"));

// 写入
Files.write(Paths.get("output.txt"), "Hello".getBytes());
Files.write(Paths.get("output.txt"), lines);  // 写入行列表

// 追加
Files.write(Paths.get("file.txt"), "Append".getBytes(), 
    StandardOpenOption.APPEND);

// 复制文件
Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING);

// 移动文件
Files.move(source, target, StandardCopyOption.REPLACE_EXISTING);

// 删除文件
Files.delete(path);
Files.deleteIfExists(path);

// 创建目录
Files.createDirectory(path);
Files.createDirectories(path);  // 创建多级目录

// 遍历目录
try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir)) {
    for (Path file : stream) {
        System.out.println(file.getFileName());
    }
}

// 遍历文件树
Files.walk(Paths.get("/path"))
    .filter(Files::isRegularFile)
    .forEach(System.out::println);

// 文件属性
boolean exists = Files.exists(path);
boolean isDir = Files.isDirectory(path);
long size = Files.size(path);
FileTime modified = Files.getLastModifiedTime(path);

NIO(New IO)

非阻塞IO,适合高并发场景。

Buffer和Channel

import java.nio.*;
import java.nio.channels.*;

// 读文件
try (FileChannel channel = FileChannel.open(
        Paths.get("file.txt"), StandardOpenOption.READ)) {
    ByteBuffer buffer = ByteBuffer.allocate(1024);
    
    while (channel.read(buffer) > 0) {
        buffer.flip();  // 切换到读模式
        while (buffer.hasRemaining()) {
            System.out.print((char) buffer.get());
        }
        buffer.clear();  // 清空,准备下次读
    }
} catch (IOException e) {
    e.printStackTrace();
}

// 写文件
try (FileChannel channel = FileChannel.open(
        Paths.get("output.txt"), 
        StandardOpenOption.CREATE, StandardOpenOption.WRITE)) {
    ByteBuffer buffer = ByteBuffer.wrap("Hello NIO".getBytes());
    channel.write(buffer);
} catch (IOException e) {
    e.printStackTrace();
}

Selector(IO多路复用)

一个线程管理多个Channel,实现高并发。

import java.nio.channels.*;

// 创建Selector
Selector selector = Selector.open();

// 注册Channel到Selector
ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.configureBlocking(false);  // 非阻塞
serverChannel.register(selector, SelectionKey.OP_ACCEPT);

// 事件循环
while (true) {
    selector.select();  // 阻塞等待事件
    
    Set<SelectionKey> selectedKeys = selector.selectedKeys();
    Iterator<SelectionKey> it = selectedKeys.iterator();
    
    while (it.hasNext()) {
        SelectionKey key = it.next();
        
        if (key.isAcceptable()) {
            // 新连接
            ServerSocketChannel server = (ServerSocketChannel) key.channel();
            SocketChannel client = server.accept();
            client.configureBlocking(false);
            client.register(selector, SelectionKey.OP_READ);
        } else if (key.isReadable()) {
            // 数据到达
            SocketChannel client = (SocketChannel) key.channel();
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            int bytesRead = client.read(buffer);
            // 处理数据...
        }
        
        it.remove();  // 必须移除,否则重复处理
    }
}

资源管理

try-with-resources(推荐)

自动关闭实现 AutoCloseable 的资源,避免资源泄漏。

// Java 7+
try (BufferedReader br = new BufferedReader(new FileReader("file.txt"));
     BufferedWriter bw = new BufferedWriter(new FileWriter("output.txt"))) {
    String line;
    while ((line = br.readLine()) != null) {
        bw.write(line);
        bw.newLine();
    }
} catch (IOException e) {
    e.printStackTrace();
}
// 自动调用close(),即使发生异常

传统try-finally

BufferedReader br = null;
try {
    br = new BufferedReader(new FileReader("file.txt"));
    String line = br.readLine();
} catch (IOException e) {
    e.printStackTrace();
} finally {
    if (br != null) {
        try {
            br.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

对比: try-with-resources更简洁,自动处理异常抑制。

IO vs NIO

特性

IO

NIO

阻塞

阻塞

非阻塞/阻塞可选

流/缓冲

面向流

面向缓冲区

选择器

Selector

数据处理

单向

双向

线程

一线程一连接

一线程多连接

性能

低并发场景够用

高并发场景优势明显

选择建议:

  • 少量连接:传统IO

  • 大量连接:NIO

  • 文件读写:Files类(最简洁)

  • 高性能服务器:NIO + Selector

  • 简单应用:传统IO + BufferedReader/Writer

最佳实践

  1. 优先Files类:Java 7+文件操作首选

  2. 使用缓冲流:BufferedReader/Writer性能提升明显

  3. try-with-resources:自动关闭资源

  4. 字符流处理文本:Reader/Writer,避免编码问题

  5. 字节流处理二进制:InputStream/OutputStream

  6. NIO用于高并发:Selector实现IO多路复用

  7. 序列化版本号:serialVersionUID避免不兼容

  8. 大文件分块读:避免内存溢出

  9. 异步IO(AIO):Java 7+ AsynchronousFileChannel

  10. 指定编码:避免平台差异,明确UTF-8

核心: 现代Java优先Files类,传统IO用缓冲流,高并发用NIO。