黑马程序员_I/O流(字节流)

来源:互联网 发布:数组tostring方法 编辑:程序博客网 时间:2024/06/03 16:09

---------- android培训、java培训、java学习型技术博客,期待与您交流!----------

        与字符流相类似,字节流也有两个基类:InputStream和OutputStream。在操作非文本文件时,必须得使用字节流来完成。同理,字节流中专门操作文件的子类对象是:FileInputStream和FileOutputStream。FileInputStream对象在读取文件数据上也有两种方法,除了调用读取流自身的read方法外,还有自定义一个字节数组来读取的方法,两种方法依然都以返回值-1来表示已达读取流末尾。值得注意的是,FileInputStream类中的available方法使得在自定义字节数组用来读取时,字节数组的大小刚刚好,即定义了一个刚刚好的缓冲区,不用再循环操作来完成读取。

        同字符流的情况类似,为了提高字节流的操作效率,其同样使用了缓冲区来实现。字节流的两个缓冲区子类对象是BufferedInputStream和BufferedOutputStream。和字符流中提到的一样,理解了字节流的缓冲区技术原理后,我们自己也可以定义一个类来模拟BufferedInputStream。来看一下这个模拟的MyBufferedInputStream类:

import java.io.*;class MyBufferedInputStream{private InputStream in;private byte[] buf = new byte[1024*4];private int pos = 0,count = 0;MyBufferedInputStream(InputStream in){this.in = in;}//一次读一个字节,从缓冲区(字节数组)获取。public int myRead() throws IOException {//通过in对象读取硬盘上的数据,并存储到buf中。if(count==0){count = in.read(buf);if(count<0)return -1;pos = 0;byte b = buf[pos];count--;pos++;return b&255;}else if(count>0){byte b = buf[pos];count--;pos++;return b&0xff;}return -1;}public void myClose() throws IOException {in.close();}}

        字节流自定义缓冲区类相对于字符流的,需要注意一个问题。非文本文件的字节流数据中,很可能存在连续“1”的数据段,字节数组在读取字节流数据时如果读入的刚好是八个二进制位的1时,就等于读取到了十进制的-1,程序就会认为已经读取到字节流数据末尾,不再读取数据。再考虑到byte类型虽然取的是八个二进制位,但其返回后接收得类型是定义为int型的myRead,因此我们需要做相应的数据转换来保持数据与原读取数据相同且不出现被识别为-1的情形。转换过程如下:

byte: -1 ---> int:-111111111 ---> 11111111 11111111 11111111 1111111111111111 --->提升了一个int类型还是-1,原因是在8个二进制1前面补的是1导致的。那么我只要在前面补0,即可保留原字节数据不变,又可以避免-1的出现。怎么补0呢? 11111111 11111111 11111111 11111111&00000000 00000000 00000000 11111111------------------------------------ 00000000 00000000 00000000 11111111
           这也是为什么上面自定义缓冲区类中byte型对象b在返回时都要“与”上255(0xff是十六进制表示)的原因。

        前面的例子都是文件到文件间的数据传输,假如现在的要求是读取键盘录入,并打印在控制台上,当录入的数据是“over”时,录入停止,这该如何完成。读取键盘录入,默认使用的是System.in和System.out,简单完成如下

import java.io.*;class ReadIn{public static void main(String[] args) throws IOException {InputStream in = System.in;StringBuilder sb = new StringBuilder();while(true){int ch = in.read();if(ch=='\r')continue;if(ch=='\n'){String s = sb.toString();if("over".equals(s))break;System.out.println(s.toUpperCase());sb.delete(0,sb.length());}elsesb.append((char)ch);}}}
        通过以上的键盘录入一行数据并打印其大写,容易看出其原理就是用System.in读取一行数据然后再用System.out打印到控制台上,读取一行数据的原理很自然能够使我们联想到readLine方法。readLine方法是字符流BufferedReader的方法,而键盘录入的read方法是字节流InputStream的方法,要使用字符流缓冲区的readLine方法就需要先将字节流转换成字符流。因此,就需要用到两个新的子类:InputStreamReader和OutputStreamWriter。它们是连接字节流和字符流的桥梁。使用这两个新的子类后,前面键盘录入的例子就可以简化成:

import java.io.*;class TransStreamDemo{public static void main(String[] args) throws IOException {BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(System.out));String line = null;while((line=bufr.readLine())!=null){if("over".equals(line))break;bufw.write(line.toUpperCase());bufw.newLine();bufw.flush();}bufr.close();}}
        现在来总结一下,流操作的基本规律。流操作的难点在于流对象有很多,在具体需求前不知道用哪个来完成。事实上只要做到三个明确,答案也就跃然纸上了。首先,明确源和目的。源考虑输入流,基类有Reader和InputStream;目的考虑输出流,基类有Writer和OutputStream。其次,明确操作的数据是否是纯文本。如果是,选用字符流;如果不是,则选用字节流。最后,明确使用哪个具体的对象。往往通过设备来进行区分,包括源设备和目的设备。

        最后扩展一点,涉及到字符编码转换时,通常需要用到转换流


---------- android培训、java培训、java学习型技术博客,期待与您交流!----------

原创粉丝点击