MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Java输入输出流基础

2022-03-121.7k 阅读

Java 输入输出流概述

在 Java 编程中,输入输出(I/O)操作是与外部环境交互的关键手段。输入流用于从外部源(如文件、网络连接、用户输入等)读取数据,而输出流则用于将数据写入到外部目的地(同样可以是文件、网络连接等)。Java 的 I/O 流体系设计得非常灵活且强大,能够适应各种不同的 I/O 场景。

Java 的 I/O 流类库位于 java.io 包下。这个包中包含了大量的类,这些类大致可以分为字节流和字符流两大类。字节流以字节(8 位)为单位处理数据,适用于处理二进制数据,如图片、音频、视频等;字符流以字符(16 位 Unicode 字符)为单位处理数据,主要用于处理文本数据。

字节流

字节输入流 - InputStream

InputStream 是所有字节输入流的抽象基类。它定义了一系列用于读取字节数据的方法。其中,最基本的方法是 read(),这个方法从输入流中读取一个字节的数据,并返回读取到的字节值(如果已到达流的末尾,则返回 -1)。示例代码如下:

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

public class ByteInputStreamExample {
    public static void main(String[] args) {
        try (InputStream inputStream = new FileInputStream("example.txt")) {
            int data;
            while ((data = inputStream.read()) != -1) {
                System.out.print((char) data);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

在上述代码中,我们使用 FileInputStream(它是 InputStream 的一个具体子类,用于从文件中读取数据)来读取一个文本文件 example.txt 的内容。while 循环通过不断调用 read() 方法读取字节,并将其转换为字符后打印到控制台。当 read() 方法返回 -1 时,表示已到达文件末尾,循环结束。

除了 read() 方法,InputStream 还提供了其他一些有用的方法。例如,read(byte[] b) 方法可以一次性读取多个字节到指定的字节数组 b 中,并返回实际读取的字节数。read(byte[] b, int off, int len) 方法则允许从字节数组的指定偏移量 off 开始,读取最多 len 个字节的数据。

字节输出流 - OutputStream

OutputStream 是所有字节输出流的抽象基类。它定义了用于将字节数据写入到输出目的地的方法。最基本的方法是 write(int b),该方法将指定的字节写入到输出流中。示例代码如下:

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;

public class ByteOutputStreamExample {
    public static void main(String[] args) {
        String message = "Hello, World!";
        try (OutputStream outputStream = new FileOutputStream("output.txt")) {
            outputStream.write(message.getBytes());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

在这段代码中,我们创建了一个 FileOutputStreamOutputStream 的具体子类,用于将数据写入文件),并使用 write(byte[] b) 方法将字符串 Hello, World! 转换为字节数组后写入到 output.txt 文件中。write(int b) 方法虽然接受一个 int 类型参数,但实际上只会写入该 int 值的低 8 位字节。

字符流

字符输入流 - Reader

Reader 是所有字符输入流的抽象基类。它以字符(16 位 Unicode 字符)为单位处理数据,提供了比字节流更方便的文本读取功能。Reader 的基本读取方法是 read(),该方法读取一个字符并返回其整数值(如果已到达流的末尾,则返回 -1)。示例代码如下:

import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;

public class CharacterInputStreamExample {
    public static void main(String[] args) {
        try (Reader reader = new FileReader("example.txt")) {
            int data;
            while ((data = reader.read()) != -1) {
                System.out.print((char) data);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

此代码使用 FileReaderReader 的具体子类,用于从文件中读取字符数据)读取 example.txt 文件的内容,并将每个字符打印到控制台。与字节流的 InputStream 类似,Reader 也有 read(char[] cbuf)read(char[] cbuf, int off, int len) 方法,用于一次性读取多个字符到字符数组中。

字符输出流 - Writer

Writer 是所有字符输出流的抽象基类。它用于将字符数据写入到输出目的地。基本的写入方法是 write(int c),该方法将指定的字符写入到输出流中。示例代码如下:

import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;

public class CharacterOutputStreamExample {
    public static void main(String[] args) {
        String message = "你好,世界!";
        try (Writer writer = new FileWriter("output.txt")) {
            writer.write(message);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

在这段代码中,我们通过 FileWriterWriter 的具体子类,用于将字符数据写入文件)将包含中文字符的字符串 你好,世界! 写入到 output.txt 文件中。Writer 还提供了 write(String str) 方法,用于直接写入字符串,以及 write(char[] cbuf) 等方法用于写入字符数组。

缓冲流

缓冲字节输入流 - BufferedInputStream

BufferedInputStreamFilterInputStream 的子类,它为字节输入流提供了缓冲功能。通过在内存中设置一个缓冲区,减少了实际的 I/O 操作次数,从而提高了读取效率。示例代码如下:

import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

public class BufferedByteInputStreamExample {
    public static void main(String[] args) {
        try (InputStream inputStream = new FileInputStream("largeFile.bin");
             BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream)) {
            int data;
            while ((data = bufferedInputStream.read()) != -1) {
                // 处理数据
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

在上述代码中,我们首先创建了一个 FileInputStream 用于读取一个二进制文件 largeFile.bin,然后将其包装在 BufferedInputStream 中。这样,当调用 bufferedInputStream.read() 方法时,它会先从缓冲区中读取数据,如果缓冲区为空,则从文件中读取一批数据填充缓冲区。

缓冲字节输出流 - BufferedOutputStream

BufferedOutputStreamFilterOutputStream 的子类,为字节输出流提供缓冲功能。它同样通过缓冲区减少实际的 I/O 操作次数,提高写入效率。示例代码如下:

import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;

public class BufferedByteOutputStreamExample {
    public static void main(String[] args) {
        byte[] data = new byte[1024];
        // 填充数据
        try (OutputStream outputStream = new FileOutputStream("output.bin");
             BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(outputStream)) {
            bufferedOutputStream.write(data);
            bufferedOutputStream.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

在这段代码中,我们创建了一个 FileOutputStream 用于写入二进制文件 output.bin,并将其包装在 BufferedOutputStream 中。调用 bufferedOutputStream.write(data) 方法时,数据会先写入缓冲区,当缓冲区满或者调用 flush() 方法时,缓冲区的数据才会被实际写入到文件中。

缓冲字符输入流 - BufferedReader

BufferedReaderFilterReader 的子类,为字符输入流提供缓冲功能。它除了继承自 Reader 的基本读取方法外,还提供了 readLine() 方法,用于读取一行文本。示例代码如下:

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;

public class BufferedCharacterInputStreamExample {
    public static void main(String[] args) {
        try (Reader reader = new FileReader("example.txt");
             BufferedReader bufferedReader = new BufferedReader(reader)) {
            String line;
            while ((line = bufferedReader.readLine()) != null) {
                System.out.println(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

在上述代码中,我们通过 BufferedReader 读取文本文件 example.txt 的内容,并逐行打印到控制台。readLine() 方法会读取到行尾(包括换行符),并返回该行的内容(不包括换行符)。如果已到达文件末尾,则返回 null

缓冲字符输出流 - BufferedWriter

BufferedWriterFilterWriter 的子类,为字符输出流提供缓冲功能。它提供了 newLine() 方法,用于写入一个平台相关的换行符。示例代码如下:

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;

public class BufferedCharacterOutputStreamExample {
    public static void main(String[] args) {
        String[] lines = {"第一行", "第二行", "第三行"};
        try (Writer writer = new FileWriter("output.txt");
             BufferedWriter bufferedWriter = new BufferedWriter(writer)) {
            for (String line : lines) {
                bufferedWriter.write(line);
                bufferedWriter.newLine();
            }
            bufferedWriter.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

在这段代码中,我们通过 BufferedWriter 将一个字符串数组中的每一行写入到 output.txt 文件中,并使用 newLine() 方法写入换行符。同样,调用 flush() 方法可以确保缓冲区的数据被及时写入到文件中。

数据流

数据输入流 - DataInputStream

DataInputStreamFilterInputStream 的子类,它允许应用程序以与机器无关的方式从底层输入流中读取基本 Java 数据类型(如 intfloatboolean 等)。示例代码如下:

import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

public class DataInputStreamExample {
    public static void main(String[] args) {
        try (InputStream inputStream = new FileInputStream("data.bin");
             DataInputStream dataInputStream = new DataInputStream(inputStream)) {
            int number = dataInputStream.readInt();
            float fraction = dataInputStream.readFloat();
            boolean flag = dataInputStream.readBoolean();
            System.out.println("读取的整数: " + number);
            System.out.println("读取的浮点数: " + fraction);
            System.out.println("读取的布尔值: " + flag);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

在上述代码中,假设 data.bin 文件是由 DataOutputStream 按照特定格式写入的包含整数、浮点数和布尔值的数据文件。DataInputStream 可以按照写入的顺序正确读取这些数据,保证数据的一致性和跨平台性。

数据输出流 - DataOutputStream

DataOutputStreamFilterOutputStream 的子类,它允许应用程序以与机器无关的方式将基本 Java 数据类型写入到底层输出流中。示例代码如下:

import java.io.DataOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;

public class DataOutputStreamExample {
    public static void main(String[] args) {
        try (OutputStream outputStream = new FileOutputStream("data.bin");
             DataOutputStream dataOutputStream = new DataOutputStream(outputStream)) {
            int number = 12345;
            float fraction = 3.14f;
            boolean flag = true;
            dataOutputStream.writeInt(number);
            dataOutputStream.writeFloat(fraction);
            dataOutputStream.writeBoolean(flag);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

在这段代码中,我们创建了一个 DataOutputStream 并将整数 12345、浮点数 3.14f 和布尔值 true 写入到 data.bin 文件中。当使用 DataInputStream 读取这个文件时,就可以按照相同的顺序和格式正确读取这些数据。

对象流

对象输入流 - ObjectInputStream

ObjectInputStream 用于从输入流中读取对象。它能够反序列化先前使用 ObjectOutputStream 写入的对象。要使用对象流进行对象的序列化和反序列化,对象的类必须实现 Serializable 接口。示例代码如下:

import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;

public class ObjectInputStreamExample {
    public static void main(String[] args) {
        try (FileInputStream fileInputStream = new FileInputStream("object.bin");
             ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream)) {
            MySerializableObject obj = (MySerializableObject) objectInputStream.readObject();
            System.out.println("反序列化后的对象: " + obj);
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

class MySerializableObject implements java.io.Serializable {
    private String message;

    public MySerializableObject(String message) {
        this.message = message;
    }

    @Override
    public String toString() {
        return "MySerializableObject{" +
                "message='" + message + '\'' +
                '}';
    }
}

在上述代码中,MySerializableObject 类实现了 Serializable 接口。ObjectInputStreamobject.bin 文件中读取并反序列化出 MySerializableObject 对象。

对象输出流 - ObjectOutputStream

ObjectOutputStream 用于将对象写入到输出流中,即进行对象的序列化。示例代码如下:

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

public class ObjectOutputStreamExample {
    public static void main(String[] args) {
        MySerializableObject obj = new MySerializableObject("这是一个可序列化的对象");
        try (FileOutputStream fileOutputStream = new FileOutputStream("object.bin");
             ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream)) {
            objectOutputStream.writeObject(obj);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

class MySerializableObject implements java.io.Serializable {
    private String message;

    public MySerializableObject(String message) {
        this.message = message;
    }

    @Override
    public String toString() {
        return "MySerializableObject{" +
                "message='" + message + '\'' +
                '}';
    }
}

在这段代码中,我们创建了一个 MySerializableObject 对象,并使用 ObjectOutputStream 将其写入到 object.bin 文件中。在对象序列化过程中,对象的状态(即对象的成员变量值)会被转换为字节序列,以便在网络传输或存储到文件中。

打印流

打印流 - PrintStream

PrintStream 是一个字节输出流,它提供了方便的格式化输出方法。System.out 就是一个 PrintStream 实例,常用于将文本输出到控制台。示例代码如下:

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;

public class PrintStreamExample {
    public static void main(String[] args) {
        try (PrintStream printStream = new PrintStream(new FileOutputStream("output.txt"))) {
            int number = 100;
            double fraction = 3.14159;
            printStream.printf("整数: %d, 浮点数: %.2f", number, fraction);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

在上述代码中,我们创建了一个 PrintStream 并将其关联到 output.txt 文件。通过 printf() 方法,我们可以按照指定的格式将整数和浮点数输出到文件中。PrintStream 还提供了 print()println() 方法,用于简单的文本输出。

打印流 - PrintWriter

PrintWriter 是一个字符输出流,同样提供了方便的格式化输出方法,类似于 PrintStream。示例代码如下:

import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;

public class PrintWriterExample {
    public static void main(String[] args) {
        try (PrintWriter printWriter = new PrintWriter(new FileWriter("output.txt"))) {
            String name = "张三";
            int age = 25;
            printWriter.printf("姓名: %s, 年龄: %d", name, age);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

在这段代码中,我们使用 PrintWriter 将格式化后的字符串写入到 output.txt 文件中。PrintWriter 更适合处理字符数据,特别是在需要进行文本格式化输出的场景下。

转换流

字节流转字符流 - InputStreamReader

InputStreamReader 用于将字节输入流转换为字符输入流。它根据指定的字符编码将字节解码为字符。示例代码如下:

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;

public class InputStreamReaderExample {
    public static void main(String[] args) {
        try (InputStreamReader inputStreamReader = new InputStreamReader(new FileInputStream("example.txt"), "UTF-8")) {
            int data;
            while ((data = inputStreamReader.read()) != -1) {
                System.out.print((char) data);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

在上述代码中,我们通过 InputStreamReaderFileInputStream 转换为字符输入流,并指定字符编码为 UTF - 8。这样,在读取文件内容时,就可以按照 UTF - 8 编码正确地将字节转换为字符。

字符流转字节流 - OutputStreamWriter

OutputStreamWriter 用于将字符输出流转换为字节输出流。它根据指定的字符编码将字符编码为字节。示例代码如下:

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;

public class OutputStreamWriterExample {
    public static void main(String[] args) {
        String message = "使用 OutputStreamWriter 写入";
        try (OutputStreamWriter outputStreamWriter = new OutputStreamWriter(new FileOutputStream("output.txt"), "UTF-8")) {
            outputStreamWriter.write(message);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

在这段代码中,我们通过 OutputStreamWriter 将字符数据按照 UTF - 8 编码写入到 output.txt 文件中。通过这种方式,可以方便地控制字符到字节的编码转换过程。

通过对上述各种输入输出流的学习和使用,开发者能够在 Java 编程中灵活地处理各种 I/O 场景,无论是简单的文件读写,还是复杂的网络通信、对象序列化等操作,都能找到合适的流类来完成任务。在实际应用中,需要根据具体需求选择合适的流,并注意流的正确关闭,以避免资源泄漏等问题。同时,合理使用缓冲流、数据流等高级流类,可以显著提高 I/O 操作的效率和灵活性。在处理字符数据时,要特别注意字符编码的选择,以确保数据的正确读写和跨平台兼容性。对于对象的序列化和反序列化,要确保相关类实现 Serializable 接口,并了解序列化过程中的一些细节,如 transient 关键字的使用等,以保证对象状态的正确保存和恢复。总之,深入理解 Java 的输入输出流体系是成为一名优秀 Java 开发者的重要基础之一。