Skip to content

IO流

IO流分类

Java IO流的分类?

答案:

按流向分类

  • 输入流(Input):读取数据
  • 输出流(Output):写入数据

按数据类型分类

  • 字节流(Byte):处理二进制数据(图片、视频等)
  • 字符流(Character):处理文本数据
字节流
├── InputStream(输入)
│   ├── FileInputStream
│   ├── BufferedInputStream
│   ├── DataInputStream
│   └── ObjectInputStream
└── OutputStream(输出)
    ├── FileOutputStream
    ├── BufferedOutputStream
    ├── DataOutputStream
    └── ObjectOutputStream

字符流
├── Reader(输入)
│   ├── FileReader
│   ├── BufferedReader
│   └── InputStreamReader
└── Writer(输出)
    ├── FileWriter
    ├── BufferedWriter
    └── OutputStreamWriter

字节流

字节流的使用?

答案:

FileInputStream/FileOutputStream

java
// 读取文件
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 (FileOutputStream fos = new FileOutputStream("output.txt")) {
    String content = "Hello World";
    fos.write(content.getBytes());
} catch (IOException e) {
    e.printStackTrace();
}

BufferedInputStream/BufferedOutputStream(带缓冲,性能更好):

java
// 复制文件
try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream("source.jpg"));
     BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("target.jpg"))) {

    byte[] buffer = new byte[1024];
    int len;
    while ((len = bis.read(buffer)) != -1) {
        bos.write(buffer, 0, len);
    }
} catch (IOException e) {
    e.printStackTrace();
}

字符流

字符流的使用?

答案:

FileReader/FileWriter

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

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

BufferedReader/BufferedWriter(推荐):

java
// 按行读取
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();
}

// 按行写入
try (BufferedWriter bw = new BufferedWriter(new FileWriter("output.txt"))) {
    bw.write("第一行");
    bw.newLine();  // 换行
    bw.write("第二行");
} catch (IOException e) {
    e.printStackTrace();
}

转换流

字节流和字符流如何转换?

答案:

InputStreamReader:字节流 → 字符流

java
// 指定编码读取
try (InputStreamReader isr = new InputStreamReader(
        new FileInputStream("input.txt"), "UTF-8");
     BufferedReader br = new BufferedReader(isr)) {

    String line;
    while ((line = br.readLine()) != null) {
        System.out.println(line);
    }
} catch (IOException e) {
    e.printStackTrace();
}

OutputStreamWriter:字符流 → 字节流

java
// 指定编码写入
try (OutputStreamWriter osw = new OutputStreamWriter(
        new FileOutputStream("output.txt"), "UTF-8");
     BufferedWriter bw = new BufferedWriter(osw)) {

    bw.write("中文内容");
} catch (IOException e) {
    e.printStackTrace();
}

对象流

如何实现对象的序列化?

答案: 序列化:将对象转换为字节流,用于存储或网络传输。

实现Serializable接口

java
public class User implements Serializable {
    private static final long serialVersionUID = 1L;  // 版本号

    private String name;
    private int age;
    private transient String password;  // transient不序列化

    // getter/setter
}

ObjectOutputStream/ObjectInputStream

java
// 序列化(写入)
try (ObjectOutputStream oos = new ObjectOutputStream(
        new FileOutputStream("user.dat"))) {
    User user = new User("Tom", 20);
    oos.writeObject(user);
} catch (IOException e) {
    e.printStackTrace();
}

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

serialVersionUID的作用?

答案: 用于验证序列化对象的版本一致性。

问题

java
// 序列化时
class User implements Serializable {
    String name;
}

// 反序列化时,类结构改变
class User implements Serializable {
    String name;
    int age;  // 新增字段
}
// 如果没有serialVersionUID,会抛出InvalidClassException

解决

java
private static final long serialVersionUID = 1L;

NIO

BIO、NIO、AIO的区别?

答案:

特性BIONIOAIO
全称Blocking IONon-blocking IOAsynchronous IO
方式同步阻塞同步非阻塞异步非阻塞
线程一个连接一个线程一个线程处理多个连接回调机制
适用连接数少连接数多连接数多且耗时长

BIO示例

java
ServerSocket server = new ServerSocket(8080);
while (true) {
    Socket socket = server.accept();  // 阻塞等待
    new Thread(() -> {
        // 处理请求
    }).start();
}

NIO示例

java
Selector selector = Selector.open();
ServerSocketChannel server = ServerSocketChannel.open();
server.bind(new InetSocketAddress(8080));
server.configureBlocking(false);
server.register(selector, SelectionKey.OP_ACCEPT);

while (true) {
    selector.select();  // 阻塞等待事件
    Set<SelectionKey> keys = selector.selectedKeys();
    for (SelectionKey key : keys) {
        if (key.isAcceptable()) {
            // 处理连接
        } else if (key.isReadable()) {
            // 处理读取
        }
    }
    keys.clear();
}

NIO的核心组件?

答案:

1. Channel(通道)

  • 双向的,可读可写
  • 常用:FileChannel、SocketChannel、ServerSocketChannel

2. Buffer(缓冲区)

  • 存储数据的容器
  • 常用:ByteBuffer、CharBuffer、IntBuffer

3. Selector(选择器)

  • 多路复用器,监听多个Channel的事件
java
// FileChannel示例
try (RandomAccessFile file = new RandomAccessFile("data.txt", "rw");
     FileChannel channel = file.getChannel()) {

    // 读取
    ByteBuffer buffer = ByteBuffer.allocate(1024);
    int bytesRead = channel.read(buffer);

    // 切换到读模式
    buffer.flip();

    while (buffer.hasRemaining()) {
        System.out.print((char) buffer.get());
    }

    // 清空缓冲区
    buffer.clear();

    // 写入
    buffer.put("Hello NIO".getBytes());
    buffer.flip();
    channel.write(buffer);

} catch (IOException e) {
    e.printStackTrace();
}

Buffer的核心属性?

答案:

java
// 核心属性
capacity  // 容量
position  // 当前位置
limit     // 限制
mark      // 标记

// 核心方法
flip()    // 切换到读模式:limit=position, position=0
clear()   // 清空:position=0, limit=capacity
rewind()  // 重读:position=0
compact() // 压缩:未读数据移到开头

状态变化

初始状态:position=0, limit=capacity
写入数据:position增加
flip():  limit=position, position=0(准备读取)
读取数据:position增加
clear(): position=0, limit=capacity(准备写入)

文件操作

Java 7的Files工具类?

答案:

java
import java.nio.file.*;

// 读取文件
List<String> lines = Files.readAllLines(Paths.get("input.txt"));

// 写入文件
Files.write(Paths.get("output.txt"), "Hello".getBytes());

// 复制文件
Files.copy(Paths.get("source.txt"), Paths.get("target.txt"));

// 移动文件
Files.move(Paths.get("old.txt"), Paths.get("new.txt"));

// 删除文件
Files.delete(Paths.get("file.txt"));

// 创建目录
Files.createDirectory(Paths.get("dir"));

// 遍历目录
try (Stream<Path> paths = Files.walk(Paths.get("."))) {
    paths.filter(Files::isRegularFile)
         .forEach(System.out::println);
}

// 监听文件变化
WatchService watchService = FileSystems.getDefault().newWatchService();
Path path = Paths.get(".");
path.register(watchService, StandardWatchEventKinds.ENTRY_CREATE);

练习题

  1. 字节流和字符流的区别?什么时候用字节流,什么时候用字符流?
  2. 为什么要使用缓冲流?
  3. 如何实现大文件的高效复制?
  4. NIO的零拷贝是什么?

Released under the MIT License.