1. 程式人生 > 實用技巧 >Java IO流初相識

Java IO流初相識

流是一種抽象概念,它代表了資料的無結構化傳遞。按照流的方式進行輸入輸出,資料被當成無結構的位元組序或字元序列。從流中取得資料的操作稱為提取操作,而向流中新增資料的操作稱為插入操作。用來進行輸入輸出操作的流就稱為IO流。換句話說,IO流就是以流的方式進行輸入輸出 。---百度百科

Java中IO流的分類

按照流的流向:

分為輸入流和輸出流

通常我們說的輸入輸出是從記憶體的角度來劃分的,例如下圖資料從服務端到客戶端,客戶端的內容負責從網路裡讀取資料,所以客戶端的程式應該使用輸入流。

按照操作單元:

分為位元組流和字元流

字元流和位元組流的用法幾乎一致,區別在於位元組流操作的資料單元是8位的位元組,而字元流次奧組的資料單元是16位的字元。

Java中IO流共涉及40多個類,這些類看上去很雜亂,但實際上很有規則,而且彼此之間存在著非常緊密的練習,Java中IO流的40多個類都是從下面四個抽象類基類中派生出來的。

InputStream/Reader:所有的輸入流的基類,前者是位元組輸入流,後者是字元輸入流。

OutputStream/Writer:所有輸出流的基類,前者是位元組輸出流,後者是字元輸出流。

既然有了位元組流,為什麼還要有字元流?

字元流是有Java虛擬機器將位元組轉換得到的。問題是這個過程比較耗時,而且如果事先不知道編碼型別就很容易出現亂碼問題,所以IO流就提供了一個直接操作字元的介面,方便對字元進行流的操作。一般情況下,如果是二進位制檔案(音訊,圖片等)使用位元組流,如果是文字檔案使用字元流操作。(二進位制檔案和文字檔案詳情可看擴充套件)

按照流的角色:

分為節點流和處理流

可以從/向一個特定的IO裝置(磁碟,網路等)讀/寫資料的流,稱為節點流,節點流也稱低階流,從下圖中可以看出,當使用節點流進行輸入/輸出時,程式直接連結到實際的資料來源。

處理流則用於時對一個已存在的流進行連線或者封裝,通過封裝後的流實現資料讀/寫的功能,處理流也稱為高階流如下圖,使用處理流進行輸入/輸出時,程式不會直接連線到實際的資料來源,使用處理流的優點是,只要使用相同的處理流,程式就可以採用完全想的輸入/輸出程式碼來訪問不同的資料來源,隨著處理流所包裝結點流的變化,程式所訪問的資料員也相應的發生變化。

Java位元組流的基本使用

位元組輸出流向檔案中寫一個位元組陣列

public static void main(String[] args) throws IOException {
    //建立FileOutputStream輸出流
    OutputStream os = new FileOutputStream("D:\\demo.txt");
    //呼叫方法,寫入位元組陣列
    //byte[] bArr = "helloWorld".getBytes();
    //os.write(bArr);
    //寫漢字
    //os.write("小舍學Java".getBytes());
    //寫位元組陣列的一部分
    //表示將helloWorld對應的位元組陣列的一部分寫到檔案,從索引為1的位置開始寫, 寫3個
    os.write("helloWorld".getBytes(), 0, 5);//hello
    //釋放資源
    os.close();
}

檔案的續寫

/**
 *     當建立位元組輸出流物件,並傳遞字串檔案的時候, 會建立該檔案,並且覆蓋原來的檔案。
 *     如果想要在原來檔案後面寫內容,要使用其他的構造方法建立流
 *     構造方法:
 *         FileOutputStream(File file, boolean append):第一個引數表示向哪個檔案寫資料,第二個引數表示是否續寫,如果引數是true表示續寫
 *         FileOutputStream(String name, boolean append) :第一個引數表示向哪個檔案寫資料,第二個引數表示是否續寫,如果引數是true表示續寫
 * @param args
 * @throws IOException
 */
public static void main(String[] args) throws IOException {
    //建立FileOutputStream輸出流
    OutputStream os = new FileOutputStream("D:\\demo.txt",true);
    os.write("臨淵羨魚不如退而結網".getBytes());
    //釋放資源
    os.close();
}

換行寫入

/**
 *     如果要向檔案中寫入換行,需要使用換行符。
 *     每種作業系統換行符都不同
 *         windows: \r\n
 *         linux: \n
 *         macOS: \r
 * @param args
 * @throws IOException
 */
public static void main(String[] args) throws IOException {
    //建立FileOutputStream輸出流
    OutputStream os = new FileOutputStream("D:\\demo.txt");
    os.write("臨淵羨魚\r\n不如退而結網".getBytes());
    //釋放資源
    os.close();
}

檔案複製

/**
 *  檔案複製本質就是讀和寫, 將原始檔中的位元組讀取出來,然後寫到新的檔案中即可。
 *
 *     複製步驟:
 *         1. 建立一個位元組輸入流,用來讀取
 *         2. 建立一個位元組輸出流,用來寫。
 *         3. 開始迴圈讀寫。 每讀取一次資料,就將讀取到的資料寫到目的地檔案。
 * @param args
 * @throws IOException
 */
public static void main(String[] args) throws IOException {
    //建立一個位元組輸入流,用來讀取
    InputStream is = new FileInputStream("D:\\demo.txt");
    //建立一個位元組輸出流,用來寫。
    OutputStream os = new FileOutputStream("D:\\copy.txt");
    //開始迴圈讀寫。 每讀取一次資料,就將讀取到的資料寫到目的地檔案。
    //一次讀取一個位元組陣列,然後將讀取到的位元組陣列複製到目的地檔案。
    byte[] bArr = new byte[1024];
    int len;
    while((len = is.read(bArr)) != -1) {
        //如果條件成立,那麼就將讀取到的資料寫到目的地檔案。
        //讀取到的資料儲存在了bArr這個陣列中, 返回值len是讀取到的個數。
        os.write(bArr, 0, len); //將bArr中的一部分寫入到檔案,從索引為0的位置開始寫, 寫len個
    }
    //釋放資源
    os.close();
    is.close();
}

Java字元流的基本使用

使用字元流一次讀取一個字元

public static void main(String[] args) throws IOException {
    //建立字元輸入流物件, 繫結一個數據原始檔。
    Reader r = new FileReader("D:\\demo.txt");
    //呼叫read方法,讀取資料
    //使用一次讀取一個字元的方式讀取資料
    int i; //定義變數i,用來接收每次讀取到的字元。
    while((i = r.read()) != -1) {
        System.out.print((char)i);
    }
    //釋放資源
    r.close();
}

使用字元輸入流讀取一個字元陣列

public static void main(String[] args) throws IOException {
    //建立字元輸入流物件
    Reader r = new FileReader("D:\\demo.txt");
    //呼叫read方法進行讀取(一次讀取一個字元陣列)
    //定義一個字元陣列,用來接收每次讀取到的資料
    char[] cArr = new char[1024];
    //定義變數len,用來接收每次讀取到的字元的個數。
    int len;
    //開始迴圈讀取
    while((len = r.read(cArr)) != -1) {
        //讀取到幾個內容,那麼就將幾個內容轉成字串輸出
        System.out.println(new String(cArr, 0, len));
    }
    //釋放資源
    r.close();
}

字元輸出流的基本使用

/**
 * FileWriter的使用步驟:
 *         1. 建立一個FileWriter字元輸出流
 *         2. 呼叫write方法寫資料。
 *         3. 呼叫flush方法重新整理。
 *         4. 呼叫close方法關閉。
 *
 *     在所有的流中,字元輸出流需要重新整理。
 * @param args
 * @throws IOException
 */
public static void main(String[] args) throws IOException {
    //建立FileWriter物件
    Writer w = new FileWriter("D:\\demo.txt");
    //呼叫write方法寫資料。
    //字元輸出流寫資料的時候,先會把資料放到記憶體緩衝區中, 並沒有直接寫到檔案中。
    //想要把記憶體緩衝區中的資料放到檔案中,那麼需要進行重新整理操作。
    w.write("臨淵羨魚不如退而結網");
    //呼叫flush方法重新整理
    w.flush();
    //釋放資源
    w.close();
}

字元輸出流重新整理方法和close方法的區別

flush:做的僅僅是重新整理的操作。 流在重新整理之後還可以使用。

close:先重新整理,然後關閉流。 流在關閉之後就不能使用了。

為什麼建立Java IO流後必須關閉

程式中開啟的檔案 IO 資源不屬於記憶體裡的資源,垃圾回收機制無法回收該資源。如果不關閉該資源,那麼磁碟的檔案將一直被程式引用著,不能刪除也不能更改。同時還會浪費系統資源的浪費,與資料庫連線類似。所以應該手動呼叫 close() 方法關閉流資源。

在java7之後,可以使用try-with-resources語句來釋放java流物件,其實他就是一個特殊的try...catch語句,從而避免了try-catch-finally語句的繁瑣,尤其是在finally子句中,close()方法也會丟擲異常,其實就是一個特殊的try...catch語句。

其他流型別

轉換流

Java IO流中提供了兩種用於將位元組流轉換為字元流的轉換流。其中InputStreamReader用於將位元組輸入流轉換為字元輸入流,其中OutputStreamWriter用於將位元組輸出流轉換為字元輸出流。使用轉換流可以在一定程度上避免亂碼,還可以指定輸入輸出所使用的字符集

InputStreamReader

/**
 *  InputStreamReader構造方法:
 *     InputStreamReader(InputStream in):引數要傳遞一個位元組輸入流。  使用這個構造方法建立的轉換流物件會採用開發環境的編碼去讀取資料。
 *     InputStreamReader(InputStream in, String charsetName):第一個引數是一個輸入流, 第二個引數是編碼的名字,會採用指定的編碼進行讀取。
 *
 *  InputStreamReader讀取資料的方法:
 *     InputStreamReader裡面的讀取資料的方法和之前字元流讀取資料的方法一模一樣。
 *
 *   使用步驟:
 *     1. 建立一個InputStreamReader用來指定編碼進行讀取。
 *     2. 呼叫read方法進行讀取。
 *     3. 釋放資源
 *
 * @param args
 * @throws IOException
 */
public static void main(String[] args) throws IOException {
    //建立一個InputStreamReader,指定以UTF-8的方式讀取檔案
    InputStreamReader isr = new InputStreamReader(new FileInputStream("d:\\demo.txt"), "UTF-8");//如果不指定編碼,那麼就會以UTF-8的方式讀取
    //開始讀取
    //開始讀取,一次讀取一個字元。
    int i;
    while((i = isr.read()) != -1) {
        System.out.print((char) i);
    }
    //釋放資源
    isr.close();
}

OutputStreamWriter

/**
 *     OutputStreamWriter也是轉換流, 用來寫, 可以指定編碼寫資料。
 *
 *     OutputStreamWriter構造方法:
 *         OutputStreamWriter(OutputStream out): 引數要傳遞一個位元組輸出流, 使用這個構造方法建立的轉換流物件會採用開發環境的編碼寫資料。
 *         OutputStreamWriter(OutputStream out, String charsetName): 第一個引數是位元組輸出流, 第二個引數為指定的編碼方式,會以指定的編碼去寫資料。
 *
 *     OutputStreamWriter裡面寫資料的方法和字元流寫資料的方法一模一樣, 以為OutputStreamWriter是字元流的一種。
 *
 *     使用步驟:
 *         1. 建立流
 *         2. 寫資料。
 *         3. 重新整理。
 *         4. 釋放資源
 * @param args
 * @throws IOException
 */
public static void main(String[] args) throws IOException {
    //建立轉換流
    OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("d:\\demo.txt"), "utf-8");
    //呼叫write方法,寫資料
    osw.write("臨淵羨魚不如退而結網");
    //重新整理
    osw.flush();
    //釋放資源
    osw.close();
}

將GBK編碼的文字檔案,轉換為UTF-8編碼的文字檔案

/**
 *     在讀取的時候指定以GBK的方式讀取檔案, 在寫的時候指定以UTF-8的方法寫即可。
 *
 *     步驟:
 *         1. 建立InputStreamReader,指定以GBK的方式進行讀取。
 *         2. 建立OutputStreamWriter, 指定以UTF-8的方式進行寫。
 *         3. 一邊讀,一邊寫, 每讀取到資料就寫到目的地檔案中。
 *         4. 重新整理
 *         5. 釋放資源。
 * @param args
 * @throws IOException
 */
public static void main(String[] args) throws IOException {
    //建立InputStreamReader,指定以GBK的方式進行讀取。
    InputStreamReader isr = new InputStreamReader(new FileInputStream("d:\\demo.txt"), "GBK");
    //建立OutputStreamWriter, 指定以UTF-8的方式進行寫。
    OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("d:\\copy.txt"), "UTF-8");
    //一邊讀,一邊寫, 每讀取到資料就寫到目的地檔案中。
    int i;
    while((i = isr.read()) != -1) {
        //將讀取出來的資料寫到新的檔案中
        osw.write(i);
        //重新整理
        osw.flush();
    }
    //釋放資源
    osw.close();
    isr.close();
}

緩衝流

緩衝流是處理流的一種, 它依賴於原始的輸入/輸出流, 緩衝流它的特點是可以提高讀寫的效率, 原因是因為內部有一個緩衝區, 這個緩衝區可以起到加速的作用。緩衝流其實本身並不具備讀或者寫的功能, 它的作用是給其他流提供加速。

/**
 * 位元組緩衝流的構造方法
 *         BufferedOutputStream(OutputStream out): 引數要傳遞一個位元組輸出流。
 *         BufferedInputStream(InputStream in): 引數要傳遞一個位元組輸入流。
 *
 *     位元組緩衝流的使用步驟:
 *         1. 建立位元組緩衝流。
 *         2. 讀或者寫
 *         3. 釋放資源
 * @param args
 * @throws IOException
 */
public static void main(String[] args) throws IOException {
    //建立位元組輸入緩衝流,用於讀取
    BufferedInputStream bis = new BufferedInputStream(new FileInputStream("D:\\demo.txt"));
    //建立位元組輸出緩衝流,用來寫入
    BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("D:\\copy.txt"));
    //開始讀寫,一次讀寫一個位元組。
    int i;
    while((i = bis.read()) != -1) {
        bos.write(i);
    }
    //釋放資源
    bos.close();
    bis.close();
}

字元緩衝流中的特有的功能

在BufferedWriter中有一個方法,可以實現一個跨平臺的換行:void newLine() 實現一個跨平臺的換行

列印流

PrintStream是列印流, 列印流是輸出流的一種, 列印流有下面特點:

  1. 列印流只有輸出,沒有輸入。
  2. 輸出資料十分方便。
/**
 *
 *     PrintStream的構造方法:
 *         PrintStream(String fileName): 引數要傳遞一個字串型別的檔案路徑。
 *         PrintStream(File file): 引數要傳遞一個File型別的檔案。
 *         PrintStream(OutputStream out): 傳遞要傳遞一個位元組輸出流。
 *
 *     PrintStream寫資料的方法(特有的方法):
 *         void print(任意型別): 輸出資料,但是不會換行。
 *         void println(任意型別): 輸出資料,但是會換行。
 *
 *     使用步驟:
 *         1. 建立列印流
 *         2. 寫資料。
 *         3. 關流
 * @param args
 * @throws IOException
 */
public static void main(String[] args) throws IOException {
    //建立列印流
    PrintStream ps = new PrintStream("D:\\demo.txt");
    ps.println("臨淵羨魚");
    ps.println("不如退而結網");
    //釋放資源
    ps.close();
}

拓展

編碼表

編碼表是由現實世界的字元和其對應的數值組成的一張表,用來解析和轉換各種字元,是一種程式碼說明表格。作用是用來幫助使用者明確無解釋資料和字元程式碼的含義。

大家都知道在計算機中儲存的資料都是位元組,而我們在計算機中看到的是字元(漢字,英文),這是因為在開啟檔案時編輯工具默默的進行了解碼。

位元組(Byte):指一小組相鄰的二進位制數碼,二進位制資料的單位。 一個位元組通常8位長,是一個很具體的儲存空間。0x01, 0x45, 0xFA……

字元:是指計算機中使用的字母、數字、字和符號,通俗的說就是人們使用的記號,抽象意義上的一個符號。包括:1、2、3、A、B、C、~!·#¥%……—*()+等等。

編碼:字元 -> 位元組,字元轉化為位元組稱為編碼

解碼:位元組 -> 字元,位元組轉化為字元稱為解碼

亂碼:因為檔案存的格式和讀取格式不一致就會亂碼了。簡單的說亂碼的出現是因為: 編碼 (encode) 和 解碼(decode) 時用了不同或者不相容的字符集。

不同編碼標準裡,字元和位元組的對應關係不同。

ASCII碼中:一共有128個字元(常見的英文字母標點符號等)

  1. 一個英文字母(不分大小寫)佔一個位元組的空間;
  2. 一箇中文漢字佔兩個位元組的空間。

Unicode編碼中:

  1. 一個英文字元等於兩個位元組;
  2. 英文標點佔一個位元組;
  3. 一箇中文(含繁體)等於兩個位元組;
  4. 中文標點佔兩個位元組.

UTF-8編碼中:

  1. 一個英文字元等於一個位元組;
  2. 英文標點佔一個位元組
  3. 一箇中文(含繁體)等於三個位元組
  4. 中文標點佔三個位元組,

UTF-16編碼中:

  1. 一個英文字母字元需要2個位元組
  2. 一個漢字字元儲存需要2個位元組(Unicode擴充套件區的一些漢字儲存需要4個位元組)。

UTF-32編碼中:

  1. 世界上任何字元的儲存都需要4個位元組。

二進位制檔案和文字檔案

大家都知道計算機的儲存在物理上是二進位制的,所以文字檔案與二進位制檔案的區別並不是物理上的,而是邏輯上的。這兩者只是在編碼層次上有差異。

簡單來說,文字檔案是基於字元編碼的檔案,常見的編碼有ASCII編碼,UNICODE編碼等等。二進位制檔案是基於值編碼的檔案,你可以根據具體應用,指定某個值是什麼意思(這樣一個過程,可以看作是自定義編碼)。

廣義的二進位制檔案即指檔案,由檔案在外部裝置的存放形式為二進位制而得名。狹義的二進位制檔案即除文字檔案以外的檔案。

第一是二進位制檔案比較節約空間,這兩者儲存字元型資料時並沒有差別。但是在儲存數字,特別是實型數字時,二進位制更節省空間;第二個原因是,記憶體中參加計算的資料都是用二進位制無格式儲存起來的,因此,使用二進位制儲存到檔案就更快捷。如果儲存為文字檔案,則需要一個轉換的過程。在資料量很大的時候,兩者就會有明顯的速度差別了。第三,就是一些比較精確的資料,使用二進位制儲存不會造成有效位的丟失問題。