Skip to content

Java IO - 05 其他流

本文在字节流与字符流的基础上,介绍其他包装流,包括缓冲流、打印流、数据流和序列化流。

1. 缓冲流

缓冲流是对原始流(字节流或字符流)进行包装,提供缓冲区以提高原始流读写数据的性能。

image-20250206120823345

缓冲流的原理:

image-20250206120938759

当使用字节缓冲流读取数据时,如果缓冲区(如图上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就是打印流。

image-20250206123009858

在Java中,打印流分为PrintStreamPrintWriter,两者都可以打印各种类型的数据,但不同是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中相关的类是DataInputStreamDataOutputStream

使用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();
    }
}

输出的文件内容不可读,因为其中带了数据类型:

image-20250206130914677

但是我们可以使用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.99

4. 序列化流

序列化流是将对象保存到文件中,或从文件中加载对象是流。我们可以使用ObjectInputStreamObjectOutputStream进行对象的序列化与反序列化。

首先定义需要序列化的类:

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)

可以看到密码是没有被序列化的。