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
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...