java IO(二):位元組流(InputStream和OutputStream)

来源:https://www.cnblogs.com/f-ck-need-u/archive/2017/12/29/8146358.html
-Advertisement-
Play Games

數據流分為輸入、輸出流,無論是輸入流還是輸出流,都可看作是在源和目標之間架設一根"管道",這些管道都是單向流動的,要麼流入到記憶體(輸入流),要麼從記憶體流出(輸出流)。 應用於java上,輸入流和輸出流分別為InputStream和OutputStream。輸入流用於讀取(read)數據,將數據載入到 ...


數據流分為輸入、輸出流,無論是輸入流還是輸出流,都可看作是在源和目標之間架設一根"管道",這些管道都是單向流動的,要麼流入到記憶體(輸入流),要麼從記憶體流出(輸出流)。

應用於java上,輸入流和輸出流分別為InputStream和OutputStream。輸入流用於讀取(read)數據,將數據載入到記憶體(應用程式),輸出流用於寫入(write)數據,將數據從記憶體寫入到磁碟中。

數據流可分為位元組流和字元流,位元組流按位元組輸入、輸出數據,字元流按字元個數輸入、輸出數據。本文介紹的是位元組流。

1.OutputStream類和FileOutputStream類

OutputStream類是位元組輸出流的超類。它只提供了幾個方法,註意這幾個方法都會拋出IOExcepiton異常,因此需要捕獲或向上拋出:

  • close():關閉輸出流,即撤掉管道。
  • flush():將緩存位元組數據強制刷到磁碟上。
  • write(byte[] b):將byte數組中的數據寫入到輸出流。
  • write(byte[] b, int off, int len):將byte數組中從off開始的len個位元組寫入到此輸出流。
  • write(int b):將指定的單個位元組寫入此輸出流。

FileOutputStream類是OutputStream的子類,專門用於操作文件相關的流,例如向文件中寫入數據。該類有以下幾個構造方法:

 FileOutputStream(File file):創建一個向指定 File 對象表示的文件中寫入數據的文件輸出流。  FileOutputStream(File file, boolean append):創建一個向指定 File 對象表示的文件中寫入數據的文件輸出流。  FileOutputStream(String name):創建一個向具有指定名稱的文件中寫入數據的輸出文件流。  FileOutputStream(String name, boolean append):創建一個向具有指定 name 的文件中寫入數據的輸出文件流。

例如:創建一個新文件,向其中寫入"abcde"。

import java.io.*;

public class OutStr1 {
    public static void main(String[] args) throws IOException {

        File tempdir = new File("D:/temp");  //create a tempdir
        if(!tempdir.exists()) {
            tempdir.mkdir();
        }

        File testfile = new File(tempdir,"test.txt");
        FileOutputStream fos = new FileOutputStream(testfile)//在記憶體和testfile之間架一根名為fos的管道 

        fos.write("abcde".getBytes());  //將位元組數組中所有位元組(byte[] b,b.length)都寫入到管道中
        fos.close();
    }
}

註意:
(1).此處的FileOutputStream()構造方法會創建一個新文件並覆蓋舊文件。如果要追加文件,採用FileOutputStream(file,true)構造方法構造位元組輸出流。
(2).上面的for.write("abcde".getBytes())用的是write(byte[] b)方法,它會將位元組數組中b.length個位元組即所有位元組都寫入到輸出流中。可以採用write(byte[] b,int off,int len)方法每次寫給定位置、長度的位元組數據。
(3).無論是構造方法FileOutputStream(),還是write()、close()方法,都會拋出異常,有些是IOException,有些是FileNotFoundException。因此,必須捕獲這些異常或向上拋出。

追加寫入和換行寫入

向文件中追加數據時,採用write(File file,boolean append)方法。

File testfile = new File(tempdir,"test.txt");
FileOutputStream fos = new FileOutputStream(testfile,true);

byte[] data = "abcde".getBytes();
fos.write(data,0,2);
fos.close();

換行追加時,需要加上換行符,Windows上的換行符為"\r\n",unix上的換行符為"\n"。如果要保證良好的移植性,可獲取系統屬性中的換行符並定義為常量。

import java.io.*;

public class OutStr1 {
    private static final String LINE_SEPARATOR = System.getProperty("line.separator"); //newline

    public static void main(String[] args) throws IOException {
        File tempdir = new File("D:/temp");  //create a tempdir
        if(!tempdir.exists()) {
            tempdir.mkdir();
        }

        File testfile = new File(tempdir,"test.txt");
        FileOutputStream fos = new FileOutputStream(testfile,true);

        String str = LINE_SEPARATOR+"abcde";  //
        fos.write(str.getByte());
        fos.close();
    }
}

2.捕獲IO異常的方法

輸出流中很多方法都定義了拋出異常,這些異常必須向上拋出或捕獲。向上拋出很簡單,捕獲起來可能比想象中的要複雜一些。

以向某文件寫入數據為例,以下是最終的捕獲代碼,稍後將逐層分析為何要如此捕獲。

File file = new File("d:/tempdir/a.txt");
FileOutputStream fos = null;
try {
    fos = new FileOutputStream(file);
    fos.write("abcde".getBytes());
} catch (IOException e) {
    ...
} finally {
    if (fos != null) {
        try {
            fos.close();
        } catch (IOException e) {
            throw new RuntimeException("xxxx");
        }
    } 
}

向某文件寫入數據,大致會包含以下幾個過程。

File file = new File("d:/tempdir/a.txt");
FileOutputStream fos = new FileOutputStream(file);   //---->(2)
fos.write("abcde".getBytes());        //------------------->(3)
fos.close();      //--------------------------------------->(4)

其中(2)-(4)這三條代碼都需要去捕獲,如果將它們放在一個try結構中,顯然邏輯不合理,例如(2)正常實例化了一個輸出流,但(3)異常,這時無法用(4)來關閉流。無論異常發生在何處,close()動作都是應該要執行,因此需要將close()放入finally結構中。

File file = new File("D:/tempdir/a.txt");
try {
    FileOutputStream fos = new FileOutputStream(file);   //---->(2)
    fos.write("abcde".getBytes());        //------------------->(3)
} catch (IOException e) {
    ...
} finally {
    fos.close();      //--------------------------------------->(4)
}

但這樣一來,fos.close()中的fos是不可識別的,因此,考慮將fos定義在try結構的外面。因此:

File file = new File("D:/tempdir/a.txt");
FileOutputStream fos = null;
try {
    fos = new FileOutputStream(file);   //---->(2)
    fos.write("abcde".getBytes());        //-->(3)
} catch (IOException e) {
    ...
} finally {
    fos.close();      //---------------------->(4)
}

如果d:\tempdir\a.txt文件不存在,那麼(2)中的fos將指向空的流對象,即空指針異常。再者,finally中的close()也是需要捕獲異常的,因此加上判斷並對其try...catch,於是:

File file = new File("D:/tempdir/a.txt");
FileOutputStream fos = null;
try {
    fos = new FileOutputStream(file);   //---->(2)
    fos.write("abcde".getBytes());        //-->(3)
} catch (IOException e) {
    ...
} finally {
    if(fos != null) {
        try {
            fos.close();//-------------------->(4)
        } catch (IOException e) {
            throw new RuntimeException("xxx");
        }
    }
}

3.InputStream類和FileInputStream類

以下是InputStream類提供的方法:

  • close():關閉此輸入流並釋放與該流關聯的所有系統資源。
  • mark(int readlimit):在此輸入流中標記當前的位置。
  • read():從輸入流中讀取數據的下一個位元組,返回的是0-255之間的ASCII碼,讀到文件結尾時返回-1
  • read(byte[] b):從輸入流中讀取一定數量的位元組存儲到緩衝區數組b中,返回讀取的位元組數,讀到文件結尾時返回-1
  • read(byte[] b, int off, int len):將輸入流中最多 len 個數據位元組讀入 byte 數組,返回讀取的位元組數,讀到文件結尾時返回-1
  • reset():將此流重新定位到最後一次對此輸入流調用 mark 方法時的位置。
  • skip(long n):跳過和丟棄此輸入流中數據的 n 個位元組。

示例:d:\temp\test.txt文件中的內容為"abcde",讀取該文件。

import java.io.*;

public class InStr1 {
    public static void main(String[] args) throws IOException {
        File file = new File("d:/temp/test.txt");
        FileInputStream fis = new FileInputStream(file);

        System.out.println(fis.read());    
        System.out.println((char)fis.read());
        System.out.println((char)fis.read());
        System.out.println((char)fis.read());
        System.out.println((char)fis.read());
        System.out.println(fis.read());
        System.out.println(fis.read());
        fis.close();
    }
}

執行結果為:

97
b
c
d
e
-1
-1

從結果可知:

(1).read()每次讀取一個位元組並返回0-255之間的數值。可以強制轉換為char字元。
(2).每次讀取後指針下移一位,直到文件的結尾。
(3).讀取文件結束符返回-1,且指針一直停留在結束符位置處,因此後面繼續讀取還是返回-1。

因此,可以使用如下代碼來讀取整個文件。

int ch = 0 ;
while ((ch=fis.read())!= -1) {
    System.out.println((char)ch);
}

當文件很大時,這樣一個位元組一個位元組讀取的速度必然極慢。因此,需要一次讀取多個位元組。下麵是使用read(byte[] b)一次讀取多個位元組的代碼。

int len = 0;
byte[] buf = new byte[2];
while ((len=fis.read(buf))!=-1) {
    System.out.println(new String(buf,0,len));
}

執行結果為:

ab
cd
e

幾個註意點:
(1).read(byte[] b)是從文件中讀取b.length個位元組存儲到位元組數組中,第一個位元組存儲到b[0],第二個位元組存儲到b[2],以此類推,註意存儲在位元組數組中的每一個位元組都是0-255的ASCII碼。例如文件中有3個位元組abc,位元組數組b的長度為5,則b[0]=a,b[1]=b,b[2]=c,b[3]和b[4]不受影響(即仍為初始化值0),如果位元組數組b長度為2,則第一批讀取兩個位元組ab存儲到b[0]和b[1],第二批再讀取一個位元組c存儲到b[0]中,此時位元組數組b中存儲的內容為cb。
(2).read(b)返回的是讀取的位元組數,在到達文件末尾時返回-1。

因此,上面的代碼讀取文件的過程大致為:讀取a和b存放到buf[0]和buf[1]中,此時len=2,轉換為字元串後列印得到ab,再讀取c覆蓋buf[0],讀取d覆蓋buf[1],轉換為字元串後列印得到cd,最後讀取e覆蓋到buf[0],到了文件的末尾,此時buf[1]=d。也就是說到此為止,b數組中存放的仍然有d,但因為String(buf,0,len)是根據len來轉換為字元串的,因此d不會被轉換。但如果將new String(buf,0,len)改為new String(buf),將得到abcded。

由此可知,位元組數組的長度決定了每次讀取的位元組數量。通常來說,可以將byte[]的長度設置為1024的整數倍以達到更高的效率,例如設置為4096,8192等都可,但也不應設置過大。

4.複製文件(位元組流)

原理:

1.在源文件上架起一根輸出流(read)管道。
2.在目標文件上架起一根輸入流(write)管道。
3.但註意,這兩根管道之間沒有任何聯繫。可以通過中間媒介實現中轉。每read()一定數量的位元組到記憶體中,可以將這些位元組write到磁碟中。
4.應該使用位元組數組作為buffer做緩存,而不應該使用單位元組的讀取、寫入,這樣會非常非常慢。

import java.io.*;

public class CopyFile {
    public static void main(String[] args) throws IOException {
        // src file and src Stream
        File src = new File("d:/temp/1.avi");
        FileInputStream fis = new FileInputStream(src);

        // dest file and dest Stream
        File dest = new File("d:/temp/1_copy.avi");
        FileOutputStream fos = new FileOutputStream(dest);

        int len = 0;
        byte[] buf = new byte[1024];
        while((len=fis.read(buf)) != -1) {        // read
            fos.write(buf,0,len);              // write
        }
        fis.close();
        fos.close();
    }
}

註:若您覺得這篇文章還不錯請點擊右下角推薦,您的支持能激發作者更大的寫作熱情,非常感謝!


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 之前開發的java程式由於依賴比較多的jar包,啟動命令為” java -classpath .:lib/*.jar 主類名“,這種啟動方式需要指定類路徑、入口類名稱,並存在jar包缺失隱患。 現在利用SpringBoot的maven打包插件,可以將依賴的jar包都整理到一個jar文件中。 一、創建 ...
  • Python 列表的創建 創建一個空列表 例如:fruit=[] 創建一個有元素的列表 例如:fruit=['apple','banana','cherry'] Python 列表元素的查找 Python 列表元素的添加和修改 Python 列表元素的刪除 Python 列表其他函數 Python ...
  • JAVA命名規範 關於Java中各種元素的命名,定義這些規範的目的是讓項目中所有的文檔都看起來像一個人寫的,增加可讀性,減少項目組中因為換人而帶來的損失。(這些規範並不是一定要絕對遵守,但是一定要讓程式有良好的可讀性): Package 的命名 Package 的名字應該都是由一個小寫單片語成。 C ...
  • VSCode小巧、快速,跨平臺,界面炫酷,各種擴展,是時候換用新的VSCode了。 ...
  • QT 是一個跨平臺的 C++ GUI 應用構架,它提供了豐富的視窗部件集,具有面向對象、易於擴展、真正的組件編程等特點,更為引人註目的是目前 Linux 上最為流行的 KDE 桌面環境就是建立在 QT 庫的基礎之上。QT 支持下列平臺:MS/WINDOWS-95、98、NT 和 2000;UNIX/ ...
  • 這篇主要寫配置文件的優化,例如 jdbc.properties 配置文件 ,引入資料庫的文件,例如driver,url,username,password 等,然後在 SqlMapConfig.xml 裡面引入相對應的文件即可,可以簡化配置文件, 在 SqlMapConfig.xml 用 <type ...
  • 本文主要介紹四種實例化bean的方式(註入方式) 或者叫依賴對象實例化的四種方式。上面的程式,創建bean 對象,用的是什麼方法 ,用的是構造函數的方式 (Spring 可以在構造函數私有化的情況下把類對象創建出來) 常用的創建方式有以下四種: 1) setter 方法 2) 構造函數 3) 靜態工 ...
  • 這是在看完了C++ Primer這本書之後又回過頭來碼的筆記,在看的時候也發現,看到後面,前面就忘了,有點像狗熊掰玉米。。。所以決定回過頭來再過一遍,這一次只看那些比較重要的知識點,太詳細了也終歸還是記不住。另外,這篇博客是使用 Markdown寫的,第一次用,估計寫出來的格式可能會有點醜。。 1. ...
一周排行
    -Advertisement-
    Play Games
  • 前言 本文介紹一款使用 C# 與 WPF 開發的音頻播放器,其界面簡潔大方,操作體驗流暢。該播放器支持多種音頻格式(如 MP4、WMA、OGG、FLAC 等),並具備標記、實時歌詞顯示等功能。 另外,還支持換膚及多語言(中英文)切換。核心音頻處理採用 FFmpeg 組件,獲得了廣泛認可,目前 Git ...
  • OAuth2.0授權驗證-gitee授權碼模式 本文主要介紹如何筆者自己是如何使用gitee提供的OAuth2.0協議完成授權驗證並登錄到自己的系統,完整模式如圖 1、創建應用 打開gitee個人中心->第三方應用->創建應用 創建應用後在我的應用界面,查看已創建應用的Client ID和Clien ...
  • 解決了這個問題:《winForm下,fastReport.net 從.net framework 升級到.net5遇到的錯誤“Operation is not supported on this platform.”》 本文內容轉載自:https://www.fcnsoft.com/Home/Sho ...
  • 國內文章 WPF 從裸 Win 32 的 WM_Pointer 消息獲取觸摸點繪製筆跡 https://www.cnblogs.com/lindexi/p/18390983 本文將告訴大家如何在 WPF 裡面,接收裸 Win 32 的 WM_Pointer 消息,從消息裡面獲取觸摸點信息,使用觸摸點 ...
  • 前言 給大家推薦一個專為新零售快消行業打造了一套高效的進銷存管理系統。 系統不僅具備強大的庫存管理功能,還集成了高性能的輕量級 POS 解決方案,確保頁面載入速度極快,提供良好的用戶體驗。 項目介紹 Dorisoy.POS 是一款基於 .NET 7 和 Angular 4 開發的新零售快消進銷存管理 ...
  • ABP CLI常用的代碼分享 一、確保環境配置正確 安裝.NET CLI: ABP CLI是基於.NET Core或.NET 5/6/7等更高版本構建的,因此首先需要在你的開發環境中安裝.NET CLI。這可以通過訪問Microsoft官網下載並安裝相應版本的.NET SDK來實現。 安裝ABP ...
  • 問題 問題是這樣的:第三方的webapi,需要先調用登陸介面獲取Cookie,訪問其它介面時攜帶Cookie信息。 但使用HttpClient類調用登陸介面,返回的Headers中沒有找到Cookie信息。 分析 首先,使用Postman測試該登陸介面,正常返回Cookie信息,說明是HttpCli ...
  • 國內文章 關於.NET在中國為什麼工資低的分析 https://www.cnblogs.com/thinkingmore/p/18406244 .NET在中國開發者的薪資偏低,主要因市場需求、技術棧選擇和企業文化等因素所致。歷史上,.NET曾因微軟的閉源策略發展受限,儘管後來推出了跨平臺的.NET ...
  • 在WPF開發應用中,動畫不僅可以引起用戶的註意與興趣,而且還使軟體更加便於使用。前面幾篇文章講解了畫筆(Brush),形狀(Shape),幾何圖形(Geometry),變換(Transform)等相關內容,今天繼續講解動畫相關內容和知識點,僅供學習分享使用,如有不足之處,還請指正。 ...
  • 什麼是委托? 委托可以說是把一個方法代入另一個方法執行,相當於指向函數的指針;事件就相當於保存委托的數組; 1.實例化委托的方式: 方式1:通過new創建實例: public delegate void ShowDelegate(); 或者 public delegate string ShowDe ...