Java基礎:IO 流中的 flush

Java設計Io流可謂是煞費苦心,如果你是初學者我敢保證第一次接觸Java的IO類,一定會“狂暈!!”,暈,倒不是因爲它有多麼難學,而是太多,而且及其讓人容易迷惑。在編程日子中,尤其是在網絡編程中,幾乎離不開Java的IO,關於Java的IO流的分類,可以到網上soso,今天跟大家分享一下flush方法。

1.  OutputStream類的flush方法

該類實現了Flushable接口,所以重寫了flush方法,看看flush()源碼,會更加的讓你明白:

  1. public void flush() throws IOException {  
  2. }  
 

sorry,該實現爲空。就是一個空方法,什麼也不做。看清楚啊,該方法不是抽象方法,是一個實實在在的方法。除了方法體中一無所有,其它還好!!!汗!!!看JDK的api如何解釋!

  1. flush  
  2. public void flush()  
  3.            throws IOException  
  4. 刷新此輸出流並強制寫出所有緩衝的輸出字節。flush 的常規協定是:如果此輸出流的實現已經緩衝了以前寫入的任何字節,則調用此方法指示應將這些字節立即寫入它們預期的目標。  
  5. 如果此流的預期目標是由基礎操作系統提供的一個抽象(如一個文件),則刷新此流只能保證將以前寫入到流的字節傳遞給操作系統進行寫入,但不保證能將這些字節實際寫入到物理設備(如磁盤驅動器)。  
  6. OutputStream 的 flush 方法不執行任何操作。  
  7. 指定者:  
  8. 接口 Flushable 中的 flush  
  9. 拋出:  
  10. IOException - 如果發生 I/O 錯誤。  
 

開始,我安慰自己,該類是一個抽象類,它的子類肯定重寫了該方法。好吧,OutputStream的直接子類有:

  1. ByteArrayOutputStream  
  2. FileOutputStream   
  3. FilterOutputStream  
  4. ObjectOutputStream  
  5. OutputStream  
  6. PipedOutputStream  
 

注意:這裏的子類OutputStream是包 org.omg.CORBA.portable 的。

對於FileOutputStream、ByteArrayOutputStream、org.omg.CORBA.portable.OutputStream類它們的flush()方法均是從父類繼承的flush方法。

FilterOutputStream類重寫了flush()方法,但是實質還是調用父類的flush方法。

ObjectOutputStream、PipedOutputStream類重寫了flush()方法。

好吧,來兩個個小例子,很簡單,第一個例子主要是向文本中寫入字符串,第二個例子向文本中寫入一定字節的數據,如下代碼:

  1. package mark.zhang;  
  2. import java.io.BufferedOutputStream;  
  3. import java.io.DataOutputStream;  
  4. import java.io.File;  
  5. import java.io.FileOutputStream;  
  6. public class Test {  
  7.     public static void main(String[] args) throws Exception {  
  8.         File file = new File("text.txt");  
  9.         if(!file.exists()) {  
  10.             file.createNewFile();  
  11.         }  
  12.         FileOutputStream fos = new FileOutputStream(file);  
  13.         BufferedOutputStream bos = new BufferedOutputStream(fos);  
  14.         DataOutputStream dos = new DataOutputStream(fos);  
  15.         dos.writeBytes("java io");  
  16.     }  
  17. }  
 

  1. package mark.zhang;  
  2. import java.io.BufferedOutputStream;  
  3. import java.io.File;  
  4. import java.io.FileOutputStream;  
  5. public class Test {  
  6.     public static void main(String[] args) throws Exception {  
  7.         File file = new File("text.txt");  
  8.         if(!file.exists()) {  
  9.             file.createNewFile();  
  10.         }  
  11.         FileOutputStream fos = new FileOutputStream(file);  
  12.         BufferedOutputStream bos = new BufferedOutputStream(fos);  
  13.         byte[] b = new byte[1024*8];  
  14.         bos.write(b);  
  15.         bos.flush();  
  16.     }  
  17. }  
 

這兩段代執行後,分別會在當前目錄下產生7字節的文件(內容爲java io)和1KB字節的文件。說到這裏,有些人會說,這有什麼稀奇,至於嗎???呵呵,別急,淡定!!現在修改第二個代碼,主要是註釋掉調用flush()方法,如下:

  1. package mark.zhang;  
  2. import java.io.BufferedOutputStream;  
  3. import java.io.File;  
  4. import java.io.FileOutputStream;  
  5. public class Test {  
  6.     public static void main(String[] args) throws Exception {  
  7.         File file = new File("text.txt");  
  8.         if(!file.exists()) {  
  9.             file.createNewFile();  
  10.         }  
  11.         FileOutputStream fos = new FileOutputStream(file);  
  12.         BufferedOutputStream bos = new BufferedOutputStream(fos);  
  13.         byte[] b = new byte[1024];  
  14.         bos.write(b);  
  15.         //bos.flush();  
  16.     }  
  17. }  
 

ok,再次運行代碼,額的神啊???文件大小居然是o字節。why????flush()方法有那麼神奇,汗??!!!

仔細的你會發現,第一個代碼並沒有調用flush()方法,居然可以。爲什麼第二個就不可以呢?還是看源碼,有說服力。

DataOutputStream繼承FilterOutputStream,實現了DataOutput接口。我們知道FilterOutputStream類重寫了flush()方法,但是實質還是調用父類的flush方法。DataOutputStream類的flush()方法效仿其父類FilterOutputStream的做法,如下:

  1. public void flush() throws IOException {  
  2.     out.flush();  
  3. }  
 

那麼,即使你在代碼後面加上dos.flush();與不加是一樣的效果,因爲它們的父類flush()方法均爲空,這就是爲什麼第一個代碼的神奇所在。再看看第二個代碼的病因在哪裏?先看看BufferedOutputStream類的結構:

  1. public class BufferedOutputStream extends FilterOutputStream   
 

再看看,它的flush()方法:

  1. public synchronized void flush() throws IOException {  
  2.         flushBuffer();  
  3.     out.flush();  
  4. }  
 

  1. /** Flush the internal buffer */  
  2. private void flushBuffer() throws IOException {  
  3.     if (count > 0) {  
  4.  out.write(buf, 0, count);  
  5.  count = 0;  
  6.     }  
  7. }  
 

不錯,該類重寫了flush()方法,不像前面幾個那樣不是繼承就是山寨父類的flush()方法。BufferedOutputStream 類是一個使用了緩衝技術的類。這種類一把都會自己實現flush()方法。

那麼,有人會問使用這種類的時候,難道必須使用flush()方法嗎,當然不是嘍??!!不過有個前提,你的字節數據必須不能小於8KB。實例代碼,注意沒有flush()方法。

  1. package mark.zhang;  
  2. import java.io.BufferedOutputStream;  
  3. import java.io.File;  
  4. import java.io.FileOutputStream;  
  5. public class Test {  
  6.     public static void main(String[] args) throws Exception {  
  7.         File file = new File("text.txt");  
  8.         if(!file.exists()) {  
  9.             file.createNewFile();  
  10.         }  
  11.         FileOutputStream fos = new FileOutputStream(file);  
  12.         BufferedOutputStream bos = new BufferedOutputStream(fos);  
  13.         byte[] b = new byte[1024*8];  
  14.         bos.write(b);  
  15.         //bos.flush();  
  16.     }  
  17. }  
 

執行代碼,會產生8KB的文本文件。當然,怎麼可能你每時每刻都知道你的數據一定會不小於8KB呢,所以還是調用flush()方法比較安全。不過,話又說回來,一般用完IO流之後(如果你有一個好的習慣)我們都會去調用close()方法,看源碼可以知道該方法也是調用相對應的flush()方法。所以,大多數情況下你不必要擔心。這裏提醒一下,如果你的文件讀寫沒有達到預期目的,十之八九是因爲你沒有調用flush()或者close()方法。

另外,字符流類大多數都實現了flush()或者close()方法,只不過,它們調用的是StreamEncoder類的該方法。該類位於sun.nio.cs包下面,其源碼在我們jdk中是沒有的。源碼地址:http://www.docjar.com/html/api/sun/nio/cs/StreamEncoder.java.html。在此,ctrl+v其源碼,如下:

  1. /* 
  2.  * Copyright 2001-2005 Sun Microsystems, Inc.  All Rights Reserved. 
  3.  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 
  4.  * 
  5.  * This code is free software; you can redistribute it and/or modify it 
  6.  * under the terms of the GNU General Public License version 2 only, as 
  7.  * published by the Free Software Foundation.  Sun designates this 
  8.  * particular file as subject to the "Classpath" exception as provided 
  9.  * by Sun in the LICENSE file that accompanied this code. 
  10.  * 
  11.  * This code is distributed in the hope that it will be useful, but WITHOUT 
  12.  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 
  13.  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License 
  14.  * version 2 for more details (a copy is included in the LICENSE file that 
  15.  * accompanied this code). 
  16.  * 
  17.  * You should have received a copy of the GNU General Public License version 
  18.  * 2 along with this work; if not, write to the Free Software Foundation, 
  19.  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 
  20.  * 
  21.  * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, 
  22.  * CA 95054 USA or visit www.sun.com if you need additional information or 
  23.  * have any questions. 
  24.  */  
  25.   
  26.   
  27.   
  28. package sun.nio.cs;  
  29.   
  30. import java.io;  
  31. import java.nio;  
  32. import java.nio.channels;  
  33. import java.nio.charset;  
  34.   
  35. public class StreamEncoder extends Writer  
  36. {  
  37.   
  38.     private static final int DEFAULT_BYTE_BUFFER_SIZE = 8192;  
  39.   
  40.     private volatile boolean isOpen = true;  
  41.   
  42.     private void ensureOpen() throws IOException {  
  43.         if (!isOpen)  
  44.             throw new IOException("Stream closed");  
  45.     }  
  46.   
  47.     // Factories for java.io.OutputStreamWriter  
  48.     public static StreamEncoder forOutputStreamWriter(OutputStream out,  
  49.                                                       Object lock,  
  50.                                                       String charsetName)  
  51.         throws UnsupportedEncodingException  
  52.     {  
  53.         String csn = charsetName;  
  54.         if (csn == null)  
  55.             csn = Charset.defaultCharset().name();  
  56.         try {  
  57.             if (Charset.isSupported(csn))  
  58.                 return new StreamEncoder(out, lock, Charset.forName(csn));  
  59.         } catch (IllegalCharsetNameException x) { }  
  60.         throw new UnsupportedEncodingException (csn);  
  61.     }  
  62.   
  63.     public static StreamEncoder forOutputStreamWriter(OutputStream out,  
  64.                                                       Object lock,  
  65.                                                       Charset cs)  
  66.     {  
  67.         return new StreamEncoder(out, lock, cs);  
  68.     }  
  69.   
  70.     public static StreamEncoder forOutputStreamWriter(OutputStream out,  
  71.                                                       Object lock,  
  72.                                                       CharsetEncoder enc)  
  73.     {  
  74.         return new StreamEncoder(out, lock, enc);  
  75.     }  
  76.   
  77.   
  78.     // Factory for java.nio.channels.Channels.newWriter  
  79.   
  80.     public static StreamEncoder forEncoder(WritableByteChannel ch,  
  81.                                            CharsetEncoder enc,  
  82.                                            int minBufferCap)  
  83.     {  
  84.         return new StreamEncoder(ch, enc, minBufferCap);  
  85.     }  
  86.   
  87.   
  88.     // -- Public methods corresponding to those in OutputStreamWriter --  
  89.   
  90.     // All synchronization and state/argument checking is done in these public  
  91.     // methods; the concrete stream-encoder subclasses defined below need not  
  92.     // do any such checking.  
  93.   
  94.     public String getEncoding() {  
  95.         if (isOpen())  
  96.             return encodingName();  
  97.         return null;  
  98.     }  
  99.   
  100.    public void flushBuffer() throws IOException {  
  101.        synchronized (lock) {  
  102.            if (isOpen())  
  103.                implFlushBuffer();  
  104.            else  
  105.                throw new IOException("Stream closed");  
  106.        }  
  107.    }  
  108.   
  109.    public void write(int c) throws IOException {  
  110.        char cbuf[] = new char[1];  
  111.        cbuf[0] = (char) c;  
  112.        write(cbuf, 01);  
  113.    }  
  114.   
  115.    public void write(char cbuf[], int off, int len) throws IOException {  
  116.        synchronized (lock) {  
  117.            ensureOpen();  
  118.            if ((off < 0) || (off > cbuf.length) || (len < 0) ||  
  119.                ((off + len) > cbuf.length) || ((off + len) < 0)) {  
  120.                throw new IndexOutOfBoundsException();  
  121.            } else if (len == 0) {  
  122.                return;  
  123.            }  
  124.            implWrite(cbuf, off, len);  
  125.        }  
  126.    }  
  127.   
  128.    public void write(String str, int off, int len) throws IOException {  
  129.        /* Check the len before creating a char buffer */  
  130.        if (len < 0)  
  131.            throw new IndexOutOfBoundsException();  
  132.        char cbuf[] = new char[len];  
  133.        str.getChars(off, off + len, cbuf, 0);  
  134.        write(cbuf, 0, len);  
  135.    }  
  136.   
  137.    public void flush() throws IOException {  
  138.        synchronized (lock) {  
  139.            ensureOpen();  
  140.            implFlush();  
  141.        }  
  142.    }  
  143.   
  144.    public void close() throws IOException {  
  145.        synchronized (lock) {  
  146.            if (!isOpen)  
  147.                return;  
  148.            implClose();  
  149.            isOpen = false;  
  150.        }  
  151.    }  
  152.   
  153.    private boolean isOpen() {  
  154.        return isOpen;  
  155.    }  
  156.   
  157.   
  158.    // -- Charset-based stream encoder impl --  
  159.   
  160.    private Charset cs;  
  161.    private CharsetEncoder encoder;  
  162.    private ByteBuffer bb;  
  163.   
  164.    // Exactly one of these is non-null  
  165.    private final OutputStream out;  
  166.    private WritableByteChannel ch;  
  167.   
  168.    // Leftover first char in a surrogate pair  
  169.    private boolean haveLeftoverChar = false;  
  170.    private char leftoverChar;  
  171.    private CharBuffer lcb = null;  
  172.   
  173.    private StreamEncoder(OutputStream out, Object lock, Charset cs) {  
  174.        this(out, lock,  
  175.         cs.newEncoder()  
  176.         .onMalformedInput(CodingErrorAction.REPLACE)  
  177.         .onUnmappableCharacter(CodingErrorAction.REPLACE));  
  178.    }  
  179.   
  180.    private StreamEncoder(OutputStream out, Object lock, CharsetEncoder enc) {  
  181.        super(lock);  
  182.        this.out = out;  
  183.        this.ch = null;  
  184.        this.cs = enc.charset();  
  185.        this.encoder = enc;  
  186.   
  187.        // This path disabled until direct buffers are faster  
  188.        if (false && out instanceof FileOutputStream) {  
  189.                ch = ((FileOutputStream)out).getChannel();  
  190.        if (ch != null)  
  191.                    bb = ByteBuffer.allocateDirect(DEFAULT_BYTE_BUFFER_SIZE);  
  192.        }  
  193.            if (ch == null) {  
  194.        bb = ByteBuffer.allocate(DEFAULT_BYTE_BUFFER_SIZE);  
  195.        }  
  196.    }  
  197.   
  198.    private StreamEncoder(WritableByteChannel ch, CharsetEncoder enc, int mbc) {  
  199.        this.out = null;  
  200.        this.ch = ch;  
  201.        this.cs = enc.charset();  
  202.        this.encoder = enc;  
  203.        this.bb = ByteBuffer.allocate(mbc < 0  
  204.                                  ? DEFAULT_BYTE_BUFFER_SIZE  
  205.                                  : mbc);  
  206.    }  
  207.   
  208.    private void writeBytes() throws IOException {  
  209.        bb.flip();  
  210.        int lim = bb.limit();  
  211.        int pos = bb.position();  
  212.        assert (pos <= lim);  
  213.        int rem = (pos <= lim ? lim - pos : 0);  
  214.   
  215.            if (rem > 0) {  
  216.        if (ch != null) {  
  217.            if (ch.write(bb) != rem)  
  218.                assert false : rem;  
  219.        } else {  
  220.            out.write(bb.array(), bb.arrayOffset() + pos, rem);  
  221.        }  
  222.        }  
  223.        bb.clear();  
  224.        }  
  225.   
  226.    private void flushLeftoverChar(CharBuffer cb, boolean endOfInput)  
  227.        throws IOException  
  228.    {  
  229.        if (!haveLeftoverChar && !endOfInput)  
  230.            return;  
  231.        if (lcb == null)  
  232.            lcb = CharBuffer.allocate(2);  
  233.        else  
  234.            lcb.clear();  
  235.        if (haveLeftoverChar)  
  236.            lcb.put(leftoverChar);  
  237.        if ((cb != null) && cb.hasRemaining())  
  238.            lcb.put(cb.get());  
  239.        lcb.flip();  
  240.        while (lcb.hasRemaining() || endOfInput) {  
  241.            CoderResult cr = encoder.encode(lcb, bb, endOfInput);  
  242.            if (cr.isUnderflow()) {  
  243.                if (lcb.hasRemaining()) {  
  244.                    leftoverChar = lcb.get();  
  245.                    if (cb != null && cb.hasRemaining())  
  246.                        flushLeftoverChar(cb, endOfInput);  
  247.                    return;  
  248.                }  
  249.                break;  
  250.            }  
  251.            if (cr.isOverflow()) {  
  252.                assert bb.position() > 0;  
  253.                writeBytes();  
  254.                continue;  
  255.            }  
  256.            cr.throwException();  
  257.        }  
  258.        haveLeftoverChar = false;  
  259.    }  
  260.   
  261.    void implWrite(char cbuf[], int off, int len)  
  262.        throws IOException  
  263.    {  
  264.        CharBuffer cb = CharBuffer.wrap(cbuf, off, len);  
  265.   
  266.        if (haveLeftoverChar)  
  267.        flushLeftoverChar(cb, false);  
  268.   
  269.        while (cb.hasRemaining()) {  
  270.        CoderResult cr = encoder.encode(cb, bb, false);  
  271.        if (cr.isUnderflow()) {  
  272.           assert (cb.remaining() <= 1) : cb.remaining();  
  273.           if (cb.remaining() == 1) {  
  274.                haveLeftoverChar = true;  
  275.                leftoverChar = cb.get();  
  276.            }  
  277.            break;  
  278.        }  
  279.        if (cr.isOverflow()) {  
  280.            assert bb.position() > 0;  
  281.            writeBytes();  
  282.            continue;  
  283.        }  
  284.        cr.throwException();  
  285.        }  
  286.    }  
  287.   
  288.    void implFlushBuffer() throws IOException {  
  289.        if (bb.position() > 0)  
  290.        writeBytes();  
  291.    }  
  292.   
  293.    void implFlush() throws IOException {  
  294.        implFlushBuffer();  
  295.        if (out != null)  
  296.        out.flush();  
  297.    }  
  298.   
  299.    void implClose() throws IOException {  
  300.        flushLeftoverChar(nulltrue);  
  301.        try {  
  302.            for (;;) {  
  303.                CoderResult cr = encoder.flush(bb);  
  304.                if (cr.isUnderflow())  
  305.                    break;  
  306.                if (cr.isOverflow()) {  
  307.                    assert bb.position() > 0;  
  308.                    writeBytes();  
  309.                    continue;  
  310.                }  
  311.                cr.throwException();  
  312.            }  
  313.   
  314.            if (bb.position() > 0)  
  315.                writeBytes();  
  316.            if (ch != null)  
  317.                ch.close();  
  318.            else  
  319.                out.close();  
  320.        } catch (IOException x) {  
  321.            encoder.reset();  
  322.            throw x;  
  323.        }  
  324.    }  
  325.   
  326.    String encodingName() {  
  327.        return ((cs instanceof HistoricallyNamedCharset)  
  328.            ? ((HistoricallyNamedCharset)cs).historicalName()  
  329.            : cs.name());  
  330.    }  
 

更多源碼查看http://www.docjar.com/projects/Open-JDK-6.b17-src-code.htm

2. Writer類的flush方法

該類是一個抽象類,聲明如下:

  1. public abstract class Writer implements Appendable, Closeable, Flushable   
 

Writer類的flush()方法是一個抽象方法,其子類一般都實現了該方法。所以,一般使用字符流之後,調用一下flush()或者close()方法。

  1. abstract public void flush() throws IOException;  
 

細節請看jdk的api,或者Java的源碼以及上面的StreamEncoder類源碼。

ok,說到這裏吧,這裏主要藉助Java的IO中字節流與字符流的flush()方法,來說明學Java看源碼和思考是很重要的。

總之,不管你使用哪種流(字符、字節、具有緩衝的流)技術,不妨調用一下flush()/close()方法,防止數據無法寫到輸出流中。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章