流的概念模型
Java 的IO流共设计 40 多个类,这些类看上去复杂,但实际上非常规则,而且彼此之间存在非常紧密的联系。它们都是从如下4 个抽象基类派生的。
- InputStream/Reader:所有输入流的基类,前者是字节输出流,后者是字符输出流
- OutputStream/Writer:所有输出流的基类,前者是字节输出流,后者是字符输出流。
InputStream/Reader
对于InPutStream 和Reader 而言,它们把输入设备抽象成一个水管,这个水管里的每个水滴一次排列,如图
字节流和字符流的处理方式其实非常相似,只是它们处理的输入/输出单位不同而言。**输入流使用隐式的记录指针来表示 **当前正准备从哪个“水滴”开始读取,每当程序从 InputStream或 Reader 里取出一个或多个 “水滴” 后,记录指针自动向后移动;除此之外,InputSteam 和 Reader里都提供了一些方法来控制记录指针的移动。
OutputStream/Writer
对于OutputStream 和 Writer而言,它们同样把输出设备抽象成一个水管,只是这个水管里没有任何水滴。
当执行输出时,程序相当于依次把 “水滴” 放入到输出流的水管中,输出流同样采用隐式的记录指正来标识当前属地即将放入的位置,每当程序向 OutPutStream 或 Writer 里输出一个或多个水滴后,记录指针自动向后移动。
以上就是Java IO流的基本概念模型,除此之外,Java 的处理流模型则体现了 Java 输入/输出流设计的灵活性。处理流的功能主要体现在以下方面:
- 性能的提高:主要以增加缓冲的方式来提高输入/输出的效率。
- 操作的便捷:处理流可能提供了一系列便捷的方法来一次输入/输出大批量的内容,而不是输入/输出一个或多个水滴
处理流可以 嫁接 在任何已存在的流的基础之上,这就允许 java 应用程序采用相同的代码,透明的方式来访问不同的输入,输出设备的数据流。
通过使用处理流,Java 程序无须理会 输入/输出 节点是磁盘,网络还是其他的输入/输出设备,程序只要讲这些节点包装成处理流,就可以使用相同的输入/输出代码来读写不同的输入/输出设备的数据。
InputStream,Reader
InputStream和Reader 都是将输入数据抽象成一条水管,所以程序既可以通过 read() 方法每次读取一个 水滴,也可以通过 read(char[] cbuf)或rea(byte[] b)方法来读取多个 “水滴”。当使用数组作为 read() 方法的参数时,可以理解为使用一个 “竹筒”到水管去取水,而read(char[] cbuf)方法中的数组就相当于一个 “竹筒” ,程序每次调用输入流的 read(char[] cbuf)或read(byte[] b)方法,就相当于用 “竹筒”从输入流中取出一筒 ”水滴“,程序得到 “竹筒” 里的 “水滴” 后,转换成相应的数据即可;程序多次重复这个取水过程,直到 read(char[] cbuf)或read(byte[] b)方法返回-1,表明到了输入流的结束点。内部每次又使用指针移动取水的下标。
InputStream是所有输入字节流的父类,是一个抽象类,主要包含三个方法
//读取一个字节并以整数的形式返回(0~255),如果返回-1已到输入流的末尾。 int read() ; //读取一系列字节并存储到一个数组buffer,返回实际读取的字节数,如果读取前已到输入流的末尾返回-1。 int read(byte[] buffer) ; //读取length个字节并存储到一个字节数组buffer,从off位置开始存,最多len, 返回实际读取的字节数,如果读取前以到输入流的末尾返回-1。 int read(byte[] buffer, int off, int len) ;
Demo
public class Test { public static void main(String[] args) throws IOException { try{ //创建字节输入流 FileInputStream fis=new FileInputStream("E:\\Android\\Mvp_Test\\app\\src\\main\\java\\com\\example\\pettepr\\mvp_test\\aaa.txt"); //创建一个长度为32的字节数组 byte[] bbuf=new byte[32]; //用于保存实际读取的字节数 int hasRead=0; while ((hasRead=fis.read(bbuf))>0){ System.out.println(new String(bbuf,0,hasRead)); } }catch (IOException e){ e.printStackTrace(); } } }
解决中文乱码问题
//创建字节输入流 FileInputStream fis = new FileInputStream("E:\\Android\\Mvp_Test\\app\\src\\main\\java\\com\\example\\pettepr\\mvp_test\\aaa.txt"); InputStreamReader input = new InputStreamReader(fis, "GBK"); BufferedReader bbuf = new BufferedReader(input); String next; while ((next=bbuf.readLine())!=null) { System.out.println(next); }
InputStream与Reader最大不同,一个是操作字节,一个是操作字符
除了上面的方法之外,InputStream和Reader还支持如下方法来移动流中的指针位置:
//在此输入流中标记当前的位置 //readlimit - 在标记位置失效前可以读取字节的最大限制。 void mark(int readlimit) // 测试此输入流是否支持 mark 方法 boolean markSupported() // 跳过和丢弃此输入流中数据的 n 个字节/字符 long skip(long n) //将此流重新定位到最后一次对此输入流调用 mark 方法时的位置 void reset()
OutputStream 和 Writer
OutputStream 是所有的输出字节流的父类,它是一个抽象类,主要包含如下4个方法:
//向输出流中写入一个字节数据,该字节数据为参数b的低8位。 void write(int b) ; //将一个字节类型的数组中的数据写入输出流。 void write(byte[] b); //将一个字节类型的数组中的从指定位置(off)开始的,len个字节写入到输出流。 void write(byte[] b, int off, int len); //将输出流中缓冲的数据全部写出到目的地。 void flush();
Writer 是所有的输出字符流的父类,它是一个抽象类,主要包含如下六个方法:
//向输出流中写入一个字符数据,该字节数据为参数b的低16位。 void write(int c); //将一个字符类型的数组中的数据写入输出流, void write(char[] cbuf) //将一个字符类型的数组中的从指定位置(offset)开始的,length个字符写入到输出流。 void write(char[] cbuf, int offset, int length); //将一个字符串中的字符写入到输出流。 void write(String string); //将一个字符串从offset开始的length个字符写入到输出流。 void write(String string, int offset, int length); //将输出流中缓冲的数据全部写出到目的地。 void flush()
可以看出,Writer比OutputStream多出两个方法,主要是支持写入字符和字符串类型的数据。
Demo
public class Test { public static void main(String[] args) throws IOException { try{ //创建字节输入流 FileInputStream fis=new FileInputStream("E:\\Android\\Mvp_Test\\app\\src\\main\\java\\com\\example\\pettepr\\mvp_test\\Test.java"); //创建字节输出流 FileOutputStream fos=new FileOutputStream("E:\\Android\\Mvp_Test\\app\\src\\main\\java\\com\\example\\pettepr\\mvp_test\\aaa.txt",true); //true指将内容添加到原内容后面 byte[] buuf=new byte[32]; int hashRead=0; //循环从输入流中取出数据 while((hashRead=fis.read(buuf))>0){ fos.write(buuf,0,hashRead); } }catch (IOException e){ e.printStackTrace(); } }
使用Java的IO流执行输出时,不要忘记关闭输出流,关闭输出流除了可以保证流的物理资源被回收之外,还能将输出流缓冲区的数据flush到物理节点里(因为在执行close()方法之前,自动执行输出流的flush()方法)
处理流
处理流可以隐藏底层设备上节点流的差异,并对外提供更加方便的输入/输出方法,让程序员只需关心高级流的操作。
使用处理流时的典型思路是,使用处理流老包装节点流,程序通过处理流来执行输入输出功能,让节点流与底层的 I/O设备,文件交互。
它的优势为以下两点:
- 对开发人员来说,使用处理流进行输入、输出操作更简单
- 使用处理流效率更高
public class Test { public static void main(String[] args){ try{ FileOutputStream fos=new FileOutputStream("E:\\Android\\Mvp_Test\\app\\src\\main\\java\\com\\example\\pettepr\\mvp_test\\aaa.txt"); PrintStream ps=new PrintStream(fos); //使用PrintStream执行输出 ps.println("Petterp"); }catch (Exception e){ } } }
转换流
转换流用于实现将字节流转换成字符流。
IntputStreamReader 将字节输入流转换成字符输入流,OutputStreamWriter 将字节输出流转换成字符输出流。
为什么没有把字符流转成字节流的呢?
字节流比字符流的应用范围更广,但字符流比字节流操作方面。如果有一个流已经是字符流了,也就是说,是一个用起来更方便的流,为什么要转换成字节流呢?反之,如果现在有一个字节流,但可以确定这个字节流的内容都是文本内容,那么把他转为字符流来处理就会更方便一些,所以 java只提供了 将字节流转换成字符流的转换。
public class Test { public static void main(String[] args) throws IOException { //创建字节输入流 FileInputStream fis = new FileInputStream("E:\\Android\\Mvp_Test\\app\\src\\main\\java\\com\\example\\pettepr\\mvp_test\\aaa.txt"); InputStreamReader input = new InputStreamReader(fis, "GBK"); //将普通的 Reader包装成 BufferedReader BufferedReader bbuf = new BufferedReader(input); String next; //readLine() 每次读入一行 while ((next=bbuf.readLine())!=null) { System.out.println(next); } } }