Java 字節流操作

?????在java中我們使用輸入流來向一個字節序列對象中寫入,使用輸出流來向輸出其內容。C語言中只使用一個File包處理一切文件操作,而在java中卻有著60多種流類型,構成了整個流家族。看似龐大的體系結構,其實只要使用適合的方法將其分門別類,就顯得清晰明了了。而我準備將其按照處理文件類型的不同,分為字節流類型和字符流類型。共兩篇文章,本篇從字節流開始。主要包含以下內容:

  • InputStream/OutPutStream - - -字節流基類
  • FileInputStream/FileOutputStream - - - - -處理文件類型
  • ByteArrayInputStream/ByteArrayOutputStream - - - -字節數組類型
  • DataInputStream/DataOutputStream - - - -裝飾類
  • BufferedInputStream/BufferedOutputStream - - - -緩沖流
    一、基類流
    ?????其實始終有人搞不清楚到底InputStream是讀還是OutputStream是讀。其實很簡單就可以記住,你把你自己想象為是一個程序,InputStream對你來說是輸入,也就是你要從某個地方讀到自己這來,而OutputStream對你來說就是輸出,也就是說你需要寫到某個地方。這樣就可以簡單的區分輸入輸出流。下面看看InputStream的成員方法:
public abstract int read() throws IOException;

public int read(byte b[]) throws IOException
 
public int read(byte b[], int off, int len)

public long skip(long n) throws IOException

public int available() throws IOException

public void close() throws IOException

public synchronized void mark(int readlimit)
public synchronized void reset() throws IOException
public boolean markSupported()

?????InputStream是一個輸入流,也就是用來讀取文件的流,抽象方法read讀取下一個字節,當讀取到文件的末尾時候返回 -1。如果流中沒有數據read就會阻塞直至數據到來或者異常出現或者流關閉。這是一個受查異常,具體的調用者必須處理異常。除了一次讀取一個字節,InputStream中還提供了read(byte[]),讀取多個字節。read(byte[])其實默認調用的還是read(byte b[], int off, int len)方法,表示每讀取一個字節就放在b[off++]中,總共讀取len個字節,但是往往會出現流中字節數小于len,所以返回的是實際讀取到的字節數。
?????接下來是一些高級的用法,skip方法表示跳過指定的字節數,來讀取。調用這種方法需要知道,一旦跳過就不能返回到原來的位置。當然,我們可以看到還有剩下的三種方法,他們一起合作實現了可重復讀的操作。mark方法在指定的位置打上標記,reset方法可以重新回到之前的標記索引處。但是我們可以想到,它一定是在打下mark標記的地方,使用字節數組記錄下接下來的路徑上的所有字節數據,直到你使用了reset方法,取出字節數組中的數據供你讀取(實際上也不是一種能夠重復讀,只是用字節數組記錄下這一路上的數據而已,等到你想要回去的時候將字節數組給你重新讀取)。
?????OutputStream是一種輸出流,具體的方法和InputStream差不多,只是,一個讀一個寫。但是,他們都是抽象類,想要實現具體的功能還是需要依賴他們的子類來實現,例如:FileInputStream/FileOutputStream等。
二、文件字節流
?????FileInputStream繼承與InputStream,主要有以下兩個構造方法:

 public FileInputStream(String name)
 
 public FileInputStream(File file)

?????第一種構造方法傳的是一個字符串,實際上是一個確定文件的路徑,內部將此路徑封裝成File類型,調用第二種構造方法。第二中構造方法,直接綁定的是一個具體的文件。
?????FileInputStream 的內部方法其實和父類InputStream中定義的方法差不多,我們通過一個讀文件的實例來演示用法。

public class Test_InputOrOutput {
    public static void main(String[] args) throws IOException{
            FileInputStream fin = new FileInputStream("hello.txt");
            byte[] buffer = new byte[1024];
            int x = fin.read(buffer,0,buffer.length);
            String str = new String(buffer);
            System.out.println(str);
            System.out.println(x);
            fin.close();
    }
}
輸出結果:
hello world
13

?????結果意料之中,調用了read方法將hello.txt中的內容讀到字節數組buffer中,然后通過String類構造方法將字節數組轉換成字符串。返回實際上讀取到的字節數13。(10個字母+兩個空格+一個字符串結束符)
?????FileOutputStream繼承父類OutputStream,主要方法代碼如下:

private final boolean append;

public FileOutputStream(String name)
public FileOutputStream(String name, boolean append)
public FileOutputStream(File file)
public FileOutputStream(File file, boolean append)

private native void writeBytes(byte b[], int off, int len, boolean append)
public void write(byte b[]) throws IOException

?????FileOutputStream的一些基本的操作和FileInputStream類似,只是一個是讀一個是寫。我們主要要知道,append屬性是指定對于文件的操作是覆蓋方式(false),還是追加方式(true)。下面通過一個實例演示其用法:

public class Test_InputOrOutput {
    public static void main(String[] args) throws IOException{
        FileOutputStream fou = new FileOutputStream("hello.txt");
        String str = "Walker_YAM";
        byte[] buffer = str.getBytes("UTF-8");
        fou.write(buffer,0 ,buffer.length);
        fou.close();
    }
}

?????如我們所料,字符串"Walker_YAM"將會被寫入hello.txt,由于沒有指定append,所以將會覆蓋hello.txt中的所有內容。
三、動態字節數組流
?????在我們上述的文件讀取流中,我們定義 byte[] buffer = new byte[1024];,buffer數組為1024,如果我們將要讀取的文件中的內容有1025個字節,buffer是不是裝不下?當然我們也可以定義更大的數組容量,但是從內存的使用效率上,這是低效的。我們可以使用動態的字節數組流來提高效率。
?????ByteArrayInputStream的內部使用了類似于ArrayList的動態數組擴容的思想。

protected byte buf[];
protected int count;

public ByteArrayInputStream(byte buf[])
public ByteArrayInputStream(byte buf[], int offset, int length)
public synchronized int read()
public synchronized int read(byte b[], int off, int len)

?????ByteArrayInputStream內部定義了一個buf數組和記錄數組中實際的字節數,read方法也很簡單,讀取下一個字節,read(byte b[], int off, int len) 將內置字節數組讀入目標數組。實際上,整個ByteArrayInputStream也就是將一個字節數組封裝在其內部。為什么這么做?主要還是為了方便參與整個InputStream的體系,復用代碼。
?????ByteArrayOutputStream的作用要比ByteArrayInputStream更加的實際一點:

protected byte buf[];
protected int count;

public ByteArrayOutputStream() {
        this(32);
    }
public ByteArrayOutputStream(int size)
private void ensureCapacity(int minCapacity)
public synchronized void write(byte b[], int off, int len)
public synchronized void writeTo(OutputStream out)
public synchronized byte toByteArray()[]
public synchronized String toString()

?????和ByteArrayInputStream一樣,內部依然封裝了字節數組buf和實際容量count,通過構造方法可以指定內置字節數組的長度。主要的是write方法,將外部傳入的字節數組寫到內置數組中,writeTo方法可以理解為將自己內置的數組交給OutputStream 的其他子類使用。toByteArray和toString則會將內置數組轉換成指定類型返回。
?????下面我們利用他們來解決剛開始說的效率問題。

public class Test_InputOrOutput {
    public static void main(String[] args) throws IOException{
        FileInputStream fin = new FileInputStream("hello.txt");
        ByteArrayOutputStream bou = new ByteArrayOutputStream();
        int x = 0;
        while((x = fin.read()) !=-1){
                bou.write(x);
        }
        System.out.println(bou.toString());
    }
}

?????從hello文件中每讀取一個字節寫入ByteArrayOutputStream 中,我們不用擔心hello文件太大而需要設置較大的數組,使用ByteArrayOutputStream 動態增加容量,如果添加字節即將超過容量上限,進行擴充(往往是指數級擴充)
四、裝飾者字節流
?????上述的流都是直接通過操作字節數組來實現輸入輸出的,那如果我們想要輸入一個字符串類型或者int型或者double類型,那還需要調用各自的轉字節數組的方法,然后將字節數組輸入到流中。我們可以使用裝飾流,幫我們完成轉換的操作。我們先看DataOutputStream。

 public DataOutputStream(OutputStream out)
 public synchronized void write(byte b[], int off, int len)
 public final void writeBoolean(boolean v)
 public final void writeByte(int v)
 public final void writeShort(int v)
 public final void writeInt(int v) 
 public final void writeDouble(double v)

?????簡單的列舉了一些方法,可以看到,DataOutputStream只有一個構造方法,必須傳入一個OutputStream類型參數。(其實它的內部還是圍繞著OutputStream,只是在它的基礎上做了些封裝)。我們看到,有writeBoolean、writeByte、writeShort、writeDouble等方法。他們內部都是將傳入的 boolean,Byte,short,double類型變量轉換為了字節數組,然后調用從構造方法中接入的OutputStream參數的write方法。

//這是writeInt的具體實現
    public final void writeInt(int v) throws IOException {
        out.write((v >>> 24) & 0xFF);
        out.write((v >>> 16) & 0xFF);
        out.write((v >>>  8) & 0xFF);
        out.write((v >>>  0) & 0xFF);
        incCount(4);
    }

?????將一個四個字節的int類型,分開寫入,先寫入高八位。總共寫四次,第一次將高八位移動到低八位與上0xFF獲得整個int的低八位,這樣就完成了將原高八位寫入的操作,后續操作類似。

public class Test_InputOrOutput {
    public static void main(String[] args) throws IOException{
        DataOutputStream da = new DataOutputStream(new FileOutputStream("hello.txt"));
        da.writeInt(11);
        da.close();
    }
}
這里寫圖片描述

????? 使用UltraEditor打開hello文件,可以看到11這個int型數值被存入文件中。DataInputStream完成的就是讀取的操作,基本和DataOutputStream 的操作是類似的,是一個逆操作。
五、緩沖流
?????在這之前,我們讀取一個字節就要將它寫會磁盤,這樣來回開銷很大,我們可以使用緩沖區來提高效率,在緩沖區滿的時候,或者流關閉時候,將緩沖區中所有的內容全部寫會磁盤。BufferedInputStream和BufferedOutputStream也是一對裝飾流,我們先看看BufferedInputStream:

private static int DEFAULT_BUFFER_SIZE = 8192;
protected volatile byte buf[];
protected int pos;
protected int count;
public BufferedInputStream(InputStream in)
public BufferedInputStream(InputStream in, int size)
public synchronized int read()
public synchronized void mark(int readlimit)
public synchronized void reset()

?????一樣也是裝飾類流,第一種構造方法要求必須傳入InputStream類型參數,DEFAULT_BUFFER_SIZE 指定了默認的緩沖區的大小,當然還可以使用第二種構造方法指定緩沖區的大小(當然不能超過上界),read方法讀取的時候會將數據讀入內部的緩沖區中,當然緩沖區也是可以動態擴容的。

public class Test_InputOrOutput {
    public static void main(String[] args) throws IOException{
        BufferedInputStream bi = new BufferedInputStream(new FileInputStream("hello.txt"));
        bi.read();
        bi.read();
        bi.read();
        bi.read();
        System.out.println(bi.available());
    }
}

?????BufferedOutputStream和它是逆操作,不在贅述。這種緩沖字節流可以很大程度上提高我們的程序執行的效率,所以一般在使用別的流的時候都會包裝上這層緩沖流。
?????最后,本文如有錯誤指出,望大家指出!下一篇會寫字符流。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,563評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,694評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,672評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,965評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,690評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,019評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,013評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,188評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,718評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,438評論 3 360
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,667評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,149評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,845評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,252評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,590評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,384評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,635評論 2 380

推薦閱讀更多精彩內容