流
在Java API中,可以从其中读入一个字节序列的对象称做输入流,而可以向其中写入一个字节序列的对象称做输出流。抽象类InputStream 和 OutputStream 构成了输入/输出类层次结构的基础。
read 和 write 方法在执行时都将阻塞,直至字节确实被读入或写出。这就意味着流如果不能被立即访问,那么当前的线程将被阻塞。这使得在这两个方法等待指定的流变为可用的这段时间里,其他的线程就有机会去执行有用的工作。
available 方法使我们可以去检查当前可读入的字节数量,这意味着像下面这样的代码片段就不可能被阻塞:
int bytesAvailable = in. available();
if(bytesAvailable > 0)
{
byte[] data = new byte[bytesAvailable];
in.read(data);
}
当你完成对流的读写时,应该通过调用close方法来关闭它,这个调用会释放掉十分有用的操作系统资源。
完整的流家族
即使某个流类提供了使用原生的read 和 write 功能的某些具体方法,我们还是很少使用它们,因为大家感兴趣的数据可能包含数字、字符串和对象,而不是原生字节。
Java提供了众多从基本的InputStream 和 OutputStream 类导出的类,这些类使我们可以处理那些以常用格式表示的数据,而不是字节。
如果按照它们的使用方法来进行划分,这样就形成了处理字节和字符的两个单独的层次结构。InputStream 和 OutputStream 类可以读写单个字节或字节数组。要想读写字符串和数字,就需要功能更强大的子类。
对于Unicode 文本 ,可以使用抽象类 Reader 和 Writer 的子类。有关Unicode的详细解释:Unicode字符集的编码方式以及码点、码元
还有四个附加的接口:Closeable、Flushable、Readable和Appendable。
Closeable 接口拥有close() 方法,抛出IOException,该接口拓展了 java.lang.AutoCloseable 接口。因此对任何 Closeable 进行操作时,都可以使用 try-with-resource 语句(声明了一个或多个资源的try 语句)。InputStream、OutputStream、Reader和Writer都实现了Closeable 接口。
Flushable 接口拥有 flush() 方法。OutputStream 和 Writer 实现了该接口。
Readable 接口只有一个方法: int read(CharBuffer cb) //CharBuffer 拥有按顺序和随机地进行读写访问的方法
Appendable 接口有两个用于添加单个字符和字符序列的方法:
Appendable append(char c)
Appendable append(CharSequence s) //描述了一个char值序列的基本属性
组合流过滤器
FileInputStream 和 FileOutputStream 可以提供附着在一个磁盘文件上的输入流和输出流,而你只需向其构造器提供文件名或文件的完整路径名。例如:FileInputStream fin = new FileInputStream("example.txt");
* 所有在java.io中的类都将相对路径名解释为以用户工作目录开始,可以通过调用 System.getProperty(“use.dir”) 来获得这个信息;并且还可以通过常量字符串java.io.File.separator 来获得应用程序所运行平台的文件分隔符。*
如果我们只有DataInputStream ,那么我们就只能读入数值类型:
DataInputStream din = ...; double s = din.readDouble();
正如FileInputStream 没有任何读入数值类型的方法一样,DataInputStream 也没有任何从文件中获取数据的方法。
我们必须对二者进行组合。例如,为了从文件中读入数字,首先,需要创建一个FileInputStream ,然后将其传递给DataInputStream的构造器:
FileInputStream fin = new FileInputStream("...");
DataInputStream din = new DataInputStream(fin);
double s = din.readDouble();
如果观察流的类结构图,你就会看到FilterInputStream 和 FilterOutputStream类。这些文件的子类用于向原生字节流添加额外的功能。如果我们想使用缓冲机制,以及用于文件的数据输入方法,那么就需要使用下面这样的构造器序列:
DataInputStream din = new DataInputStream(
new BufferedInputStream(
new FileInputStream("...")));
当读入输入时,你经常需要浏览下一个字节,以了解它是否是你想要的值。Java 提供了用于此目的的PushbackInputStream:
PushbackInputStream pbin = new PushbackInputStream(
new BufferedInputStream(
new FileInputStream("...")));
//预读下一个字节
int b = pbin.read();
//并非你所期望的值时将其推回流中
if(b != '<' )
pbin.unread(b);
如果你希望能够预先浏览并且还可以读入数字,那么你就需要一个既是可回推输入流,又是一个数据输入流的引用。
DataInputStream din = new DataInputStream(
pbin = new PushbackInputStream(
new BufferedInputStream(
new FileInputStream("..."))));
这种混合并匹配过滤器类以构建真正有用的流序列的能力,将带来极大的灵活性。
文本输入与输出
在保存数据时,可以选择二进制格式或文本格式。
在存储文本字符串时,需要考虑字符编码方式。
OutputStreamWriter 类将使用选定的字符编码方式,把Unicode 字符流 转换为字节流。而InputStreamReader 类将包含字节(用某种字符编码方式表示的字符) 的输入流转换为可以产生Unicode码元的读入器。
例如,下面的代码就展示了如何让一个输入读入器可以从控制台读入键盘敲击信息,并将其转换为Unicode:
InputStreamReader in = new InputStreamReader(System.in); //该输入流读入器会假定使用主机系统所使用的默认字符编码方式
可以通过在InputStreamReader 的构造器中进行指定的方式来选择不同的编码方式。例如:
InputStreamReader in = new InputStreamReader(new FileInputStream("..."),"ISO8859_5")
如何写出文本输出
对于文本输出,可以使用PrintWriter。这个类拥有以文本格式打印字符串和数字的方法,下面的语句:
PrintWriter out = new PrintWriter("example.txt");
等同于:
PrintWriter out = new PrintWriter(new FileWriter("example.txt"));
为了输出到打印写出器,需要使用与使用System.out 时相同的print、println和printf 方法。例如:
out.print("helloworld"); out.println("helloworld"); //它将把这些字符输出到写出器out,之后这些字符将会被转换成字节并最终写入example.txt中。
如果写出器设置为自动冲刷模式,那么只要println 被调用,缓冲区中的所有字符都会被发送到它们的目的地(打印写出器总是带缓冲区的)。默认情况下,自动冲刷机制是禁用的,你可以通过使用PrintWriter(Writer out,Boolean autoFlush)来启用或禁用自动冲刷机制:
PrintWriter out = new PrintWriter(new FileWriter("example.txt"),true); //需要捕获创建FileWriter对象抛出的IOException
import java.io.*;
public class PrintWriterTest{
public static void main(String[] args) throws IOException{
PrintWriter out = new PrintWriter(new FileWriter("hello.txt"),true);
out.print("1");
out.println("2");
out.print("3");
out.print("4");
// out.flush(); 当并没有设置自动冲刷为true时,需要手动的调用该方法
}
}
//运行结果为:
12 //hello.txt ,
//分析:由于在print("3") 和 print("4") 之后,没有调用println,所以无法将其发送到目的地。
如何读入文本输入
我们广泛使用的类是Scanner。
Scanner in = new Scanner(System.in); int i = in.nextInt();
但是,在Java SE 5.0之前,处理文本输入的唯一方式就是通过BufferReader 类,它拥有一个readLine 方法,使得我们可以读入一行文本。你需要将带缓冲器的读入器与输入源组合起来:
BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream("..."),"UTF-8"));
String line;
while((line = in.readLine())!=null){
...
}
// BufferedReader 没有任何用于读入数字的方法
字符集
在Java SE 1.4 中引入的java.nio包用Charset 类统一了对字符集的转换。
可以通过调用静态的forName方法来获得一个Charset,只需向这个方法传递一个官方名字或是它的某个别名:
Charset cset = Charset.forName("ISO-8859-1");
编码Java字符串:
String str = ...;
ByteBuffer buffer = cset.encode(str);
byte[] bytes = buffer.array();
解码字节序列:
byte[] bytes = ... ;
ByteBuffer bbuf = ByteBuffer.wrap(bytes,offset,length); //将一个字节数组转换成一个字节缓冲区。
CharBuffer cbuf = cset.decode(bbuf);
String str = cbuf.toString();
读写二进制数据
DataOutput 接口定义了用于以二进制格式写数组、字符、boolean值和字符串的方法。
DataInputStream类实现了DataInput接口,为了从文件中读入二进制数据,可以将DataInputStream与某个字节源相结合:
DataInputStream in = new DataInputStream(new FileInputStream("..."));
与此类似,要想写出二进制数据,可以使用实现了DataOutput接口的DataOutputStream类:
DataOutputStream out = new DataOutputStream(new FileOutputStream("..."));
随机访问文件
RandomAccessFile 类可以在文件中的任何位置查找或写入数据。磁盘文件都是随机访问的,但是从网络而来的数据流却不是。可以打开一个随机访问文件,只用于读入或者同时用于读写:
RandomAccessFile in = new RandomAccessFile("...","r");
RandomAccessFile in = new RandomAccessFile("...","rw");
当你将已有文件作为RandomAccessFile 打开时,这个文件并不会删除。随机访问文件有一个表示下一个将被读入或写出的字节所处位置的文件指针,seek方法可以将这个文件指针设置到文件中的任意字节位置,RandomAccessFile 类同时实现了DataInput 和 DataOutput 接口。
ZIP文档
ZIP文档通常以压缩格式存储了一个或多个文件。在Java中,可以使用ZipInputStream 来读入ZIP文档。你可能需要浏览文档中每个单独的项,getNextEntry方法就可以返回一个描述这些项的ZipEntry 类型的对象。ZipInputStream 的read 方法被修改为在碰到当前项的结尾时返回 -1,然后你调用closeEntry 来读入下一项。下面是典型的通读ZIP文件的代码序列:
ZipInputStream zin = new ZipInputStream(new FileInputStream(zipname));
ZipEntry entry;
while((entry = zip.getNextEntry()) != null){
...
zip.closeEntry();
}
zip.close();
写出到ZIP文件:
FileOutputStream fout = new FileOutputStream("...");
ZipOutputStream zout = new ZipOutputStream(fout);
//对于所有你希望放入到ZIP文件中的每一项
ZipEntry ze = new ZipEntry(filename);
zout.putNextEntry(ze);
...
zout.closeEntry();
zout.close();
以上来源于Java 核心技术卷Ⅱ (原书第九版)