InputStream和OutputStream是Java标准库中最基本的IO流,它们都位于java.io包中,该包提供了所有同步IO的功能。
二者都不是接口,而是抽象类,它们分别是所有输入流和输出流的超类。这两个抽象类定义了两个最重要的方法:
public abstract int read() throws IOException; public abstract void write(int b) throws IOException;
下面的代码演示了如何完整地读取一个FileInputStream的所有字节:
public void readFile throws IOException{ //创建一个FileInputStream对象 InputStream input = new FileInputStream("src/readme.txt"); for(;;){ int n = input.read(); //反复调用read(),直到返回-1 if(n==-1){ break; } System.out.println(n);//打印byte值 } input.close(); }
在计算机中,应用程序如果打开了有一个文件进行读写,完成后要及时关闭,以便OS把资源释放。
下面代码演示了如何将多个bytes写入文件流:
public void writeFile() throws IOException{ OutputStream output = new FileOutputStream("out/readme.txt"); output.write(72);//H ... output.write(111);//o output.close(); }
每次写入1byte太麻烦,更常见的是一次性写入多个bytes。可以通过OutputStream提供的重载方法void write( byte[ ] )来实现:
public void writeFile() throws IOException{ OutputStream output = new FileOutputStream("out/readme.txt"); output.write("Hello".getBytes("UTF-8")); output.close(); }
在操作完之后,InputStream和OutputStream都需要通过close()来关闭流,之后OS会释放底层资源。
只用于OutputStream,目的是将buffer内容真正输出到目的地。
因为写的时候,出于效率考虑,OS不是每次直接把1 byte写入文件或发送到网络,而是先放到内存buffer(本质上是byte [ ]数组),等到buffer写满,再一次性写入文件或网络。
对所有IO设备来说,一次写1B或1000B,花费的时间几乎相同,所以OutputStream有flush()方法,能强制把buffer内容输出。
通常情况下,我们不需要调用这个flush(),因为①buffer写满了OutputStream会自动调用它;②调用close()关闭OutputStream之前,也会自动调用flush()方法。
但是某些时机我们需要手动调用flush(),比如在实现一个即时通讯软件时,用户发一句就要flush一句,而不能等待buffer满。
InputStream也有buffer。当从FileInputStream读取1 byte时,OS往往会一次性读取多个 bytes到buffer,read时直接返回buffer中的byte,而不是每次都IO 1 byte。
读写IO流时,可能发生错误,例如文件不存在、权限异常,它们由JVM封装为IOException抛出。
因此,所有与IO相关的代码都必须处理IOException。
实际使用时,为了避免读取时发生IO错误,IO流无法正确关闭,资源也无法及时释放,所以我们要用try...finally来保证IO流正确关闭:
public void readFile() throws IOException { InputStream input = null; try { input = new FileInputStream("src/readme.txt"); int n; while ((n = input.read()) != -1) { System.out.println(n); } } finally { if (input != null) { input.close(); } } }
用try...finally来编写会感觉复杂,更好的写法是利用Java 7引入新的try(resource)语法,只需要编写try语法,就能让编译器自动为我们关闭资源。推荐写法如下:
//InputStream public void readFile() throws IOException{ try(InputStream input = new FileInputStream("src/readme.txt")){ int n; while((n=input.read())!=-1) System.out.println(n); }//编译器在此自动为我们写入finally并调用close() } //OutputStream public void writeFile() throws IOException{ OutputStream output = new FileOutputStream("out/readme.txt"); output.write("Hello".getBytes("UTF-8"));//Hello output.close(); }
同时操作多个资源时,在try(resource){ ... }中可以同时写出多个资源,用;隔开。例如同时读写两个文件:
//读取input.txt,写入output.txt try(InputStream input = new FileInputStream("input.txt"); OutputStream output = new FileOutputStream("output.txt")){ input.transferTo(output); }
不过实际上,编译器并不会特别为InputStream加上自动关闭。只看resource是否实现了java.lang.AutoCloseable接口,如果实现了,就自动加上finally并调用close()方法。InputStream、OutputStream都实现了这个接口,因此都可以用在try( resoucrce )中。
IO流中,每次用read()、write()读写1 byte未免太麻烦了。IO流各提供了两个重载方法来支持读写多个字节:
利用read方法一次读取多个bytes时,需要先定义一个byte[ ]作为buffer,read会尽可能多的读取byte到buffer,但不会超过buffer大小。read返回实际读取的byte数,如果返回-1,表示没有更多的数据了。
用buffer一次读取多个bytes的代码如下:
public void readFile() throws IOException{ try(InputStream input = new FileInputStream("src/readme.txt")){ byte[] buffer = new byte[1000]; int n; while((n=input.read(buffer))!=-1)//读取到buffer System.out.println("read "+n+" bytes."); } }
之前我们说过java.io是同步io,在进行IO时,会中断程序运行,直到数据IO完毕——此之谓阻塞。
用FileInputStream可以从文件获取输入流,这是InputStream常用的一个实现类。
在内存中模拟一个InputStream:
public class Main { public static void main(String[] args) throws IOException { byte [] data ={ 72, 101, 108, 108, 111, 33 }; try(InputStream input = new ByteArrayInputStream(data)){ int n; while((n=input.read())!=-1) System.out.println((char)n); } } }
ByteArrayInputStream实际上是把一个byte[ ]数组在内存中变成一个InputStream,虽然应用不多,但测试时可以用来构造一个InputStream。
在内存中模拟一个OutputStream:
public class Main { public static void main(String[] args) throws IOException { byte[] data; try (ByteArrayOutputStream output = new ByteArrayOutputStream()) { output.write("Hello".getBytes("UTF-8")); output.write("world!".getBytes("UTF-8")); data = output.toByteArray(); } System.out.println(new String(data, "UTF-8")); } }