无意中发现了一个巨牛的人工智能教程,忍不住分享一下给大家。教程不仅是零基础,通俗易懂,而且非常风趣幽默,像看小说一样!觉得太牛了,所以分享给大家。点这里可以跳转到教程。
内容概要
Java IO 流的设计不得不让人拍案叫绝,佩服设计者鬼斧天工的手法。
如果你是 Java 初学者,我敢保证第一次接触 Java 的 IO 类,一定会 ”狂晕!”,倒不是因为它有多么难学而是太多让人难以琢磨,让人有种 “不识庐山真面目” 的感觉,当你用心学完之后也会收获 “只缘身在此山中” 的顿悟。
在 Java 编程的日子中尤其是在网络编程中,几乎离不开 Java 的 IO 流,所以学好和用好它是很有必要的。
关于 Java 的 IO 流的分类,可以去找找相关的资料,今天跟大家分享一下其中比较神奇的flush
方法。
flush 与 OutputStream
该类实现了 Flushable 接口,所以重写了 flush 方法,看看 flush() 源码,会更加的让你明白:
public void flush() throws IOException { }
sorry,该实现为空。这里的flush
居然是一个空方法,什么也没做。看清楚啊,该方法不是抽象方法,是一个实实在在的方法。除了方法体中一无所有,其它还好!看JDK的API如何解释吧!
flush public void flush() throws IOException 刷新此输出流并强制写出所有缓冲的输出字节。flush 的常规协定是:如果此输出流的实现已经缓冲了以前写入的任何字节,则调用此方法指示应将这些字节立即写入它们预期的目标。 如果此流的预期目标是由基础操作系统提供的一个抽象(如一个文件),则刷新此流只能保证将以前写入到流的字节传递给操作系统进行写入,但不保证能将这些字节实际写入到物理设备(如磁盘驱动器)。OutputStream 的 flush 方法不执行任何操作。指定者: 接口 Flushable 中的 flush 抛出: IOException - 如果发生 I/O 错误。
开始,我安慰自己,该类是一个抽象类,它的子类肯定重写了该方法。
好吧,OutputStream
的直接子类有:ByteArrayOutputStream
、FileOutputStream
、FilterOutputStream
、ObjectOutputStream
、OutputStream
、PipedOutputStream
等几个类。
注意:这里的子类OutputStream
是包org.omg.CORBA.portable
的。
对于FileOutputStream
、ByteArrayOutputStream
、org.omg.CORBA.portable.OutputStream
类它们的flush()
方法均是从父类继承的flush
方法。
FilterOutputStream
类重写了flush()
方法,但是实质还是调用父类的flush()
方法。ObjectOutputStream
、PipedOutputStream
类重写了flush()
方法。
JDK 中,有两大类IO流即字节流和字符流(包括输入和输出),这些流类中有些是带有缓冲区功能的有些是没有的。BufferedInputStream 和 BufferedOutputStream 是字节缓冲区类;BufferedReader 和 BufferedWriter 是字符缓冲区类。
来举两个小例子,第一个例子主要是使用非缓冲区的IO流向文本中写入指定的字符串,第二个例子是使用带有缓冲区的IO流向文本中写入一定的字节数据。
1、例子1:向文本中写入字符串
import java.io.BufferedOutputStream; import java.io.DataOutputStream; import java.io.File; import java.io.FileOutputStream;public class Test {public static void main(String[] args) throws Exception { File file = new File("text1.txt"); if (!file.exists()) { file.createNewFile(); } FileOutputStream fos = new FileOutputStream(file);DataOutputStream dos = new DataOutputStream(fos);dos.writeBytes("java io");} }
这段代执行后,会在当前目录下产生 1KB的文件(内容为java io
)。
特别注意,这里没有调用 flush 方法,只是调用了 writeBytes 方法。调用流程图如下:
2、例子2:向文本中写入一定字节的数据
import java.io.BufferedOutputStream; import java.io.File; import java.io.FileOutputStream; public class Test {public static void main(String[] args) throws Exception { File file = new File("text2.txt"); if (!file.exists()) { file.createNewFile(); } FileOutputStream fos = new FileOutputStream(file);BufferedOutputStream bos = new BufferedOutputStream(fos); byte[] b = new byte[1024*8]; bos.write(b); bos.flush(); }}
这段代执行后,会在当前目录下产生8KB字节的文件。调用关系如下:
现在我们修改第二个例子的代码,主要修改两个地方
(1)、注释掉调用flush()
方法;
(2)、将 byte 大小改为 1024 大小。
修改如下:
import java.io.BufferedOutputStream;import java.io.File;import java.io.FileOutputStream;public class Test {public static void main(String[] args) throws Exception {File file = new File("text.txt");if (!file.exists()) {file.createNewFile();}FileOutputStream fos = new FileOutputStream(file);BufferedOutputStream bos = new BufferedOutputStream(fos);byte[] b = new byte[1024];bos.write(b);//bos.flush();}}
OK,再次运行代码,额的神啊???文件大小居然是0字节。
Why?
仔细的你会发现,第一个例子中的代码中并没有调用flush()
方法,居然可以正常的写入。为什么第二个就不可以呢?还是从源码入手找答案吧!
DataOutputStream
继承FilterOutputStream
,实现了DataOutput
接口。我们知道FilterOutputStream
类重写了flush()
方法,但是实质还是调用父类的flush()
方法。
DataOutputStream
类的flush()
方法效仿其父类FilterOutputStream
的做法,如下:
public void flush() throws IOException { out.flush(); }
即使你在第一个例子的代码后面加上dos.flush()
结果也是正常的,加与不加是一样的效果,因为它们的父类flush()
方法均为空,这就是为什么第一个代码的神奇所在。
第一个例子调用的 write 方法本质是调用了 native 实现的 wirte 方法,该方法直接将数据写入到了文件当中不需要调用 flush 方法,有兴趣的可以去查看一下 JDK 中的源码。
再看看第二个代码的 “病因” 在哪里?先看看BufferedOutputStream
类的结构:
public class BufferedOutputStream extends FilterOutputStream
再看看,它的flush()
方法:
public synchronized void flush() throws IOException {flushBuffer(); out.flush();} /** Flush the internal buffer */ private void flushBuffer() throws IOException { if (count > 0) {out.write(buf, 0, count);count = 0; } }
不错,该类重写了flush()
方法,不像前面几个类那样不是继承就是山寨父类的flush()
方法。BufferedOutputStream
类是一个使用了缓冲技术的类,这种类一把都会自己实现flush()
方法。
那么,有人会问使用这种类的时候,难道必须使用flush()
方法吗,当然不是喽??!!有两种情况下可以不用调用 flush 方法。
(1)、写入的数据不小于8KB。
如下示例代码,byte 大小改为 8KB:
import java.io.BufferedOutputStream; import java.io.File; import java.io.FileOutputStream; public class Test { public static void main(String[] args) throws Exception { File file = new File("text.txt"); if (!file.exists()) { file.createNewFile(); } FileOutputStream fos = new FileOutputStream(file); BufferedOutputStream bos = new BufferedOutputStream(fos); byte[] b = new byte[1024*8]; bos.write(b); //bos.flush(); }}
执行代码,会产生 8KB 大小的文本文件。
(2)、修改默认缓冲区大小
如下示例代码,修改一下构造 BufferedOutputStream 的方法,设置默认缓冲区大小为 1024。
File file = new File("text4.txt");if (!file.exists()) {file.createNewFile();}FileOutputStream fos = new FileOutputStream(file);BufferedOutputStream bos = new BufferedOutputStream(fos, 1024);byte[] b = new byte[1024];bos.write(b);//bos.flush();
执行代码,会产生 1KB 大小的文本文件。
这里提醒一下,如果你的文件读写没有达到预期目的,十之八九是因为你没有调用flush()
或者close()
方法。
另外,字符流类大多数都实现了flush()
或者close()
方法,只不过,它们调用的是StreamEncoder
类的该方法。该类位于sun.nio.cs
包下面,其源码在我们JDK中是没有的。
可以点击 StreamEncoder.java 查看源码。
flush 与 Writer
该类Writer
是一个抽象类,声明如下:
public abstract class Writer implements Appendable, Closeable, Flushable
Writer
类的flush()
方法是一个抽象方法,其子类一般都实现了该方法。
所以,一般使用字符流之后需要调用一下flush()
或者close()
方法。
abstract public void flush() throws IOException;
细节请看JDK的API,或者Java的源码以及上面的StreamEncoder
类源码。
今天就说到这里吧,本文主要借助Java IO中字节流与字符流的flush()
方法,来说明学编程语言看源码和思考是很重要的。
总之,不管你使用哪种流(字符、字节、具有缓冲的流)技术,不妨调用一下flush()
或者close()
方法,防止数据无法写到输出流中。
学习没有一蹴而就的方法,坚持思考、练习才是王道~