IO模型与NIO基础二
抽象基类之二
FilterInputStream
FilterInputStream 的作用是用来“封装其它的输入流,并为它们提供额外的功能”。
它的常用的子类有BufferedInputStream和DataInputStream。
(1) BufferedInputStream
的作用就是为“输入流提供缓冲功能,以及mark()和reset()功能”。
- InputStream和Reader提供的一些移动指针的方法:
- void mark(int readlimit ); 在记录指针当前位置记录一个标记(mark)。
- boolean markSupported(); 判断此输入流是否支持mark()操作,即是否支持记录标记。
- void reset(); 将此流的记录指针重新定位到上一次记录标记(mark)的位置。
- long skip(long n); 记录指针向前移动n个字节/字符。
readlimit 参数给出当前输入流在标记位置变为非法前允许读取的字节数。
这句话的意思是说:mark就像书签一样,用于标记,以后再调用reset时就可以再回到这个mark过的地方。mark方法有个参数,通过这个整型参数,告诉系统,希望在读出多少个字符之前,这个mark保持有效。
比如说mark(10),那么在read()10个以内的字符时,reset()操作指针可以回到标记的地方,然后重新读取已经读过的数据,如果已经读取的数据超过10个,那reset()操作后,就不能正确读取以前的数据了,mark()打标记已经失效,reset()会报错。
但实际的运行情况却和JAVA文档中的描述并不完全相符。 有时候在BufferedInputStream类中调用mark(int readlimit)方法后,即使读取超过readlimit字节的数据,mark标记仍可能有效,仍然能正确调用reset方法重置。
事实上,mark在JAVA中的实现是和缓冲区相关的。只要缓冲区够大,mark后读取的数据没有超出缓冲区的大小,mark标记就不会失效。如果不够大,mark后又读取了大量的数据,导致缓冲区更新,原来标记的位置自然找不到了。
因此,mark后读取多少字节才失效,并不完全由readlimit参数确定,也和BufferedInputStream类的缓冲区大小有关。 如果BufferedInputStream类的缓冲区大小大于readlimit,在mark以后只有读取超过缓冲区大小的数据,mark标记才会失效。
public class test1 {public static void main(String[] args) throws IOException {byte[] b=new byte[] {1,2,3,4,5};//把数组转为数组输入流ByteArrayInputStream bais = new ByteArrayInputStream(b);//进行一次封装,封装时指定缓冲区大小,先指定2个字节大小BufferedInputStream bis = new BufferedInputStream(bais,2);//先读出第一个字节数据出来,指针会指向第二个字节即2上面System.out.println(bis.read()); // 1//现在指针在2上,打一个标记bis.mark(1); //按官方文档来说,读第一个数据出来后,标记会失效,reset()方法会报错,事实上不会报错,经过测试,是缓冲区bis读三个数据时,//大小缓冲区大小,缓冲区装不下了,标记才有用。System.out.println(bis.read()); //2System.out.println(bis.read()); //3bis.reset();System.out.println(bis.read()); //2}}
----------------------------------------------------------------------------
1
2
3
2
当连续读三个数据时,缓冲区(2个字节大小)装不下,标记才会失效
public class test1 {public static void main(String[] args) throws IOException {byte[] b=new byte[] {1,2,3,4,5};//把数组转为数组输入流ByteArrayInputStream bais = new ByteArrayInputStream(b);//进行一次封装,封装时指定缓冲区大小,先指定2个字节大小BufferedInputStream bis = new BufferedInputStream(bais,2);//先读出第一个字节数据出来,指针会指向第二个字节即2上面System.out.println(bis.read()); // 1//现在指针在2上,打一个标记bis.mark(1); //按官方文档来说,读第一个数据出来后,标记会失效,reset()方法会报错,事实上不会报错,经过测试,是缓冲区bis读三个数据时,//大小缓冲区大小,缓冲区装不下了,标记才有用。System.out.println(bis.read()); //2System.out.println(bis.read()); //3System.out.println(bis.read()); //4bis.reset();System.out.println(bis.read()); 没有数据打印,并报错了}}
----------------------------------------------------------------------------
1
2
3
4
Exception in thread "main" java.io.IOException: Resetting to invalid markat java.io.BufferedInputStream.reset(Unknown Source)at cn.ybzy.io.filter.test1.main(test1.java:33)
不设标记,不重设定位正常读没问题
public class test1 {public static void main(String[] args) throws IOException {byte[] b=new byte[] {1,2,3,4,5};//把数组转为数组输入流ByteArrayInputStream bais = new ByteArrayInputStream(b);//进行一次封装,封装时指定缓冲区大小,先指定2个字节大小BufferedInputStream bis = new BufferedInputStream(bais,2);System.out.println(bis.read()); // 1System.out.println(bis.read()); //2System.out.println(bis.read()); //3System.out.println(bis.read()); //4System.out.println(bis.read()); //5}}
(2) DataInputStream
是用来装饰其它输入流,它“允许应用程序以与机器无关方式从底层输入流中读取基本 Java 数据类型”。
应用程序可以使用DataOutputStream(数据输出流)写入由DataInputStream(数据输入流)读取的数据。
FilterOutputStream
FilterOutputStream 的作用是用来“封装其它的输出流,并为它们提供额外的功能”。
它主要包括BufferedOutputStream, DataOutputStream和PrintStream。
(1) BufferedOutputStream的作用就是为“输出流提供缓冲功能”。
(2) DataOutputStream 是用来装饰其它输出流,将DataOutputStream和DataInputStream输入流配合使用,
“允许应用程序以与机器无关方式从底层输入流中读写基本 Java 数据类型”。
打印流PrintStream
PrintStream是用来装饰其它输出流。它能为其他输出流添加了功能,使它们能够方便地打印各种数据值表示形式。
打印流提供了非常方便的打印功能,可以打印任何类型的数据信息,例如:小数,整数,字符串。
之前打印信息需要使用OutputStream但是这样,所有数据输出会非常麻烦,PrintStream可以把OutputStream类重新包装了一下,使之输出更方便。
public class PrintStreamTest {public static void main(String[] args) throws FileNotFoundException {FileOutputStream fos =new FileOutputStream("c:\\c.txt");PrintStream print=new PrintStream(fos);print.print("xiongshaowen");print.print(123);print.print(12.3);print.print(new Object());print.close();}}
格式化输出:
JAVA对PrintStream功能进行了扩充,增加了格式化输出功能。直接使用Print即可。但是输出的时候需要指定输出的数据类型。
public class PrintStreamTest {public static void main(String[] args) throws FileNotFoundException {FileOutputStream fos =new FileOutputStream("c:\\c.txt");PrintStream ps=new PrintStream(fos);/*print.print("xiongshaowen");print.print(123);print.print(12.3);print.println(new Object());print.close();*///格式打印int i = 10;String s="打印流";float f = 15.5f;ps.printf("整数 %d,字符串 %s,浮点数 %f",i,s,f);ps.println();ps.printf("整数 [%d],字符串 %s,浮点数 '%f'",i,s,f);ps.close();}}
推回输入流的使用
通常我们使用输入流的时候,我们读取输入流是顺序读取,当读取的不是我们想要的怎么办,又不能放回去,虽然我们可以使用程序做其他的处理来解决,但是Java提供了推回输入流来解决这个问题,
推回输入流可以做这样子的事情:将已经读取出来的字节或字符数组内容推回到推回缓冲区里面,
从而允许重复读取刚刚读取的我们不想要的东西之前内容
注意:
当程序创建一个推回输入流时需要指定推回缓冲区的大小,默认的推回缓冲区长度为一,
如果程序推回到推回缓冲区的内容超出了推回缓冲区的大小,将会引发Pushback buffer overflow 异常。
程序举例:
假如C盘下有一个aa.txt文件,内容如下:我现在只想读取aaa前面的内容,后面的我不想要。
public class TuiHuistream {public static void main(String[] args) throws IOException {//1文件流,2字符流 3输入流Reader reader = new FileReader("C:\\aa.txt");PushbackReader pr = new PushbackReader(reader,1024);//从输入流中读取数据char[] cs = new char[5];int hasReadCount = 0;String sumString="";int count=0; //计数器,看看下面读了多少次数据while((hasReadCount=pr.read(cs))!=-1) {String curString = new String(cs,0,hasReadCount);sumString =sumString+curString;count++;int aaaIndex = sumString.indexOf("aaa"); //这里我们以'aaa’为标记,推回aaa之前的内容到缓冲区中if(aaaIndex>-1) {pr.unread(sumString.toCharArray()); //把所有内容推到缓冲区中(cs)//重新把我想要的内容(即aaa之前的内容)读出来if(aaaIndex >5) {cs = new char[aaaIndex];//扩容缓冲区}pr.read(cs,0,cs.length);System.out.println("我想要的内容为:"+new String(cs));break;}else {System.out.println(new String(cs));}}System.out.println("一共读了多少次:"+count+"次");pr.close();}}
------------------------------------------------------------------------------------------------------------------------------
ccccc
ccccc
ccccc
cc
c
cccca
我想要的内容为:ccccccccccccccccc
ccccc
一共读了多少次:6次