黑马程序员——IO流4:IO流操作规律

来源:互联网 发布:狼群算法 编辑:程序博客网 时间:2024/05/18 02:46
------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------


IO流4:IO流操作规律


1 举例与问题——IO流类的选择

        在对IO流操作规律进行总结之前,我们再次回顾前面的博客《IO流2:字符流》中最后部分的例程代码,并通过这个例程来引出IO流操作中一个常见的问题,通过这个问题最终总结出IO流操作规律。

代码1:

<div style="text-align: left;"><span style="font-family: Arial, Helvetica, sans-serif;">import java.io.*;</span></div><div style="text-align: left;"><span style="font-family: Arial, Helvetica, sans-serif;"> </span></div><div style="text-align: left;"><span style="font-family: Arial, Helvetica, sans-serif;">class TransStreamDemo</span></div><div style="text-align: left;"><span style="text-align: center; font-family: Arial, Helvetica, sans-serif;">{</span></div><div style="text-align: left;"><span style="font-family: Arial, Helvetica, sans-serif;"><span style="white-space:pre"></span>public static void main(String[] args) throws IOException</span></div><div style="text-align: left;"><span style="font-family: Arial, Helvetica, sans-serif;"><span style="white-space:pre"></span>{</span></div><div style="text-align: left;"><span style="font-family: Arial, Helvetica, sans-serif;"><span style="white-space:pre"></span>BufferedReader bufr =</span></div><div style="text-align: left;"><span style="font-family: Arial, Helvetica, sans-serif;"><span style="white-space:pre"><span style="white-space: pre; background-color: rgb(240, 240, 240);"><span style="white-space:pre"></span>new BufferedReader(new InputStreamReader(System.in));</span></span></span></div><div style="text-align: left;"><span style="font-family: Arial, Helvetica, sans-serif;"><span style="white-space:pre"></span>BufferedWriter bufw = </span></div><div style="text-align: left;"><span style="font-family: Arial, Helvetica, sans-serif;"><span style="white-space:pre"></span>new BufferedWriter(new OutputStreamWriter(System.out));</span></div> <div style="text-align: left;"><span style="text-align: center; font-family: Arial, Helvetica, sans-serif;"><span style="white-space:pre"></span>String line = null;</span></div><div style="text-align: left;"><span style="font-family: Arial, Helvetica, sans-serif;"><span style="white-space:pre"></span>while((line= bufr.readLine()) != null)</span></div><div style="text-align: left;"><span style="font-family: Arial, Helvetica, sans-serif;"><span style="white-space:pre"></span>{</span></div><div style="text-align: left;"><span style="font-family: Arial, Helvetica, sans-serif;"><span style="white-space:pre"></span>if("over".equals(line))</span></div><div style="text-align: left;"><span style="text-align: center; font-family: Arial, Helvetica, sans-serif;"><span style="white-space:pre"></span>break;</span></div><div style="text-align: left;"><span style="font-family: Arial, Helvetica, sans-serif; text-align: center;"></span></div><div style="text-align: left;"><span style="font-family: Arial, Helvetica, sans-serif; text-align: center;"><span style="white-space:pre"></span>bufw.write(line);</span></div><div style="text-align: left;"><span style="font-family: Arial, Helvetica, sans-serif; text-align: center;"><span style="white-space:pre"></span>bufw.newLine();</span></div><div style="text-align: left;"><span style="font-family: Arial, Helvetica, sans-serif;"><span style="white-space:pre"></span>bufw.flush();</span></div><div style="text-align: left;"><span style="font-family: Arial, Helvetica, sans-serif;"><span style="white-space:pre"></span>}</span></div> <div style="text-align: left;"><span style="font-family: Arial, Helvetica, sans-serif;"><span style="white-space:pre"></span>bufr.close();</span></div><div style="text-align: left;"><span style="font-family: Arial, Helvetica, sans-serif;"><span style="white-space:pre"></span>bufw.close();</span></div><div style="text-align: left;"><span style="font-family: Arial, Helvetica, sans-serif;"><span style="white-space:pre"></span>}</span></div><div style="text-align: left;"><span style="font-family: Arial, Helvetica, sans-serif;">}</span></div>
以上代码简单实现了将键盘录入的一行文本打印到控制台中的功能。

       那么在以上代码中,数据源是键盘,而目的是控制台。如果我们希望将键盘录入的文本写入到一个硬盘文件中时,也就是说,数据源是键盘,而目的是文件,此时只需将写入流中的System.out修改为FileOuputStream("文件名.格式")即可,而无需修改代码的其他部分。若继续修改需求——将文件中的文本打印到控制台,那么此时数据源是文件,而目的是控制台,若要实现该需求,只需将读取流中的System.in修改为FileInputStreamReader("文件名.格式")。

       以上我们举了三个例子,通过不断修改数据源和目的,来实现不同的读取功能。那么通过这些例子就能够为实际开发中的一个现实问题提供一个解决思路,这个问题就是:面对IO工具包中繁多的IO流类,我们究竟应该是用哪一个呢?

2 IO流操作规律

        我们通过以下三个明确来正确选择我们真正需要的IO流:

        第一,  明确数据源和目的:我们要明确将要实现的功能中涉及的是对数据的读取,还是数据的写入,还是二者皆有。

源指的就是读取流,包括InputStream子类和Reader子类。

目的指的就是写入流,包括OutputStream子类和Writer子类。

        第二,  明确操作的数据是否是纯文本。第一点明确以后,我们再通过文件的类型来确定最终使用的体系——字符流还是字节流。

若操作纯文本,那么数据源就是Reader子类,而目的就是Writer子类。

若操作字节文本,那么数据源就是InputStream子类,而目的就是OutputStream。

通过以上两点,我们能够最终确定我们将要使用的体系是哪一支,那么

        第三,  明确具体的IO流类。不同的IO流类,体现的是对不同源和目的的操作,而通过对不同“设备”的区分可以明确具体使用哪个IO流类。

        数据源设备包括,内存(后面内容中会涉及到)、硬盘(文件)和键盘(System.in——标准输入)

        目的设备包括内存(后面内容会涉及到)、硬盘(文件)和控制台(System.out——标准输出)

3 规律的应用

        下面我们就根据以上三个明确,通过分析几个需求来说明IO流操作规律的使用。

(1)   需求一:

       将一个文本文件中的数据存储到另一个文本文件中——复制文本文件。

分析:

        第一步,该需求中既涉及读取,也涉及写入,因此既需要数据源也需要目的。

        第二步,读取流包括InputStream和Reader,由于操作的是文本,因此选择Reader体系。写入流包括OutputStream和Writer,由于写入目的同为文本,因此选择Writer。

        第三步,由于读取流操作的是文件,因此源设备就是硬盘,而Reader体系中可以对文件进行操作的IO流类就是FileReader,对应的流创建代码为,

FileReader fr = new FileReader("文件路径\\文件名.格式")

写入流操作的同样是文件,因此源设备同为硬盘,因此选择FileWriter,对应的流创建代码为,

FileWriter fw = new FileWriter("文件路径\\文件名.格式")

        通过以上三步,我们就可以较为明确的选择我们需要的IO流类,而剩下工作就是开启while循环,不断进行读写操作,如果需要还可以使用缓冲读写流——BufferedReader和BufferedWriter,进行效率的提升。

(2)   需求二:

       将一个图片文件中数据存储到另一个文件中——复制图片文件。

分析:

       第一步,该需求中同时包含读取和写入,相应的需要数据源和目的。

第二步,由于图片是字节文件,那么读取流应选择InputStream体系,写入流选择OutputStream体系。

       第三步,读取流和写入流操作的均是文件,因此数据源设备和目的设备均是硬盘,那么读取流最终选择FileInputStream,对应的流创建代码为,

       FileInputStreamfis = new FileInputStream("文件路径\\文件名.格式")

       写入流选择

FileOutputStreamfos = new FileOutputStream("文件路径\\文件名.格式")

同样,如果需要提升读写效率,可以使用缓冲字节读写流——BufferedInputStream和BufferedOutputStream。

(3)   需求三:

        将键盘录入的数据保存到一个文件中。

分析:

       第一步,需求中同时包含读取和写入,因此既要定义数据源还要定义目的。

       第二步,键盘录入的内容只能是文本,因此读取流选择Reader体系,相应的写入流选择Writer体系。

       第三步,数据源设备是键盘,那么对应的写入流应通过System.in获取,而通过该代码获取到的是字节读取流对象,那么能够将字节数据作为源进行操作的字符流就是InputStreamReader。这里大家可能会有这样的疑惑:既然System.in返回的是一个字节读取流对象,这看起来与前面选择的Reader体系是矛盾的。大家可以这样理解,该字节读取流对象,就相当于和字符读取流(比如FileReader)关联起来的文件,换句话说,这个字节读取流对象是作为数据源设备存在的,而不是对数据进行操作的流对象。实际上,无论是文本文件还是作为标准输入的键盘,它们都是以字节形式存在的数据源,然后再通过字节转字符的转换流,才最终成为字符流。那么键盘作为数据源的字符读取流对象创建代码为,

InputStreamReader isr = new InputStreamReader(System.in)

若需要提高读取效率,可使用字符缓存流封装,

BufferedReader bufr = new BufferedReader(isr)

该需求中,写入目的是一个硬盘文本文件,那么能够操作文本文件的Writer子类就是FileWriter,对应的流创建代码为,

FileWriter fw = new FileWriter("文件路径\\文件名.格式")

若提高效率,可使用字符缓存流封装

BufferedWriterbufw = new BufferedWriter("文件路径\\文件名.格式")

4 转换流补充

        由于以上内容中涉及到了转换流的另一个应用,因此在这里对转换流进行补充说明。

4.1 指定编码表

        如果我们去查阅FileReader或者FileWriter的API文档,就会发现对这两个类的描述中都会说到:此类的构造方法假定默认字符编码。也就是说,这两个字符流,都会使用默认的字符编码表——GBK,对文本数据进行编码与解码。但是根据文本文件的不同,可能不同的文件需要使用不同的编码表进行编码,而转换流的另一个主要功能就是可以通过指定的编码表来对文本文件进行读写操作。

       那么我们去查阅两个转换流API文档的构造方法摘要,分别有以下构造方法:

        public InputStream(InputStream in,String charsetName):创建使用指定字符集的InputStreamReader。将与字节流对象关联的文件,通过指定的编码表进行解码,并将解码得到的字符写入到文件中。

        public OutputStream(OutputStream out,String charsetName):创建使用指定字符集的OutputStreamWriter。通过指定的编码表对将要被写入的字符进行编码,并把编码的到的字节写入到与字节写入流对象关联的文件中。

       这也就是FileReader和FileWriter的直接父类分别是InputStreamReader和OutputStreamWriter的原因——FileReader和FileWriter通过他们的直接父类的构造方法初始化与数据源和目的关联的字节读写流,并在内部指定了编码表为GBK,并将这些动作全部封装了起来,直接用于读写文本文件即可。

4.2 转换流关于编码表的应用实例

       这里我们借用前述代码1,并将写入流中传入OutputStreamWriter构造方法中的参数由于System.out改为FileOutputStream,修改后的代码为,

代码2:

<div style="text-align: left;"><span style="font-family: Arial, Helvetica, sans-serif;">import java.io.*;</span></div> <div style="text-align: left;"><span style="font-family: Arial, Helvetica, sans-serif;">class TransStreamDemo2</span></div><div style="text-align: left;"><span style="text-align: center; font-family: Arial, Helvetica, sans-serif;">{</span></div><div style="text-align: left;"><span style="font-family: Arial, Helvetica, sans-serif;">public static void main(String[] args) throws IOException</span></div><div style="text-align: left;"><span style="text-align: center; font-family: Arial, Helvetica, sans-serif;"><span style="white-space:pre"></span>{</span></div><div style="text-align: left;"><span style="text-align: center; font-family: Arial, Helvetica, sans-serif;"><span style="white-space:pre"></span>BufferedReader bufr =</span></div><div style="text-align: left;"><span style="text-align: center; font-family: Arial, Helvetica, sans-serif;"><span style="white-space:pre"></span>new BufferedReader(new InputStreamReader(System.in));</span></div><div style="text-align: left;"><span style="font-family: Arial, Helvetica, sans-serif;"><span style="white-space:pre"></span>BufferedWriter bufw =</span></div><div style="text-align: left;"><span style="text-align: center; font-family: Arial, Helvetica, sans-serif;"><span style="white-space:pre"></span></span><span style="text-align: center; font-family: Arial, Helvetica, sans-serif;">new BufferedWriter(new OutputStreamWriter(new FileOutputStream("dest.txt")));</span><span style="text-align: center; font-family: Arial, Helvetica, sans-serif;"></span></div> <div style="text-align: left;"><span style="font-family: Arial, Helvetica, sans-serif;">//new BufferedWriter(new OutputStreamWriter(new FileOutputStream("dest2.txt"),"GBK"));</span></div><div style="text-align: left;"><span style="font-family: Arial, Helvetica, sans-serif;"><span style="white-space:pre"></span>//指定编码表为GBK</span></div><div style="text-align: left;"><span style="font-family: Arial, Helvetica, sans-serif;"><span style="white-space:pre"></span></span><span style="text-align: center; font-family: Arial, Helvetica, sans-serif;">//newBufferedWriter(new OutputStreamWriter(new FileOutputStream("dest3.txt"),"UTF-8"));</span></div><div style="text-align: left;"><span style="text-align: center; font-family: Arial, Helvetica, sans-serif;"><span style="white-space:pre"></span>//指定编码表为UTF-8</span><span style="font-family: Arial, Helvetica, sans-serif;"><span style="white-space:pre"></span></span></div><div style="text-align: left;"><span style="font-family: Arial, Helvetica, sans-serif;"></span><pre name="code" class="java" style="text-align: left;"><span style="white-space:pre"></span>String line = null;
while((line= bufr.readLine()) != null)
{
if("over".equals(line))
break;
bufw.write(line);
bufw.newLine();
bufw.flush();
}

bufr.close();
bufw.close();
}
}
        通过以上代码,分别使用默认编码表、GBK以及UTF-8,向目的文件dest.txt中写入“你好”两个字。虽然打开这些文件后的内容都是一样的,但是这些文件的大小是不同,使用默认编码表和GBK编码表的文件大小是相同的,均是6个字节,而使用UTF-8编码表写入的文件大小是8个字节。这就说明了两件事:第一,默认编码表确实是GBK;第二,由于GBK和UTF-8的编码形式不同,也就是说,相同字符所对应的字节值以及字节个数不同,导致了最终写入的文件大小的不同。

       也就是说,FileReader可以直接读取使用GBK编码的文本文件,但不能直接读取通过UTF-8编码的文本文件,此时就需要使用读取转换流。那么两相比较就可以发现:FileReader和FileWriter的特点就是不必初始化字节读取流对象和编码表,只需关联文件就可以读取文件,便捷快速,但是使用有局限性;而转换读写流,虽然需要手动初始化字节读写流以及指定编码表,但是可以用于操作使用不同编码表编码的文件,用途较为广泛。

       注意,以上内容中我们稍微涉及了编码相关内容,而关于这个主题我们将在后续博客中进行更为详细的介绍。

       最后总结一点,通常,涉及到字符编码的转换时,需要用到转换流,以实现将同一个文本在不同编码表之间进行切换。


0 0
原创粉丝点击