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對象,這里不再給出源碼),使得我們的輸入更加簡單。

以上是輸出流的總結,總的來說,理解輸出流類的繼承關系和使用關系很重要,只有這樣才能知道如何使用輸出流。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM