• java IO之输出流——OutputStream


    OutputStream抽象类是所有输出字节流的超类,输出流接收输出字节,并将这些字节发送到某个接收器。这个接收器可以是字节数组、文件、管道。该类的定义如下:

     1 public abstract class OutputStream implements Closeable, Flushable {
     2     //将指定的字节写到这个输出流中
     3     public abstract void write(int b) throws IOException;
     4     //将指定字节数组中的内容写到输出流中
     5     public void write(byte b[]) throws IOException {
     6         write(b, 0, b.length);
     7     }
     8     public void write(byte b[], int off, int len) throws IOException {
     9         if (b == null) {
    10             throw new NullPointerException();
    11         } else if ((off < 0) || (off > b.length) || (len < 0) ||
    12                    ((off + len) > b.length) || ((off + len) < 0)) {
    13             throw new IndexOutOfBoundsException();
    14         } else if (len == 0) {
    15             return;
    16         }
    17         for (int i = 0 ; i < len ; i++) {
    18             write(b[off + i]);
    19         }
    20     }
    21     //清理输出流中的数据,迫使缓冲的字节写出去
    22     public void flush() throws IOException {
    23     }
    24     //关闭流
    25     public void close() throws IOException {
    26     }
    27 }
    View Code

    输出字节流的类结构图如下,同样,这里只列举常用的几个类,还有很多未被列出。

     下面对不同的输出流进行简单的分析,会给出相应的类源码和示例。

    1、ByteArrayOutputStream,字节数组输出流,此类实现了一个输出流,其中的数据被写入一个 byte 数组。缓冲区会随着数据的不断写入而自动增长。可使用 toByteArray()toString() 获取数据。 源代码如下:

     1 import java.util.Arrays;
     2 public class ByteArrayOutputStream extends OutputStream {
     3     //定义一个用于存储输出数据的缓冲数组
     4     protected byte buf[];
     5     protected int count;
     6     public ByteArrayOutputStream() {
     7         this(32);
     8     }
     9     public ByteArrayOutputStream(int size) {
    10         if (size < 0) {
    11             throw new IllegalArgumentException("Negative initial size: "+ size);
    12         }
    13         buf = new byte[size];
    14     }
    15     private void ensureCapacity(int minCapacity) {
    16         // overflow-conscious code
    17         if (minCapacity - buf.length > 0)
    18             grow(minCapacity);
    19     }
    20     private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
    21     //扩展缓冲数组大小
    22     private void grow(int minCapacity) {
    23         // overflow-conscious code
    24         int oldCapacity = buf.length;
    25         int newCapacity = oldCapacity << 1;
    26         if (newCapacity - minCapacity < 0)
    27             newCapacity = minCapacity;
    28         if (newCapacity - MAX_ARRAY_SIZE > 0)
    29             newCapacity = hugeCapacity(minCapacity);
    30         buf = Arrays.copyOf(buf, newCapacity);
    31     }
    32     private static int hugeCapacity(int minCapacity) {
    33         if (minCapacity < 0) // overflow
    34             throw new OutOfMemoryError();
    35         return (minCapacity > MAX_ARRAY_SIZE) ?
    36             Integer.MAX_VALUE :
    37             MAX_ARRAY_SIZE;
    38     }
    39     //将数字b写到缓冲数组中
    40     public synchronized void write(int b) {
    41         ensureCapacity(count + 1);
    42         buf[count] = (byte) b;
    43         count += 1;
    44     }
    45     public synchronized void write(byte b[], int off, int len) {
    46         if ((off < 0) || (off > b.length) || (len < 0) ||
    47             ((off + len) - b.length > 0)) {
    48             throw new IndexOutOfBoundsException();
    49         }
    50         ensureCapacity(count + len);
    51         System.arraycopy(b, off, buf, count, len);
    52         count += len;
    53     }
    54     //将此 byte 数组输出流的全部内容写入到指定的输出流参数中,
    55     //这与使用 out.write(buf, 0, count) 调用该输出流的 write 方法效果一样。    
    56     public synchronized void writeTo(OutputStream out) throws IOException {
    57         out.write(buf, 0, count);
    58     }
    59     public synchronized void reset() {
    60         count = 0;
    61     }
    62     //将输出的内容以字符数组的形式返给用户
    63     public synchronized byte toByteArray()[] {
    64         return Arrays.copyOf(buf, count);
    65     }
    66     public synchronized int size() {
    67         return count;
    68     }
    69     //将输出的内容以字符串的形式返给用户
    70     public synchronized String toString() {
    71         return new String(buf, 0, count);
    72     }
    73     //将输出的内容以以指定字符编码的字符串形式返给用户
    74     public synchronized String toString(String charsetName)
    75         throws UnsupportedEncodingException
    76     {
    77         return new String(buf, 0, count, charsetName);
    78     }
    79     @Deprecated
    80     public synchronized String toString(int hibyte) {
    81         return new String(buf, hibyte, 0, count);
    82     }
    83     public void close() throws IOException {
    84     }
    85 }
    View Code

    从源码可以看出,涉及到数据操作的方法都加了synchronized关键字,所以该类是安全同步的类。使用方法如下:

     1 static void byteArrayOutputTest(){
     2         ByteArrayOutputStream out=new ByteArrayOutputStream(8);
     3         try{
     4             while(out.size()!=8){
     5                 out.write(System.in.read());
     6             }
     7             for(byte by:out.toByteArray()){
     8                 System.out.print((char)by+" ");
     9             }
    10             System.out.println();
    11         }catch(Exception e){
    12             e.printStackTrace();
    13         }        
    14     }
    View Code

    我们创建了一个字节数组输出流,它的缓冲容量大小为8,然后我们从控制台进行输入,输入的时候可以不加空格,如果添加空格,空格也计数在内,可以输入多个字符,但最后输出的字符数只有8个,因为我们已经指定了缓冲容量的大小,当用toByteArray()方法取出数据时,它返回的字符数组长度为8.

    2、FileOutputStream,文件输出流,它是将数据输出到文件中,注意这里操作对象——文件的可用与否与平台有关,某些平台一次只允许一个 FileOutputStream(或其他文件写入对象)打开文件进行写入。在这种情况下,如果所涉及的文件已经打开,则此类中的构造方法将失败。

      1 import java.nio.channels.FileChannel;
      2 import sun.nio.ch.FileChannelImpl;
      3 public class FileOutputStream extends OutputStream
      4 {
      5     private final FileDescriptor fd;
      6     private final boolean append;
      7     private FileChannel channel;
      8     private final String path;
      9     private final Object closeLock = new Object();
     10     private volatile boolean closed = false;
     11     //创建文件输出流,如果文件不存在,则自动创建文件
     12     public FileOutputStream(String name) throws FileNotFoundException {
     13         this(name != null ? new File(name) : null, false);
     14     }
     15     //创建文件输出流,如果文件不存在,则自动创建文件
     16     //同时指定文件是否具有追加内容的功能,如果有,则新添内容放到文件后面,而不覆盖源文件内容
     17     public FileOutputStream(String name, boolean append)
     18         throws FileNotFoundException
     19     {
     20         this(name != null ? new File(name) : null, append);
     21     }
     22     public FileOutputStream(File file) throws FileNotFoundException {
     23         this(file, false);
     24     }
     25     //创建文件输出流,并指定可以在文件最后追加内容,如果没有指定,则将原来内容覆盖
     26     public FileOutputStream(File file, boolean append)
     27         throws FileNotFoundException
     28     {
     29         String name = (file != null ? file.getPath() : null);
     30         SecurityManager security = System.getSecurityManager();
     31         if (security != null) {
     32             security.checkWrite(name);
     33         }
     34         if (name == null) {
     35             throw new NullPointerException();
     36         }
     37         if (file.isInvalid()) {
     38             throw new FileNotFoundException("Invalid file path");
     39         }
     40         this.fd = new FileDescriptor();
     41         fd.attach(this);
     42         this.append = append;
     43         this.path = name;
     44         open(name, append);
     45     }
     46     public FileOutputStream(FileDescriptor fdObj) {
     47         SecurityManager security = System.getSecurityManager();
     48         if (fdObj == null) {
     49             throw new NullPointerException();
     50         }
     51         if (security != null) {
     52             security.checkWrite(fdObj);
     53         }
     54         this.fd = fdObj;
     55         this.append = false;
     56         this.path = null;
     57         fd.attach(this);
     58     }
     59     private native void open0(String name, boolean append)
     60         throws FileNotFoundException;
     61     private void open(String name, boolean append)
     62         throws FileNotFoundException {
     63         open0(name, append);
     64     }
     65     //调用本地方法,将内容写入指定文件
     66     private native void write(int b, boolean append) throws IOException;
     67     public void write(int b) throws IOException {
     68         write(b, append);
     69     }
     70     private native void writeBytes(byte b[], int off, int len, boolean append)
     71         throws IOException;
     72     //将字符数组内容写进文件
     73     public void write(byte b[]) throws IOException {
     74         writeBytes(b, 0, b.length, append);
     75     }
     76     public void write(byte b[], int off, int len) throws IOException {
     77         writeBytes(b, off, len, append);
     78     }
     79     //关闭文件流
     80     public void close() throws IOException {
     81         synchronized (closeLock) {
     82             if (closed) {
     83                 return;
     84             }
     85             closed = true;
     86         }
     87         if (channel != null) {
     88             channel.close();
     89         }
     90 
     91         fd.closeAll(new Closeable() {
     92             public void close() throws IOException {
     93                close0();
     94            }
     95         });
     96     }
     97      public final FileDescriptor getFD()  throws IOException {
     98         if (fd != null) {
     99             return fd;
    100         }
    101         throw new IOException();
    102      }
    103     public FileChannel getChannel() {
    104         synchronized (this) {
    105             if (channel == null) {
    106                 channel = FileChannelImpl.open(fd, path, false, true, append, this);
    107             }
    108             return channel;
    109         }
    110     }
    111     //清除文件流缓冲内容,并关闭文件流
    112     protected void finalize() throws IOException {
    113         if (fd != null) {
    114             if (fd == FileDescriptor.out || fd == FileDescriptor.err) {
    115                 flush();
    116             } else {
    117                 close();
    118             }
    119         }
    120     }
    121     private native void close0() throws IOException;
    122     private static native void initIDs();
    123     static {
    124         initIDs();
    125     }
    126 }
    View Code

    文件输出流操作不是线程安全的,如果用于多线程访问,注意使用同步。以下是文件输出流操作的例子:

     1 //文件输出流测试
     2     static void fileOutputTest(){
     3         FileOutputStream fout=null;
     4         FileInputStream fin=null;
     5         try{
     6             //从my.java文件中读取内容
     7             fin=new FileInputStream("my.java");
     8             fout=new FileOutputStream("out.txt");
     9             byte[] buff=new byte[1024];
    10             while(fin.read(buff)>0){
    11                 //FileInputStream将从my.java文件中读取到的内容写到buff数组中
    12                 //然后FileOutputStream将buff数组中的内容写到流中
    13                 fout.write(buff);
    14             }//将流中缓冲的内容输出到文件中
    15             fout.flush();
    16         }catch(Exception e){
    17             e.printStackTrace();
    18         }finally{
    19             try{
    20                 if(fout!=null)
    21                     fout.close();
    22             }catch(Exception e){
    23                 e.printStackTrace();
    24             }
    25         }
    26     }
    View Code

    为了减少操作,这里使用了文件输入流对象,我们从my.java文件中读取内容,然后将读取到的内容写到out.txt文件中,从my.java文件中读取内容要用输入流,向文件中写内容要用输出流,这里两种流都做了使用。

    3、FilterOutputStream,该类是提供输出流的装饰器类的接口,继承该类的子类相当于一个装饰器,能够为OutputStream类型的对象操作提供额外的功能,这里以BufferedOutputStream为例,该类实现缓冲的输出流。通过设置这种输出流,应用程序就可以将各个字节写入底层输出流中,而不必针对每次字节写入调用底层系统。 比如,我们需要向一个文件中输入内容,这时候我们可以先将内容存储到缓冲数组中,并不是真的向该文件写内容,当调用flush()方法或关闭流时,内容才真正写到文件中。该类的源码如下:

     1 public class BufferedOutputStream extends FilterOutputStream {
     2     //内部存储数据的缓冲数组
     3     protected byte buf[];
     4      //缓冲中的有效字节数目
     5     protected int count;
     6     //创建一个缓冲输出流,将时数据写到特定的底层输出流中
     7     public BufferedOutputStream(OutputStream out) {
     8         this(out, 8192);
     9     }
    10     //创建一个缓冲输出流,将时数据写到具有特定大小容量的特定的底层输出流中
    11     public BufferedOutputStream(OutputStream out, int size) {
    12         super(out);
    13         if (size <= 0) {
    14             throw new IllegalArgumentException("Buffer size <= 0");
    15         }
    16         buf = new byte[size];
    17     }
    18     //将缓冲中的数据清理出去,输出到目的地
    19     private void flushBuffer() throws IOException {
    20         if (count > 0) {
    21             out.write(buf, 0, count);
    22             count = 0;
    23         }
    24     }
    25     //将指定的字节写到缓冲输出流中
    26     public synchronized void write(int b) throws IOException {
    27         if (count >= buf.length) {
    28             flushBuffer();
    29         }
    30         buf[count++] = (byte)b;
    31     }
    32     //从指定位置off开始,将b数组内的len个字节写到缓冲输出流中
    33     //一般来说,此方法将给定数组的字节存入此流的缓冲区中,根据需要将该缓冲区刷新,并转到底层输出流。
    34     //但是,如果请求的长度至少与此流的缓冲区大小相同,则此方法将刷新该缓冲区并将各个字节直接写入底层输出流。因此多余的 BufferedOutputStream 将不必复制数据。 
    35     public synchronized void write(byte b[], int off, int len) throws IOException {
    36         if (len >= buf.length) {
    37             flushBuffer();
    38             out.write(b, off, len);
    39             return;
    40         }
    41         if (len > buf.length - count) {
    42             flushBuffer();
    43         }
    44         System.arraycopy(b, off, buf, count, len);
    45         count += len;
    46     }
    47     //刷新此缓冲的输出流。这迫使所有缓冲的输出字节被写出到底层输出流中。 
    48     public synchronized void flush() throws IOException {
    49         flushBuffer();
    50         out.flush();
    51     }
    52 }
    View Code

    从源码来看,可以得知,该类也是线程同步的,所以在多线程环境下能够保证访问的正确性。下面给出该类的一个示例:

     1 //缓冲输出流测试
     2     static void bufferedOutputTest(){
     3         BufferedOutputStream bof=null;
     4         BufferedInputStream bin=null;
     5         FileOutputStream fout=null;
     6         FileInputStream fin=null;
     7         byte[] buff=new byte[1024];
     8         try{
     9             fin=new FileInputStream("my.java");
    10             bin=new BufferedInputStream(fin);
    11             fout=new FileOutputStream("out.txt");
    12             bof=new BufferedOutputStream(fout);
    13             while(fin.read(buff)>0){
    14                 bof.write(buff,0,buff.length);
    15             }
    16             //如果将下面这一行注释掉,而且有没有将bof关掉,则out.txt文件中不会有内容出现
    17             //因为内容还在缓冲中,还未并没有输出到文本上
    18             bof.flush();
    19         }catch(Exception e){
    20             e.printStackTrace();
    21         }finally{
    22             try{
    23                 if(bof!=null)
    24                     bof.close();
    25                 if(fout!=null)
    26                     fout.close();
    27                 if(fin!=null)
    28                     fin.close();
    29             }catch(Exception e){
    30                 e.printStackTrace();
    31             }
    32         }
    33     }
    View Code

    该示例同样用了输入流,为了操作方便,我们直接向一个文件中读取数据,所以要用输入流,然后将读取到的数据输出到另一个文件中,如果out.txt文件不存在,则会自动创建该文件,在输出数据时,可以利用flush函数及时将缓冲数组中的内容输出到文件中。

    4、DataOutputStream,数据输出流允许应用程序以适当方式将基本 Java 数据类型写入输出流中。然后,应用程序可以使用数据输入流将数据读入。下面是该类源码:

      1 //DataOutputStream允许应用程序将基本的java数据类型写入到一个输出流中,
      2  //同时应用程序还可以用一个DataInputStream将数据读回。
      3 public class DataOutputStream extends FilterOutputStream implements DataOutput {
      4     //记录当前写到数据输入流中的字节数
      5     protected int written;
      6     //bytearr根据请求,由writeUTF进行初始化
      7     private byte[] bytearr = null;
      8     public DataOutputStream(OutputStream out) {
      9         super(out);
     10     }
     11     private void incCount(int value) {
     12         int temp = written + value;
     13         if (temp < 0) {//输入字节数操作能够表示的最大数
     14             temp = Integer.MAX_VALUE;
     15         }
     16         written = temp;
     17     }
     18     //将指定的字节b写到底层的输出流
     19     public synchronized void write(int b) throws IOException {
     20         out.write(b);
     21         incCount(1);
     22     }
     23     public synchronized void write(byte b[], int off, int len)
     24         throws IOException
     25     {
     26         out.write(b, off, len);
     27         incCount(len);
     28     }
     29     //清理输出流,迫使所有缓冲的输出字节被写出到流中。 
     30     public void flush() throws IOException {
     31         out.flush();
     32     }
     33     //将值为ture的boolean类型的数据以1的形式记录
     34     public final void writeBoolean(boolean v) throws IOException {
     35         out.write(v ? 1 : 0);
     36         incCount(1);
     37     }
     38     //将一个 byte 值以 1-byte 值形式写出到基础输出流中
     39     public final void writeByte(int v) throws IOException {
     40         out.write(v);
     41         incCount(1);
     42     }
     43     public final void writeShort(int v) throws IOException {
     44         out.write((v >>> 8) & 0xFF);
     45         out.write((v >>> 0) & 0xFF);
     46         incCount(2);
     47     }
     48     public final void writeChar(int v) throws IOException {
     49         out.write((v >>> 8) & 0xFF);
     50         out.write((v >>> 0) & 0xFF);
     51         incCount(2);
     52     }
     53     public final void writeInt(int v) throws IOException {
     54         out.write((v >>> 24) & 0xFF);
     55         out.write((v >>> 16) & 0xFF);
     56         out.write((v >>>  8) & 0xFF);
     57         out.write((v >>>  0) & 0xFF);
     58         incCount(4);
     59     }
     60     private byte writeBuffer[] = new byte[8];
     61     public final void writeLong(long v) throws IOException {
     62         writeBuffer[0] = (byte)(v >>> 56);
     63         writeBuffer[1] = (byte)(v >>> 48);
     64         writeBuffer[2] = (byte)(v >>> 40);
     65         writeBuffer[3] = (byte)(v >>> 32);
     66         writeBuffer[4] = (byte)(v >>> 24);
     67         writeBuffer[5] = (byte)(v >>> 16);
     68         writeBuffer[6] = (byte)(v >>>  8);
     69         writeBuffer[7] = (byte)(v >>>  0);
     70         out.write(writeBuffer, 0, 8);
     71         incCount(8);
     72     }
     73     public final void writeFloat(float v) throws IOException {
     74         writeInt(Float.floatToIntBits(v));
     75     }
     76     public final void writeDouble(double v) throws IOException {
     77         writeLong(Double.doubleToLongBits(v));
     78     }
     79     public final void writeBytes(String s) throws IOException {
     80         int len = s.length();
     81         for (int i = 0 ; i < len ; i++) {
     82             out.write((byte)s.charAt(i));
     83         }
     84         incCount(len);
     85     }
     86     public final void writeChars(String s) throws IOException {
     87         int len = s.length();
     88         for (int i = 0 ; i < len ; i++) {
     89             int v = s.charAt(i);
     90             out.write((v >>> 8) & 0xFF);
     91             out.write((v >>> 0) & 0xFF);
     92         }
     93         incCount(len * 2);
     94     }
     95     public final void writeUTF(String str) throws IOException {
     96         writeUTF(str, this);
     97     }
     98     public final int size() {
     99         return written;
    100     }
    101 }
    View Code

    从源码可以看出,该类也是线程安全的。具体的使用方法通过一个示例来了解,如下:

     1 static void dataOutputTest(){
     2         DataOutputStream dout=null;
     3         FileOutputStream fout=null;
     4         DataInputStream din=null;
     5         FileInputStream fin=null;
     6         int[] arr={1,2,3,4,5};
     7         try{
     8             fout=new FileOutputStream("out.txt");
     9             dout=new DataOutputStream(fout);
    10             for(int val:arr){
    11                 //将数据读入到文件中
    12                 dout.writeInt(val);
    13             }
    14             //从文件中读出数据
    15             fin=new FileInputStream("out.txt");
    16             din=new DataInputStream(fin);
    17             while(din.available()>0){
    18                 System.out.print(din.readInt()+" ");
    19             }
    20         }catch(Exception e){
    21             e.printStackTrace();
    22         }finally{
    23             try{
    24                 if(fin!=null)
    25                     fin.close();
    26                 if(fout!=null)
    27                     fout.close();
    28                 if(din!=null)
    29                     din.close();
    30                 if(dout!=null)
    31                     dout.close();
    32             }catch(Exception e){
    33                 e.printStackTrace();
    34             }
    35         }
    36     }
    View Code

    我们可以向文件中写入基本的java数据,也可以相应地读出这些数据,读出数据同样用到了输入流。

    5、PrintStream,打印流,同样也是一个装饰器,可以为其他输出流添加功能,PrintStream 打印的所有字符都使用平台的默认字符编码转换为字节。在需要写入字符而不是写入字节的情况下,应该使用 PrintWriter 类。我们用的最多的输出就是System.out.println()方法,直接将内容输出到控制台上,其中,System是一个包含几个静态变量和静态方法的类,它不能够被实例化,其中一个静态变量out就是PrintStream类型的对象,使用System.out.println默认将输出送到控制台上,我们也可以将内容输出到一个文件中,如下例子:

     1 static void printStreamTest(){
     2         PrintStream pri=null;
     3         try{
     4             pri=new PrintStream("out.txt");
     5             pri.print("hello,world");
     6             pri.println();
     7             pri.println(12);
     8             pri.close();
     9         }catch(Exception e){
    10             e.printStackTrace();
    11         }
    12     }
    View Code

    我们可以利用println方法,将内容输出到一个文件中,其实这就是装饰器的功能,它增强了FileOutputStream的功能(注意,我们这里是将内容输出到一个文件中,所以PrintStream类内部调用的是FileOutputStream对象,这里不再给出源码),使得我们的输入更加简单。

    以上是输出流的总结,总的来说,理解输出流类的继承关系和使用关系很重要,只有这样才能知道如何使用输出流。

  • 相关阅读:
    仙人球的残影
    Calculate the formula
    自定义代码段
    getter-setter方法练习
    封装
    Xcode文档安装
    Xcode模板修改
    匿名对象
    OC多文件开发介绍
    #pragma mark指令
  • 原文地址:https://www.cnblogs.com/codeMedita/p/7400437.html
Copyright © 2020-2023  润新知