一、 流
当数据到达计算机底层时,永远只会由一连串的0和1组成,而在其上运行的处理程序则有数据抽象化的功能,将这些由0和1组成的数据抽象成8个一组的字节,然后根据应用程序自己的需求解释其代表的含义,比如一个16字节的数组可看成是4个integer或2个double等等。流即是在字节层次上进行传输的,因而一般的流只负责传输字节,而不对其进行解释,当然还是有例外,比如过滤流(filter stream)即是一项对开发者较为贴心的设计。
1. 基本输入流(InputStream)
最开始的java.io中将流的功能抽象成InputStream和OutputStream两种基本的抽象类,尽管现在已经在I/O上大量使用API进行操作,若纯粹要传输字节还是可以选择直接继承这两类。InputStream类的函式大致可分出两种功能:读取和控制流,其中读取的功能read由3种函式重载,第一种为abstract的需要复写,每次读取一个字节并返回;另外两种则有默认的功能实现,分别能将读到的字节填入直接缓冲区及将读取到的字节接上先前已有字节储存的缓冲区中。read方法基本原理是读入一串字节填入缓冲区直到返回-1或是抛出java.io.IOException异常为止,停止后即进入阻塞状态,常造成程序性能严重滑落,缓冲区的使用则让read方法则能够使读入效能更好的实现。
至于控制流的相关功能则有:close,skip,mark,reset及available等等。close方法用于关闭流,然而书中建议以try-with-resources语句替代,避免直接调用。skip方法则能跳过指定字节,从特定的起点开始读取,然而开发者必须时时注意指定的字节数是否超出了流的范围才行。mark和reset功能常搭配着一起使用,可以用来重复读取读过的字节,透过reset起点到mark所标志的地方实现。最后,available方法能告诉使用者当前流中剩下可读取的字节数,透过read和available的搭配使用,read方法所造成的阻塞问题就能得到良好的解决,这样的方法搭配常见于大文件的数据读取。
2. 基本输出流(OutputStream)
OutputStream的写入功能主要靠write方法实现,该方法与read方法相同有3种重载形式:一次写入一个字节,将写入的字节填入缓冲区,将写入的字节解析昂接上先前已存在字节的缓冲区等等。至于流的控制功能除了关闭流的close方法外,flush方法会强制要求OutputStream对象将缓冲区的字节写入流中,一般来说flush方法会在OutputStream对象消灭前被调用,因此开发者不用自己调用这个方法。
3. 输入流的复用
实际应用中,应用程序常必须将InputStream对象发给一个以上的客户端(比如XML文档解析API),然而,InputStream本身代表的是数据的使用方式,而并非数据本身,而流的内容在被读过既无法回头,此时数据的复用就成了一大问题。现实中主要透过两种方法解决上述的问题:mark和reset的搭配使用以及将InputStream对象转换成数据来处理。第一种方法常透过继承自InputStream的BufferedInputStream来实现,其属于过滤流的一种,使用缓冲区提升性能,并加强了对mark和reset方法的支持实现,复用时只要记得在流的开始做个记号,然后读完之后在进行重置即可达成复用。
package com.java7.example.io;
import java.io.*;
public class StreamReuse {
private InputStream input; //build an InputStream object
public StreamReuse(InputStream input){
if(!input.markSupported()){
this.input=new BufferedInputStream(input); //build BufferedInputStream object if doesn't support mark function
} else {
this.input=input;
}
}
public InputStream getInputStream(){ //function to get the InputStream member
input.mark(Integer.MAX_VALUE);
return input;
}
public void markUsed() throws IOException{ //reset after reading process is over
input.reset();
}
}
package com.java7.example.io;
import java.io.*;
public class SavedStream {
private InputStream input; //build an InputStream object
private byte[] data = new byte[0]; //build the byte array to save the InputStream data
/*Remember to throw IOException*/
public SavedStream(InputStream input) throws IOException{
this.input = input;
save(); //save InputStream to byte array member 'data'
}
public void save() throws IOException{
ByteArrayOutputStream output = new ByteArrayOutputStream(); //build a ByteArrayInputStream object
byte[] buffer = new byte[1024]; //initialize the byte array with the max reading byte number
int len = -1; //build an int to save the current reading length
while((len = input.read(buffer)) != -1){ //update the 'len' and check if stream are read over
output.write(buffer,0,len); //write the bytes one by one into 'output'
}
data = output.toByteArray(); //write stream data into byte array
}
public InputStream getInputStream(){
return new ByteArrayInputStream(data); //create new InputStream object with ByteArrayInputStream
}
}
4. 过滤输入输出流
前面说到BufferedInputStream和BufferedOutputStream利用缓冲区提升读写操作性能。这里介绍另一组java.io提供同样强大的过滤流方法,DataInputStream和DataOutputStream,他们提供了将字节流转换成基本字节类型的支持,节省了开发者在不同平台中执行复杂的字节转换工作,DatInputStream可以透过readInt,readFloat,readUTF等方法实现,而DataOutputStream则可透过writeInt,writeFloat,writeUTF等方法实现。另外,为了保证数据类型的正确性,输入和输出必须相应同步进行才行。
5. 其他输入输出流方法
其他方法还有ObjectInputStream和ObjectOutputStream,他们实现了将对象内部状态写入流中以及从流中创建对象的功能;java.io.PushbackInputStream的unread方法实现了将一或多个未读取的字节放回流中,提供下次再利用;FileInputStream和FileOutputStream则实现了文件的输出输入读写;PipeInputStream和PipedOutputStream可透过构造方法或connect方法将输入输出流连接起来,需注意若在不同线程中使用可能造成死锁问题;SequenceInputStream和SequenceOutputStream则实现了输入流之间的相互连接功能。
6. 字符流
前面介绍了不少字节处理的类和方法,然而实际使用时,使用者更希望直接看见输出的字符讯息,于是Java也提供了java.io.Reader和java.io.Writer及其延伸出的子类来处理字符类型的流。
常见创建字符流的方法包括:InputStreamReader和OuputStreamWriter,使用时记得指定字符编码格式;再来是java.io.StringReader和java.io.StringWriter,可以从字串中建立字符流;若希望从字符数组中创建,可使用java.io.CharArrayReader和java.io.CharArrayWriter实现;Java同样也提供了java.io.BufferedReader和java.io.BufferedWriter类提升缓冲区的利用效率。
然而要注意的是,当我们在处理文本的时候,比如XML编码格式文档,使用建议字节流遵照原来既定编码格式处理,保证正确性。
补充一下,若要将输入流对象转换成String对象处理,第一种方法是想创建InputStream相应的InputStreamReader对象,将其内容循环读入char数组中,然后用char数组再创建一个java.lang.StringBuilder对象,最后将StringReader对象装换成String即可;若希望提高性能,可利用BufferedReader包装InputStreamReader,再用readLine方法一行行读取数据;同样的,java.util.Scanner以及apache.commons,io,IOUtils的toString方法都可达成类似功能。
流的介绍先到这里为止,下次再补上缓冲区的介绍。这是小弟第一次发博客,希望日后能持续利用这样的方式精进自己对Java的学习,如有相关问题欢迎互相交流、砥砺。