Java中的I/O流(输入/输出流)是处理输入和输出数据的机制。I/O流提供了通过数据源(如文件、网络、内存等)读取和写入数据的标准方法。Java的I/O流分为两大类:字节流(Byte Stream)和字符流(Character Stream)。字节流处理原始的二进制数据,而字符流则处理文本数据。以下是详细介绍和示例。
一、字节流(Byte Stream)
字节流用于处理8位字节的数据。它们适合于任何类型的I/O操作,如读写图像、音频等。字节流包括两个基本的抽象类:
- InputStream:用于读取字节的抽象类。
- OutputStream:用于写入字节的抽象类。
1.1 FileInputStream 和 FileOutputStream
FileInputStream用于从文件中读取字节数据,而FileOutputStream用于将字节数据写入文件。
示例:将文件内容从一个文件复制到另一个文件
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class ByteStreamExample {
public static void main(String[] args) {
// 输入文件路径和输出文件路径
String inputFile = "input.txt";
String outputFile = "output.txt";
// 声明FileInputStream和FileOutputStream对象
FileInputStream fis = null;
FileOutputStream fos = null;
try {
// 初始化FileInputStream对象用于读取文件
fis = new FileInputStream(inputFile);
// 初始化FileOutputStream对象用于写入文件
fos = new FileOutputStream(outputFile);
// 读取的字节数据
int byteData;
// 循环读取文件内容,直到文件结束
while ((byteData = fis.read()) != -1) {
// 将读取到的字节数据写入输出文件
fos.write(byteData);
}
System.out.println("文件复制完成!");
} catch (IOException e) {
// 捕获并处理IO异常
e.printStackTrace();
} finally {
try {
// 关闭输入流
if (fis != null) {
fis.close();
}
// 关闭输出流
if (fos != null) {
fos.close();
}
} catch (IOException e) {
// 捕获并处理关闭流时的IO异常
e.printStackTrace();
}
}
}
}
二、字符流(Character Stream)
字符流用于处理16位的Unicode字符。它们适合处理文本文件。字符流也包括两个基本的抽象类:
- Reader:用于读取字符的抽象类。
- Writer:用于写入字符的抽象类。
2.1 FileReader 和 FileWriter
FileReader用于从文件中读取字符数据,而FileWriter用于将字符数据写入文件。
示例:将文件内容从一个文件复制到另一个文件
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public class CharStreamExample {
public static void main(String[] args) {
// 输入文件路径和输出文件路径
String inputFile = "input.txt";
String outputFile = "output.txt";
// 声明FileReader和FileWriter对象
FileReader fr = null;
FileWriter fw = null;
try {
// 初始化FileReader对象用于读取文件
fr = new FileReader(inputFile);
// 初始化FileWriter对象用于写入文件
fw = new FileWriter(outputFile);
// 读取的字符数据
int charData;
// 循环读取文件内容,直到文件结束
while ((charData = fr.read()) != -1) {
// 将读取到的字符数据写入输出文件
fw.write(charData);
}
System.out.println("文件复制完成!");
} catch (IOException e) {
// 捕获并处理IO异常
e.printStackTrace();
} finally {
try {
// 关闭读取流
if (fr != null) {
fr.close();
}
// 关闭写入流
if (fw != null) {
fw.close();
}
} catch (IOException e) {
// 捕获并处理关闭流时的IO异常
e.printStackTrace();
}
}
}
}
三、缓冲流(Buffered Stream)
缓冲流提供了对原始流的包装,使得读写操作更为高效。缓冲流通常包括:
- BufferedInputStream 和 BufferedOutputStream:为字节流提供缓冲功能。
- BufferedReader 和 BufferedWriter:为字符流提供缓冲功能。
示例:使用缓冲流进行文件复制
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public class BufferedStreamExample {
public static void main(String[] args) {
// 输入文件路径和输出文件路径
String inputFile = "input.txt";
String outputFile = "output.txt";
// 声明BufferedReader和BufferedWriter对象
BufferedReader br = null;
BufferedWriter bw = null;
try {
// 初始化BufferedReader对象用于读取文件
br = new BufferedReader(new FileReader(inputFile));
// 初始化BufferedWriter对象用于写入文件
bw = new BufferedWriter(new FileWriter(outputFile));
// 读取的文本行数据
String line;
// 循环读取文件内容,直到文件结束
while ((line = br.readLine()) != null) {
// 将读取到的文本行写入输出文件
bw.write(line);
// 换行符
bw.newLine();
}
System.out.println("文件复制完成!");
} catch (IOException e) {
// 捕获并处理IO异常
e.printStackTrace();
} finally {
try {
// 关闭读取流
if (br != null) {
br.close();
}
// 关闭写入流
if (bw != null) {
bw.close();
}
} catch (IOException e) {
// 捕获并处理关闭流时的IO异常
e.printStackTrace();
}
}
}
}
四、标准I/O
Java还提供了对标准输入(System.in)、标准输出(System.out)、和标准错误(System.err)的支持,通常用于命令行操作。
示例:从标准输入读取数据并打印到标准输出
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class StandardIOExample {
public static void main(String[] args) {
// 使用BufferedReader和InputStreamReader从标准输入读取数据
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
try {
System.out.println("请输入一些文字:");
// 读取用户输入的文本行
String input = reader.readLine();
// 打印输入的文本行到标准输出
System.out.println("您输入的是:" + input);
} catch (IOException e) {
// 捕获并处理IO异常
e.printStackTrace();
} finally {
try {
// 关闭读取流
if (reader != null) {
reader.close();
}
} catch (IOException e) {
// 捕获并处理关闭流时的IO异常
e.printStackTrace();
}
}
}
}
五、对象流(Object Stream)
对象流用于读写Java对象,它可以将对象写入流中或从流中读取对象。对象流包括:
- ObjectInputStream:用于反序列化对象(将流中的字节转换为对象)。
- ObjectOutputStream:用于序列化对象(将对象转换为字节并写入流中)。
对象流常用于保存对象的状态到文件或通过网络传输对象。
5.1 ObjectOutputStream 和 ObjectInputStream
这些类可以将一个对象写入文件(序列化),或从文件中读取对象(反序列化)。
示例:将对象保存到文件并从文件中读取对象
import java.io.*;
// 创建一个可序列化的简单类
class Person implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person{name='" + name + "', age=" + age + "}";
}
}
public class ObjectStreamExample {
public static void main(String[] args) {
// 定义一个Person对象
Person person = new Person("Alice", 30);
// 文件路径
String filename = "person.dat";
// 序列化:将对象写入文件
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filename))) {
// 将Person对象写入文件
oos.writeObject(person);
System.out.println("对象已序列化并保存到文件:" + filename);
} catch (IOException e) {
// 捕获并处理IO异常
e.printStackTrace();
}
// 反序列化:从文件读取对象
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename))) {
// 从文件中读取Person对象
Person savedPerson = (Person) ois.readObject();
System.out.println("从文件中读取的对象:" + savedPerson);
} catch (IOException | ClassNotFoundException e) {
// 捕获并处理IO异常和类未找到异常
e.printStackTrace();
}
}
}
5.2 注意事项
对象的可序列化性:类必须实现Serializable接口,否则将抛出NotSerializableException异常。
- serialVersionUID:用于确保反序列化时类的一致性。如果类定义发生改变且未指定serialVersionUID,可能会导致反序列化失败。
六、Java NIO(New I/O)
Java NIO (New I/O) 是Java 1.4引入的一个高级I/O API,它与传统的I/O流相比提供了更高的效率和更强的功能。NIO主要通过缓冲区(Buffer)、**通道(Channel)和选择器(Selector)**等核心概念来实现非阻塞的I/O操作。
6.1 缓冲区(Buffer)
缓冲区是一个用于读写数据的容器。与传统I/O中的直接读写操作不同,NIO通过缓冲区进行数据交换。缓冲区可以理解为一个数组,但它除了数据存储功能外,还提供了对数据读写操作的管理,如位置、限制、容量等。
示例:使用ByteBuffer操作数据
import java.nio.ByteBuffer;
public class BufferExample {
public static void main(String[] args) {
// 分配一个容量为10字节的缓冲区
ByteBuffer buffer = ByteBuffer.allocate(10);
// 写入数据到缓冲区
for (int i = 0; i < buffer.capacity(); i++) {
buffer.put((byte) i);
}
// 切换为读取模式
buffer.flip();
// 读取数据并打印
while (buffer.hasRemaining()) {
System.out.print(buffer.get() + " ");
}
}
}
关键点解释:
- allocate(int capacity):分配一个指定容量的缓冲区。
- put(byte b):将数据写入缓冲区。
- flip():切换缓冲区为读取模式。
- get():从缓冲区读取数据。
6.2 通道(Channel)
通道类似于传统I/O中的流,但它可以双向操作,即既可以读也可以写。常见的通道包括FileChannel、SocketChannel、ServerSocketChannel等。通道通常与缓冲区结合使用,以实现高效的数据传输。
示例:使用FileChannel复制文件
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
public class ChannelExample {
public static void main(String[] args) {
try (
// 创建文件输入流和输出流
FileInputStream fis = new FileInputStream("input.txt");
FileOutputStream fos = new FileOutputStream("output.txt");
// 获取输入和输出通道
FileChannel inputChannel = fis.getChannel();
FileChannel outputChannel = fos.getChannel()
) {
// 分配缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
// 读取数据到缓冲区
while (inputChannel.read(buffer) != -1) {
// 切换缓冲区为读取模式
buffer.flip();
// 将数据从缓冲区写入输出通道
outputChannel.write(buffer);
// 清空缓冲区以准备下一次读操作
buffer.clear();
}
System.out.println("文件复制完成!");
} catch (Exception e) {
// 捕获并处理异常
e.printStackTrace();
}
}
}
关键点解释:
- getChannel():从文件输入流/输出流中获取通道。
- read(ByteBuffer buffer):将数据从通道读入缓冲区。
- write(ByteBuffer buffer):将缓冲区的数据写入通道。
- clear():清空缓冲区,以准备下次读取数据。
6.3 选择器(Selector)
选择器是NIO中的核心组件之一,允许单线程监控多个通道的事件(如读、写、连接等),从而实现非阻塞I/O操作。选择器特别适合处理大量连接的网络应用程序。
示例:使用Selector进行非阻塞I/O操作
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
public class SelectorExample {
public static void main(String[] args) throws IOException {
// 打开ServerSocketChannel和Selector
ServerSocketChannel serverChannel = ServerSocketChannel.open();
Selector selector = Selector.open();
// 绑定服务器端口并设置为非阻塞模式
serverChannel.bind(new InetSocketAddress(8080));
serverChannel.configureBlocking(false);
// 注册通道到选择器,并关注接受连接事件
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("服务器启动,等待连接...");
while (true) {
// 选择已准备好的通道
selector.select();
// 获取选择的键集合
Iterator<SelectionKey> keyIterator = selector.selectedKeys().iterator();
while (keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
// 检查是否有新的连接
if (key.isAcceptable()) {
// 接受新的连接
SocketChannel clientChannel = serverChannel.accept();
clientChannel.configureBlocking(false);
// 注册新连接的通道到选择器,并关注读事件
clientChannel.register(selector, SelectionKey.OP_READ);
System.out.println("新的连接已接受:" + clientChannel.getRemoteAddress());
} else if (key.isReadable()) {
// 读取客户端数据
SocketChannel clientChannel = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(256);
clientChannel.read(buffer);
String request = new String(buffer.array()).trim();
System.out.println("收到消息:" + request);
// 响应客户端
buffer.flip();
clientChannel.write(ByteBuffer.wrap("响应消息".getBytes()));
// 关闭连接
clientChannel.close();
}
// 移除已处理的键
keyIterator.remove();
}
}
}
}
关键点解释:
- open():打开通道或选择器。
- configureBlocking(false):设置通道为非阻塞模式。
- register(Selector selector, int ops):将通道注册到选择器并指定要监听的事件类型。
- select():选择已经准备好的通道。
- selectedKeys():获取已选择的键集合,代表通道已准备好的操作。
七、小结
Java的I/O流提供了灵活的读写数据方式,适用于各种类型的输入输出操作。字节流用于处理二进制数据,字符流则适合处理文本数据,而缓冲流则提高了I/O操作的效率。在实际开发中,根据具体需求选择合适的流类型,可以显著提高程序的性能和可靠性。