Java基础 Java-IO流 深入浅出 (4)

利用缓冲区一次读取多个字节的代码如下:

public void readFile() throws IOException { try (InputStream input = new FileInputStream("src/readme.txt")) { // 定义1000个字节大小的缓冲区: byte[] buffer = new byte[1000]; int n; while ((n = input.read(buffer)) != -1) { // 读取到缓冲区 System.out.println("read " + n + " bytes."); } } } 阻塞

在调用InputStream的read()方法读取数据时,我们说read()方法是阻塞(Blocking)的。它的意思是,对于下面的代码:

int n; n = input.read(); // 必须等待read()方法返回才能执行下一行代码 int m = n;

执行到第二行代码时,必须等read()方法返回后才能继续。因为读取IO流相比执行普通代码,速度会慢很多,因此,无法确定read()方法调用到底要花费多长时间。

OutputStream

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

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

public abstract void write(int b) throws IOException;

这个方法会写入一个字节到输出流。要注意的是,虽然传入的是int参数,但只会写入一个字节,即只写入int最低8位表示字节的部分(相当于b & 0xff)。

Flush

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

为什么要有flush()?因为向磁盘、网络写入数据的时候,出于效率的考虑,操作系统并不是输出一个字节就立刻写入到文件或者发送到网络,而是把输出的字节先放到内存的一个缓冲区里(本质上就是一个byte[]数组),等到缓冲区写满了,再一次性写入文件或者网络。对于很多IO设备来说,一次写一个字节和一次写1000个字节,花费的时间几乎是完全一样的,所以OutputStream有个flush()方法,能强制把缓冲区内容输出。

通常情况下,我们不需要调用这个flush()方法,因为缓冲区写满了OutputStream会自动调用它,并且,在调用close()方法关闭OutputStream之前,也会自动调用flush()方法。

但是,在某些情况下,我们必须手动调用flush()方法。举个栗子:

小明正在开发一款在线聊天软件,当用户输入一句话后,就通过OutputStream的write()方法写入网络流。小明测试的时候发现,发送方输入后,接收方根本收不到任何信息,怎么肥四?

原因就在于写入网络流是先写入内存缓冲区,等缓冲区满了才会一次性发送到网络。如果缓冲区大小是4K,则发送方要敲几千个字符后,操作系统才会把缓冲区的内容发送出去,这个时候,接收方会一次性收到大量消息。

解决办法就是每输入一句话后,立刻调用flush(),不管当前缓冲区是否已满,强迫操作系统把缓冲区的内容立刻发送出去。

实际上,InputStream也有缓冲区。例如,从FileInputStream读取一个字节时,操作系统往往会一次性读取若干字节到缓冲区,并维护一个指针指向未读的缓冲区。然后,每次我们调用int read()读取下一个字节时,可以直接返回缓冲区的下一个字节,避免每次读一个字节都导致IO操作。当缓冲区全部读完后继续调用read(),则会触发操作系统的下一次读取并再次填满缓冲区。

FileOutputStream

我们以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() } 阻塞

和InputStream一样,OutputStream的write()方法也是阻塞的。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpjsjf.html