Appearance
Java IO - 05 其他流
本文在字节流与字符流的基础上,介绍其他包装流,包括缓冲流、打印流、数据流和序列化流。
1. 缓冲流
缓冲流是对原始流(字节流或字符流)进行包装,提供缓冲区以提高原始流读写数据的性能。

缓冲流的原理:

当使用字节缓冲流读取数据时,如果缓冲区(如图上8KB大小的桶)内没有数据,会先从原始流中加载一批数据到缓冲区中,然后再从缓冲区中读数据;如果缓冲区内有数据,则直接从缓冲区内读取数据。
同理,当使用字节缓冲流输出数据时,会先把数据输出到缓冲区中,待缓冲区内数据装满后,会一次性使用原始流将数据输出到磁盘或网络。
我们使用缓冲流对比原始流的读写性能(以复制大文件为案例):
java
public static void main(String[] args) {
copyFile01();
copyFile02();
}
/**
* 使用字节流复制文件
*/
private static void copyFile01(){
long startTime = System.currentTimeMillis();
try (
InputStream inputStream = new FileInputStream("src/main/resources/test.data");
OutputStream outputStream = new FileOutputStream("src/main/resources/test.data.copy01");
){
byte[] buffer = new byte[1024];
int len;
while ((len = inputStream.read(buffer)) != -1){
outputStream.write(buffer, 0, len);
}
}catch (Exception e){
e.printStackTrace();
}
long endTime = System.currentTimeMillis();
System.out.println("字节流复制文件耗时:" + (endTime - startTime) / 1000.0 + "s");
}
/**
* 使用缓冲流复制文件
*/
private static void copyFile02(){
long startTime = System.currentTimeMillis();
try (
InputStream inputStream = new FileInputStream("src/main/resources/test.data");
BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
OutputStream outputStream = new FileOutputStream("src/main/resources/test.data.copy02");
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(outputStream);
){
byte[] buffer = new byte[1024];
int len;
while ((len = bufferedInputStream.read(buffer)) != -1){
bufferedOutputStream.write(buffer, 0, len);
}
}catch (Exception e){
e.printStackTrace();
}
long endTime = System.currentTimeMillis();
System.out.println("缓冲流复制文件耗时:" + (endTime - startTime) / 1000.0 + "s");
}结果为:
txt
字节流复制文件耗时:2.263s
缓冲流复制文件耗时:1.201s可以看到,使用缓冲流对文件读写性能有提升。其实我们也可以使用原始流,将缓冲区设置大一点,以实现缓冲流的效果。
2. 打印流
打印流可以实现打印数据,即方便地输出各种类型数据。我们平时用的System.out就是打印流。

在Java中,打印流分为PrintStream和PrintWriter,两者都可以打印各种类型的数据,但不同是PrintStream
可以输出字节数据,而PrintWriter可以输出字符数据。
我们以PrintWriter演示打印流的使用:
java
public static void main(String[] args) {
try (
PrintWriter printWriter = new PrintWriter(
"src/main/resources/printWriterDemo.txt",
Charset.forName("UTF-8")
);
) {
printWriter.println(111); // 打印整数
printWriter.println(99.9); // 打印浮点数
printWriter.println(false); // 打印布尔值
printWriter.println("你好,hello"); // 打印字符串
printWriter.write("世界,world"); // PrintWriter 可以写字符数据
}catch (Exception e){
e.printStackTrace();
}
}我们可以更改System.out的输出目的地:
java
public static void main(String[] args) throws FileNotFoundException, UnsupportedEncodingException {
PrintStream printStream = new PrintStream("src/main/resources/printStreamDemo.txt", "UTF-8");
System.setOut(printStream);
System.out.println("输出到文件中....");
}这样,当之后使用System.out输出内容时,会输出到文件中,而不是控制台。
3. 数据流
数据流可以将数据与其类型一同输出和读入。在Java中相关的类是DataInputStream和DataOutputStream。
使用DataOutputStream输出数据:
java
public static void main(String[] args) {
try(
OutputStream outputStream = new FileOutputStream("src/main/resources/dataOutputStream.txt");
DataOutputStream dataOutputStream = new DataOutputStream(outputStream);
){
dataOutputStream.writeInt(100); // 输出整数
dataOutputStream.writeBoolean(true); // 输出布尔值
dataOutputStream.writeUTF("hello,你好"); // 输出字符串
dataOutputStream.writeDouble(99.99); // 输出浮点数
}catch (Exception e){
e.printStackTrace();
}
}输出的文件内容不可读,因为其中带了数据类型:

但是我们可以使用DataInputStream按序读取数据:
java
public static void main(String[] args) {
try(
InputStream inputStream = new FileInputStream("src/main/resources/dataOutputStream.txt");
DataInputStream dataInputStream = new DataInputStream(inputStream);
){
System.out.println(dataInputStream.readInt());
System.out.println(dataInputStream.readBoolean());
System.out.println(dataInputStream.readUTF());
System.out.println(dataInputStream.readDouble());
}catch (Exception e){
e.printStackTrace();
}
}输出结果:
txt
100
true
hello,你好
99.994. 序列化流
序列化流是将对象保存到文件中,或从文件中加载对象是流。我们可以使用ObjectInputStream和ObjectOutputStream进行对象的序列化与反序列化。
首先定义需要序列化的类:
java
@Data
@AllArgsConstructor
class User implements Serializable {
private String username;
private transient String password;
private int age;
private String email;
}注意点:
- 实体类需要实现
Serializable接口; - 对于敏感数据(比如密码)不想序列化时,可以使用关键字
transient;
然后实现序列化:
java
public static void serialize() {
try(
OutputStream outputStream = new FileOutputStream("src/main/resources/user.txt");
ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
) {
User user = new User("zhangsan", "123456", 18, "zhangsan@qq.com");
objectOutputStream.writeObject(user);
}catch (Exception e){
e.printStackTrace();
}
}实现反序列化操作:
java
public static void deserialize() {
try(
InputStream inputStream = new FileInputStream("src/main/resources/user.txt");
ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
){
User user = (User)objectInputStream.readObject();
System.out.println(user);
}catch (Exception e){
e.printStackTrace();
}
}结果:
txt
User(username=zhangsan, password=null, age=18, email=zhangsan@qq.com)可以看到密码是没有被序列化的。