Java I/O流操作全面教程


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);
// 读取操作...

六、性能优化建议

  1. 使用缓冲流:总是包装FileInputStream/FileOutputStream
  2. 合理设置缓冲区大小:通常8KB-32KB效果最佳
  3. NIO提升性能:大文件操作使用FileChannel
  4. 直接缓冲区:频繁IO操作使用ByteBuffer.allocateDirect()
  5. 及时关闭资源:使用try-with-resources确保资源释放
  6. 减少系统调用:批量读写而非单字节操作
  7. 异步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发展路线

  1. Java 1.0:基本InputStream/OutputStream体系
  2. Java 1.1:引入Reader/Writer字符流
  3. Java 1.4:NIO (New I/O) 通道和缓冲区
  4. Java 7:NIO.2 (Path, Files, WatchService)
  5. Java 11:直接添加读写文件的便捷方法

九、总结

Java I/O系统提供了丰富的API来处理各种数据源:

  1. 基础流类:适合简单IO操作
  • 字节流:InputStream/OutputStream体系
  • 字符流:Reader/Writer体系
  • 装饰器模式:BufferedXXX、DataXXX等
  1. NIO:高性能IO操作
  • Channel和Buffer机制
  • 非阻塞IO支持
  • 内存映射文件
  1. NIO.2:现代文件操作
  • Path接口替代File类
  • Files工具类简化常见操作
  • 目录监视服务
  1. 最佳实践
  • 总是使用缓冲流
  • 确保资源释放(try-with-resources)
  • 大文件使用NIO处理
  • 注意字符编码问题

掌握Java I/O系统需要理解不同场景下的适用API,并根据性能需求选择合适的实现方式。对于现代Java开发,建议优先考虑NIO.2的Files和Paths工具类,它们在简洁性和功能性上都有显著优势。

,

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注