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 }
輸出字節流的類結構圖如下,同樣,這里只列舉常用的幾個類,還有很多未被列出。

下面對不同的輸出流進行簡單的分析,會給出相應的類源碼和示例。
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 }
從源碼可以看出,涉及到數據操作的方法都加了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 }
我們創建了一個字節數組輸出流,它的緩沖容量大小為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 }
文件輸出流操作不是線程安全的,如果用於多線程訪問,注意使用同步。以下是文件輸出流操作的例子:
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 }
為了減少操作,這里使用了文件輸入流對象,我們從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 }
從源碼來看,可以得知,該類也是線程同步的,所以在多線程環境下能夠保證訪問的正確性。下面給出該類的一個示例:
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 }
該示例同樣用了輸入流,為了操作方便,我們直接向一個文件中讀取數據,所以要用輸入流,然后將讀取到的數據輸出到另一個文件中,如果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 }
從源碼可以看出,該類也是線程安全的。具體的使用方法通過一個示例來了解,如下:
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 }
我們可以向文件中寫入基本的java數據,也可以相應地讀出這些數據,讀出數據同樣用到了輸入流。
5、PrintStream,打印流,同樣也是一個裝飾器,可以為其他輸出流添加功能,PrintStream 打印的所有字符都使用平台的默認字符編碼轉換為字節。在需要寫入字符而不是寫入字節的情況下,應該使用 類。我們用的最多的輸出就是System.out.println()方法,直接將內容輸出到控制台上,其中,System是一個包含幾個靜態變量和靜態方法的類,它不能夠被實例化,其中一個靜態變量out就是PrintStream類型的對象,使用System.out.println默認將輸出送到控制台上,我們也可以將內容輸出到一個文件中,如下例子:PrintWriter
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 }
我們可以利用println方法,將內容輸出到一個文件中,其實這就是裝飾器的功能,它增強了FileOutputStream的功能(注意,我們這里是將內容輸出到一個文件中,所以PrintStream類內部調用的是FileOutputStream對象,這里不再給出源碼),使得我們的輸入更加簡單。
以上是輸出流的總結,總的來說,理解輸出流類的繼承關系和使用關系很重要,只有這樣才能知道如何使用輸出流。
