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的区别?
答案:
| 特性 | BIO | NIO | AIO |
|---|---|---|---|
| 全称 | Blocking IO | Non-blocking IO | Asynchronous 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);练习题
- 字节流和字符流的区别?什么时候用字节流,什么时候用字符流?
- 为什么要使用缓冲流?
- 如何实现大文件的高效复制?
- NIO的零拷贝是什么?