OutputStream也是抽象类,它是所有输出流的超类,这个抽象类定义的一个最重要的方法就是void write(int b)

  1. public abstract void write(int b) throws IOException;

这个方法会写入一个字节到输出流,虽然传入的参数是int参数,但是只会写入一个字节,即只写入int最低8位表示字节的部分(相当于 b & 0xff

InputStream类似,OutputStream也提供了close()方法关闭输出流,以便释放系统资源。

OutpuStream还提供了一个flush()方法,它的目的是将缓冲区的内容真正输出到目的地。

向磁盘、网络写入数据的时候,出于效率的考虑,操作系统并不是输出一个字节就立刻写入到文件或者发送到网络,而是把输出的字节先放到内存的一个缓冲区里(本质上就是一个bytep[]数组),等到缓冲区写满了,再一次性写入文件或者网络。对于很多IO设备来说,一次写一个字节和一次写1000个字节,花费的时间几乎是完全一样的,所以OutputStream有个flush()方法,能强制把缓冲区内容输出。

通常情况下,我们不需要调用这个flush()方法,因为缓冲区写满了OutputStream会自动调用它,并且,在调用close()方法关闭OutpuStream之前,也会自动调用flush()方法。
但是某些情况下,我们必须手动调用flush()方法。

小明正在开发一款在线聊天软件,当用户输入一句话后,就通过OutpuStreamwrite()方法写入网络流。 小明测试的时候发现,发送方输入后,接收方根本收不到任何信息,怎么肥四?原因就在于写入网络流是先写入内存缓冲区,等缓冲区满江了才会一次性发送到网络。如果缓冲区大小是4K,则发送方要敲几千个字符后,操作系统才会把缓冲区的内容发送出去,这个时候,接收方会一次性收到大量消息。 解决办法就是每输入一句话后,立刻调用flush(),不管当前缓冲区是否已满,强迫操作系统把缓冲区的内容立刻发送出去。

实际上,InputStream也有缓冲区。
例如从FileInputStream读取一个字节时,操作系统往往会一次性读取若干字节到缓冲区,并维护一个指针指向未读的缓冲区。然后,每次我们调用int read()读取下一个字节时,可以直接返回缓冲区的下一个字节,避免每次读一个字节都导致IO操作。当缓冲区全部读完后,继续调用read(),则会触发操作系统的下一次读取并再次填满缓冲区。

FileOutputStream

写入文件流

  1. public void writeFile() throws IOException {
  2. OutputStream output = new FileOutputStream("src/readme.txt");
  3. output.write(72);
  4. output.write(101);
  5. output.write(108);
  6. output.write(108);
  7. output.write(111);
  8. output.close();
  9. }

每次写入一个字节非常麻烦,更常见的是一次性写入若干字节。这里,可以用OutpuStream提供的重载方法void wrte(bytep[])来实现

  1. public void wrteFile() throws IOException {
  2. OutputStream output = new FileOutputStream("src/readme.txt");
  3. output.write("Hello".getBytes("UTF-8"));
  4. output.close();
  5. }

InputStream一样,我们需要用try(resource)来保证OutputStream在无论是否发生IO错误的时候都能够正确地关闭。

  1. public void writeFile() throws IOException{
  2. try(OutputStream output = new FileOutputStream("src\\readme.txt")){
  3. output.write("Hello".getBytes("UTF-8"));
  4. }
  5. }

阻塞

InputStream一样,OutputStreamwrite()方法也是阻塞的。

OuputStream实现类

FileOutputStream可以从文件获取输出流,这是OutpuStream常用的一个实现类。此外,`ByteArrayOutpuStream 可以在内存中模拟一个OutputStream

  1. public class Main {
  2. public static void main(String[] args) throws IOException{
  3. byte[] data;
  4. try(ByteArrayOutputStream output = new ByteArrayOutputStream()){
  5. output.wirte("Hello".getBytes("UTF-8"));
  6. output.write("world!".getBytes("UTF-8"));
  7. data = output.toByteArray();
  8. }
  9. Sytem.out.println(new String(data, "UTF-8"));
  10. }
  11. }

ByteArrayOutputStream实际上是把一个byte[]数组在内存中变成一个OutputStream,虽然实际应用不多,但测试的时候,可以用它来构造一个OutputStream
同时操作多个AutoCloseable资源时, 在try(resource) {}语句中可以同时写出多个资源,用;分隔。

  1. try(InputStream input = new FileInputStream();
  2. OutputStream output = new FileOutputStream()){
  3. input.transferTo(output);
  4. }

练习

利用InputStreamOutputStream,编写一个复制文件的程序,它可以带参数运行

  1. public class Main {
  2. public static void main(String[] args){
  3. if(args.length != 2){
  4. System.exit(1);
  5. }
  6. copyFileTo(args[0],args[1]);
  7. }
  8. public static void copyFileTo(String source,String target){
  9. try(InputStream input = new FileInputStream(source);
  10. OutputStream output = new FIleOutputStream(target)){
  11. int n;
  12. byte[] bytes = new byte[1000];
  13. while ( (n = input.read(bytes)) != -1) {
  14. output.write(bytes,0,n);
  15. }
  16. }
  17. }
  18. }

小结

Java标准库的java.io.OutputStream定义了所有输出流的超类

  • FileOutputStream实现了文件流输出
  • ByteArrayOutpuStream在内存中模拟一个字节流输出

某些情况下需要手动调用 OutputStreamflush()方法强制输出缓冲区
总是使用try(resource)来保证OutputStream正确关闭