Java的I/O流处理:深入文件读写操作、缓冲流、序列化
在Java编程中,I/O流是处理输入输出操作的基础,特别是在文件读写、网络通信等领域。本文将在前文的基础上,进一步探讨缓冲流、序列化以及NIO(New I/O)在文件读写操作中的应用和原理。
【创作】 不易,【点赞】 是情义,【关注】 是动力,【收藏】 是回忆
代码地址:https://gitee.com/code-in-java/csdn-blog.git
一、缓冲流(Buffering Streams)
缓冲流是对基本I/O流的增强,它通过在内存中创建一个缓冲区来减少对底层I/O设备的直接访问次数,从而提高读写效率。Java提供了BufferedInputStream、BufferedOutputStream、BufferedReader和BufferedWriter四种缓冲流。
1. 字节缓冲流
- BufferedInputStream:用于读取数据,通过缓冲区减少磁盘I/O次数。
- BufferedOutputStream:用于写入数据,数据首先写入缓冲区,当缓冲区满或调用**flush()**方法时,再将数据写入目标文件。
示例代码(字节缓冲流写入文件)
import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class BufferedWriteExample {
public static void main(String[] args) {
String data = "使用BufferedOutputStream写入数据";
try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("fileExample/buffered_output.txt"))) {
bos.write(data.getBytes());
bos.flush(); // 可选,因为try-with-resources会自动调用close(),而close()会先调用flush()
System.out.println("文件写入成功!");
} catch (IOException e) {
e.printStackTrace();
}
}
}
2. 字符缓冲流
- BufferedReader:用于高效读取文本文件,提供了readLine() 方法按行读取。
- BufferedWriter:用于高效写入文本文件,提供了newLine() 方法用于写入行分隔符。
示例代码(字符缓冲流读取文件)
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
public class BufferedReadExample {
public static void main(String[] args) {
try (BufferedReader br = new BufferedReader(new FileReader("fileExample/buffered_output.txt"))) {
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
运行结果:
使用BufferedOutputStream写入数据
二、序列化(Serialization)
序列化是将对象的状态信息转换为可以存储或传输的形式的过程。在Java中,这通常是通过实现 Serializable 接口并使用 ObjectOutputStream 和 ObjectInputStream 类来完成的。
1. 序列化的关键作用
- 持久化存储:序列化可以将对象的状态(即对象的数据成员)转换为字节序列,然后可以将这个字节序列保存到文件系统、数据库或其他持久化存储介质中。这样,即使Java虚拟机(JVM)关闭,对象的状态也不会丢失,可以在将来重新加载并恢复对象的状态。
- 网络传输:在分布式系统或网络通信中,对象序列化允许将对象转换为字节流,便于通过网络传输。接收方收到字节流后,可以对其进行反序列化,重建原始对象。这对于远程方法调用(RMI)、Web服务、消息队列等场景非常有用。
- 跨平台兼容性:序列化生成的字节流格式是跨平台的,这意味着在一台机器上序列化的对象可以在另一台不同操作系统或架构的机器上进行反序列化,只要后者支持相同的序列化协议。
- 简化对象复制:有时候需要复制对象的实例,序列化提供了一种简便的方法来实现深拷贝,即创建一个与原对象具有相同状态的新对象,但两者在内存中是独立的实体。
- 对象版本控制:通过序列化ID(serialVersionUID),可以管理类的不同版本,确保即使类的内部结构改变,仍然可以正确地反序列化旧版本的实例。
- 实现特定功能: 某些Java框架或库可能依赖于序列化来实现特定的功能,例如缓存机制、配置管理等。
2. 序列化过程
- 使对象所属的类实现 Serializable 接口。
- 创建 ObjectOutputStream 实例,包装一个输出流(如 FileOutputStream )。
- 调用 writeObject() 方法将对象序列化到输出流中。
3. 示例代码(序列化对象)
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class SerializationExample implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private int age;
public static void main(String[] args) {
SerializationExample obj = new SerializationExample("张三", 30);
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("fileExample/obj.ser"))) {
oos.writeObject(obj);
System.out.println("对象序列化成功!");
System.out.println();
} catch (IOException e) {
e.printStackTrace();
}
}
public SerializationExample(String name, int age) {
super();
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public static long getSerialversionuid() {
return serialVersionUID;
}
}
4. 代码运行结果
在 fileExample 路径下生成了一个文件 obj.ser 并保存了序列化的对象信息
三、反序列化
1. 反序列化过程
- 创建 ObjectInputStream 实例,包装一个输入流(如 FileInputStream)。
- 调用 readObject() 方法从输入流中读取对象,该方法返回 Object 类型,通常需要强制类型转换。
2. 示例代码(反序列化对象)
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
public class DeserializationExample {
public static void main(String[] args) {
//读取对象文件
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("fileExample/obj.ser"))) {
//读取对象后强制转换为 SerializationExample 类型
SerializationExample obj = (SerializationExample) ois.readObject();
//打印对象信息
System.out.println(obj.getName() + " " + obj.getAge());
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
3. 运行结果:
张三 30
四、NIO(New I/O)
NIO是Java 1.4及以后版本中引入的一种新的I/O处理方式,它提供了与标准I/O不同的I/O操作方式。NIO基于通道(Channel)和缓冲区(Buffer)的模型,使得I/O操作更加高效。
1. 通道(Channel)
通道是一个连接到I/O服务(如文件或套接字)的管道,用于读取和写入数据。常见的通道有FileChannel、SocketChannel等。
2. 缓冲区(Buffer)
缓冲区是一个容器,它包含了一些要写入通道或者从通道中读出的数据。缓冲区实质上是一个数组,但提供了更多的操作,比如标记(mark)/重置(reset)、清空(clear)、反转(flip)等。
3. 选择器(Selector)
选择器允许单个线程处理多个通道,它是基于非阻塞I/O的。选择器会不断地轮询注册在其上的通道,如果某个通道处于就绪状态,则进行相应的I/O操作。
4. 示例代码(使用NIO写入文件)
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
public class NIOWriteExample {
public static void main(String[] args) throws Exception {
String data = "使用NIO写入文件";
try (RandomAccessFile aFile = new RandomAccessFile("fileExample/nio_output.txt", "rw");
FileChannel inChannel = aFile.getChannel()) {
ByteBuffer buf = ByteBuffer.allocate(1024);
buf.clear();
buf.put(data.getBytes());
buf.flip();
while(buf.hasRemaining()){
inChannel.write(buf);
}
System.out.println("文件写入成功!");
}
}
}
这段代码展示了如何使用Java NIO(New Input/Output)库中的FileChannel和ByteBuffer来写入文件。以下是详细解析:
1)创建 RandomAccessFile 对象
- RandomAccessFile aFile = new
RandomAccessFile(“fileExample/nio_output.txt”, “rw”); - 这行代码创建了一个 RandomAccessFile 对象 aFile,用于访问文件 “fileExample/nio_output.txt”。“rw” 模式表示文件将被用于读写。
2)获取 FileChannel
- FileChannel inChannel = aFile.getChannel();
- 通过 aFile 的 getChannel() 方法获取 FileChannel 对象 inChannel。FileChannel 是一个用于文件IO操作的通道。
3)创建 ByteBuffer 并写入数据
- ByteBuffer buf = ByteBuffer.allocate(1024);
- 这行代码创建了一个容量为1024字节的 ByteBuffer 对象 buf。
- buf.clear(); 清除缓冲区,准备写入新数据。
- buf.put(data.getBytes()); 将字符串 data 转换为字节后写入缓冲区。
4)切换缓冲区模式并写入文件
- buf.flip(); 切换缓冲区的模式,从填充数据切换到准备释放数据。
- while(buf.hasRemaining()){
inChannel.write(buf);
}循环检查缓冲区是否还有未写入的数据,如果有,则通过 inChannel.write(buf) 将数据写入文件。
总结,这段代码演示了如何使用 NIO 中的 FileChannel 和 ByteBuffer 将字符串数据写入文件。通过 RandomAccessFile 获取 FileChannel,然后使用 ByteBuffer 作为中介,将数据写入文件。
五、总结
Java的I/O流处理提供了丰富的API来支持各种输入输出操作。缓冲流通过减少底层I/O设备的直接访问次数来提高效率;序列化允许对象的状态被保存和传输;NIO则通过通道和缓冲区的模型进一步提高了I/O操作的性能。掌握这些技术对于进行高效的文件读写、网络通信等操作至关重要。
【创作】 不易,【点赞】 是情义,【关注】 是动力,【收藏】 是回忆,请接受我对您的 【谢意】