Java 语法 -- IO 流

Java 的 IO 流分为两大类:

  • 字节流
  • 字符流

从超类到实现类,Java IO 流的继承关系大致如下图:

InputStream

InputStream 就是 Java 标准库提供的最基本的字节输入流。它位于java.io这个包里。java.io包提供了所有同步IO的功能。

要特别注意的一点是,InputStream 并不是一个接口,而是一个抽象类,它是所有输入流的超类。

InputStream 类定义的一个最重要的方法就是 int read(),这个方法会读取输入流的下一个字节,并返回字节表示的 int 值(0~255)。如果已读到末尾,返回 -1 表示不能继续读取了。

FileInputStream

FileInputStream 是 InputStream 的一个子类。顾名思义,FileInputStream 就是从文件流中读取数据。下面的代码演示了如何完整地读取一个FileInputStream的所有字节:

public void readFile() throws IOException {
    // 创建一个FileInputStream对象:
    InputStream input = new FileInputStream("src/readme.txt");
    for (;;) {
        int n = input.read(); // 反复调用read()方法,直到返回-1
        if (n == -1) {
            break;
        }
        System.out.println(n); // 打印byte的值
    }
    input.close(); // 关闭流
}

InputStream 和 OutputStream 都是通过 close() 方法来关闭流。关闭流就会释放对应的底层资源。

所有与 IO 操作相关的代码都必须正确处理 IOException, 如果读取过程中发生了 IO 错误,InputStream 就没法正确地关闭,资源也就没法及时释放。

利用 Java 7引入的新的 try(resource) 的语法,只需要编写 try 语句,让编译器自动为我们关闭资源。推荐的写法如下:

public void readFile() throws IOException {
    try (InputStream input = new FileInputStream("src/readme.txt")) {
        int n;
        while ((n = input.read()) != -1) {
            System.out.println(n);
        }
    } // 编译器在此自动为我们写入finally并调用close()
}

OutputStream

和 InputStream 相反,OutputStream 是 Java 标准库提供的最基本的字节输出流。

和InputStream类似,OutputStream也是抽象类,它是所有输出流的超类。这个抽象类定义的一个最重要的方法就是void write(int b)

和InputStream类似,OutputStream也提供了close()方法关闭输出流,以便释放系统资源。要特别注意:OutputStream还提供了一个flush()方法,它的目的是将缓冲区的内容真正输出到目的地。

FileOutputStream

用 FileOutputStream 可以从文件获取输出流,这是 OutputStream 常用的一个实现类。

以 FileOutputStream 为例,演示如何将若干个字节写入文件流:

public void writeFile() throws IOException {
    OutputStream output = new FileOutputStream("out/readme.txt");
    output.write(72); // H
    output.write(101); // e
    output.write(108); // l
    output.write(108); // l
    output.write(111); // o
    output.close();
}

每次写入一个字节非常麻烦,更常见的方法是一次性写入若干字节。这时,可以用 OutputStream 提供的重载方法void write(byte[])来实现:

public void writeFile() throws IOException {
    OutputStream output = new FileOutputStream("out/readme.txt");
    output.write("Hello".getBytes("UTF-8")); // Hello
    output.close();
}

和InputStream一样,上述代码没有考虑到在发生异常的情况下如何正确地关闭资源。写入过程也会经常发生IO错误,例如,磁盘已满,无权限写入等等。我们需要用try(resource)来保证OutputStream在无论是否发生IO错误的时候都能够正确地关闭:

public void writeFile() throws IOException {
    try (OutputStream output = new FileOutputStream("out/readme.txt")) {
        output.write("Hello".getBytes("UTF-8")); // Hello
    } // 编译器在此自动为我们写入finally并调用close()
}

​​PrintStream

PrintStream 继承自 FilterOutputStream, FilterOutputStream 又继承自 OutputStream。

它在OutputStream的接口上,额外提供了一些写入各种数据类型的方法:

写入int:print(int)
写入boolean:print(boolean)
写入String:print(String)
写入Object:print(Object),实际上相当于print(object.toString())

以及对应的一组println()方法,它会自动加上换行符

我们经常使用的System.out.println()实际上就是使用PrintStream打印各种数据。其中,System.out是系统默认提供的PrintStream,表示标准输出:

System.out.print(12345); // 输出12345
System.out.print(new Object()); // 输出类似java.lang.Object@3c7a835a
System.out.println("Hello"); // 输出Hello并换行
System.err是系统默认提供的标准错误输出。

PrintStream和OutputStream相比,除了添加了一组print()/println()方法,可以打印各种数据类型,比较方便外,它还有一个额外的优点,就是不会抛出IOException,这样我们在编写代码的时候,就不必捕获IOException。

序列化与反序列化

序列化是指把一个Java对象变成二进制内容,本质上就是一个byte[]数组。

为什么要把Java对象序列化呢?因为序列化后可以把byte[]保存到文件中,或者把byte[]通过网络传输到远程,这样,就相当于把Java对象存储到文件或者通过网络传输出去了。

有序列化,就有反序列化,即把一个二进制内容(也就是byte[]数组)变回Java对象。有了反序列化,保存到文件中的byte[]数组又可以“变回”Java对象,或者从网络上读取byte[]并把它“变回”Java对象。

ObjectOutputStream

把一个 Java 对象变为 byte[] 数组,需要使用 ObjectOutputStream。它负责把一个 Java 对象写入一个字节流:

ObjectInputStream

和ObjectOutputStream相反,ObjectInputStream负责从一个字节流读取Java对象:

try (ObjectInputStream input = new ObjectInputStream(...)) {
    int n = input.readInt();
    String s = input.readUTF();
    Double d = (Double) input.readObject();
}

除了能读取基本类型和String类型外,调用readObject()可以直接返回一个Object对象。要把它变成一个特定类型,必须强制转型。

Java的序列化机制仅适用于Java,如果需要与其它语言交换数据,必须使用通用的序列化方法,例如JSON。

Reader 接口

Reader是Java的IO库提供的另一个输入流接口。和InputStream的区别是,InputStream是一个字节流,即以byte为单位读取,而Reader是一个字符流,即以char为单位读取:

java.io.Reader是所有字符输入流的超类,它最主要的方法是:int read()

这个方法读取字符流的下一个字符,并返回字符表示的int,范围是0~65535。如果已读到末尾,返回-1。

​BufferedReader

BufferedReader 是缓冲字符输入流。它继承于Reader。

BufferedReader 的作用是为其他字符输入流添加一些缓冲功能。

提供了读取一行的功能:readLine()

​LineNumberReader

LineNumberReader 继承自 BufferedReader,并且增加了下面两个功能:

  • 获取行号:getLineNumber()
  • 设置行号:setLineNumber()

​InputStreamReader

InputStreamReader : 是字节流与字符流之间的桥梁,能将字节流输出为字符流,并且能为字节流指定字符集,可输出一个个的字符.

​FileReader

​FileReader 继承自 InputStreamReader

它可以打开文件并获取Reader。下面的代码演示了如何完整地读取一个FileReader的所有字符:

public void readFile() throws IOException {
    // 创建一个FileReader对象:
    Reader reader = new FileReader("src/readme.txt"); 
    for (;;) {
        int n = reader.read(); // 反复调用read()方法,直到返回-1
        if (n == -1) {
            break;
        }
        System.out.println((char)n); // 打印char
    }
    reader.close(); // 关闭流
}

​StringReader

StringReader是接口 Reader 的一个实现类,其用法是读取一个String字符串。

Writer 接口

Reader是带编码转换器的InputStream,它把byte转换为char,而Writer就是带编码转换器的OutputStream,它把char转换为byte并输出。

Writer和OutputStream的区别如下:

Writer是所有字符输出流的超类,它提供的方法主要有:

  • 写入一个字符(0~65535):void write(int c);
  • 写入字符数组的所有字符:void write(char[] c);
  • 写入String表示的所有字符:void write(String s)。

​BufferedWriter

BufferedWriter 为带有默认缓冲的字符输出流。

主要方法:

  • void write(char ch);//写入单个字符。

  • void write(char []cbuf,int off,int len)//写入字符数据的某一部分。

  • void write(String s,int off,int len)//写入字符串的某一部分。

  • void newLine()//写入一个行分隔符。

  • void flush();//刷新该流中的缓冲。将缓冲数据写到目的文件中去。

  • void close();//关闭此流,再关闭前会先刷新他。

​OutputStreamWriter

整个IO包实际上分为字节流和字符流,但是除了这两个流之外,还存在一组字节流-字符流的转换类。

OutputStreamWriter:是Writer的子类,将输出的字符流变为字节流,即将一个字符流的输出对象变为字节流输出对象。

InputStreamReader:是Reader的子类,将输入的字节流变为字符流,即将一个字节流的输入对象变为字符流的输入对象。

如果以文件操作为例,则内存中的字符数据需要通过OutputStreamWriter变为字节流才能保存在文件中,读取时需要将读入的字节流通过InputStreamReader变为字符流。过程如下:

写入数据-->内存中的字符数据-->字符流-->OutputStreamWriter-->字节流-->网络传输(或文件保存)
读取数据<--内存中的字符数据<--字符流<--InputStreamReader<--字节流<--网络传输(或文件保存)

可以清楚地发现,不管如何操作,最终全部是以字节的形式保存在文件中或者进行网络传输。

​FileWriter

FileWriter 类从 OutputStreamWriter 类继承而来。该类按字符向流中写入数据。

它的使用方法和FileReader类似:

try (Writer writer = new FileWriter("readme.txt", StandardCharsets.UTF_8)) {
    writer.write('H'); // 写入单个字符
    writer.write("Hello".toCharArray()); // 写入char[]
    writer.write("Hello"); // 写入String
}

​PrintWriter

PrintStream最终输出的总是byte数据,而PrintWriter则是扩展了Writer接口,它的print()/println()方法最终输出的是char数据。两者的使用方法几乎是一模一样的:

public class Main {
    public static void main(String[] args)     {
        StringWriter buffer = new StringWriter();
        try (PrintWriter pw = new PrintWriter(buffer)) {
            pw.println("Hello");
            pw.println(12345);
            pw.println(true);
        }
        System.out.println(buffer.toString());
    }
}

​StringWriter

​StringWriter 是接口 Writer 的一个实现类,主要作用是写入一个字符串。

每天学习一点点,每天进步一点点。

posted @ 2020-08-16 10:09  爱吃西瓜的番茄酱  阅读(216)  评论(0编辑  收藏  举报