Java IO 第2篇:IO 流,掌控一切

IO 流,掌控一切

上一篇文章我們認識了文件操作的源頭 File 類,這篇文章就來聊聊文件操作的核心 IO 流。
我們經??梢月牭剑狠斎肓?、輸出流、字節(jié)流、字符流、節(jié)點流、處理流等詞語,咋一聽,忍不住“哇~~~!”的一聲,心里在想:“感覺好復雜的樣子,學習 IO 流需要知道這么多東西啊!”,從而有了畏難的情緒。大家千萬不要被這些詞語嚇到,望而卻步,它們只不過是從三個維度對 IO 流的總結。
學習 IO 流是有套路的,通過這篇文章的學習,你一定能掌握 IO 流的使用技巧,從而掌控一切文件操作問題。

一、認識 IO 流

1、IO 流的分類

從 IO 流的流向來劃分,IO 流分為:輸入流、輸出流。

從 IO 流要處理的數(shù)據(jù)來劃分,IO 流分為:字節(jié)流、字符流。其中,字節(jié)流可以處理一切文件數(shù)據(jù),包括純文本,word文檔,pdf文檔,圖片,音頻和視頻等二進制數(shù)據(jù);字符流只能處理純文本文件。

從 IO 流的功能來劃分,IO 流分為:節(jié)點流和處理流。其中,節(jié)點流是用來包裝數(shù)據(jù)源(File)的,它直接和數(shù)據(jù)源連接,表示從一個節(jié)點讀取數(shù)據(jù)或者把數(shù)據(jù)寫入到一個節(jié)點;處理流是用來包裝節(jié)點流的,它是對一個已經存在的節(jié)點流進行連接,處理流通過增加緩存的方式來提高輸入輸出操作的性能。

總的來說,java.io 包中流的操作主要分為字節(jié)流和字符流兩類,他倆都有對應的節(jié)點流與數(shù)據(jù)源進行連接,為了提高文件操作的性能,在節(jié)點流的基礎上提供了處理流,以便增強節(jié)點流的功能,同時他倆都有輸入和輸出操作。

通過上面的分類,大家先對 IO 流先有一個初步的了解,后面結合代碼給大家進一步講解。

2、區(qū)分流的輸入與輸出

在程序中所有的數(shù)據(jù)都是以流的方式進行傳輸?shù)?,程序需要?shù)據(jù)的時候就用輸入流讀取數(shù)據(jù),當程序需要將計算好的數(shù)據(jù)進行保存到文件或者輸出到其他系統(tǒng)時,就用輸出流寫出數(shù)據(jù)。

簡單來說的話,就是以我們的程序為中心,如果是外部的數(shù)據(jù)流向程序,那么就是輸入流,輸入流一定是讀取操作;如果是程序里的數(shù)據(jù)流出到外部,那么就是輸出流,輸出流一定是寫出操作。

輸入輸出流與文件和程序的關系示意圖

3、IO 操作的套路

Java 中 IO 操作也是有套路的,有標準的操作步驟,主要的操作步驟如下:

1、使用 File 類與文件建立聯(lián)系

2、選擇對應的輸入流或者輸出流

3、進行讀或寫操作

4、關閉資源

先對這個套路進行一個了解,后面結合代碼一下就明白了,原來套路如此簡單。

二、萬能鑰匙字節(jié)流

1、認識字節(jié)流

字節(jié)流主要操作 byte 類型數(shù)據(jù),說它是萬能鑰匙,是因為它可以處理一切文件,包括文本、word文檔、Excel文檔、pdf文檔、圖片、語音、視頻等,統(tǒng)統(tǒng)都可以處理。

字節(jié)流分為字節(jié)輸入流和字節(jié)輸出流,在 Java 中 字節(jié)輸入流用 InputStream 表示,字節(jié)輸出流用 OutputStream 表示。

字節(jié)輸入流:InputStream 是一個抽象類,必須依靠其子類 FileInputStream 來讀取文件內容,輸入到程序中。我們常用的方法是:

int read(byte b[]) //讀取byte數(shù)組中的內容,返回讀入的長度
close() //關閉資源

字節(jié)輸出流:OutputStream 是一個抽象類,必須依靠其子類 FileOutputStream 來讀取文件內容,輸入到程序中。我們常用的方法是:

//將一個制定范圍的byte數(shù)組輸出
void write(byte b[], int off, int len) 
close() //關閉資源
flush() // 在關閉資源的時候默認會調用刷新方法

2、字節(jié)輸出流 FileOutputStream 的使用

我們來看一個例子,把“演示字節(jié)輸出流的使用\r\n用 FileOutputStream 類操作!”的文本輸出到 D:/file/txt/output.txt 文件中。

因為文件操作有可能發(fā)生 FileNotFoundException 和 IOException,為了精簡代碼,便于閱讀主要代碼,除了本例子以外,后續(xù)的例子我會直接使用 throws 關鍵字拋出異常,并且關閉資源也不放在finally里,這樣可以減少 try...catch...finally的代碼。

@Test
  public void testOutput() {
    // 1、建立聯(lián)系, File對象, 輸出文件的地址
    // 如果文件不存在則可以創(chuàng)建文件并寫入,
    // 但是如果加了文件夾,那么文件夾不存在則會產生FileNotFoundException,系統(tǒng)找不到指定的路徑
    String path = "D:/txt/output.txt";
    File file = new File(path);
    // 2、選擇流
    // 由于os要在finally中用到,放到try的外部,以提升os的變量作用范圍
    OutputStream os = null;
    try {
      // 用FileOutputStream子類實例化父類OutputStream
      // 以追加的方式輸出到文件,必須是true,否則就會覆蓋原有的文件
      os = new FileOutputStream(file, true);
      // 3、操作
      String info = "演示字節(jié)輸出流的使用\r\n用 FileOutputStream 類操作!\r\n";
      byte[] b = info.getBytes();// 字符串轉字節(jié)數(shù)組
      os.write(b, 0, b.length);// 寫出
      // 要養(yǎng)成這個習慣,為了避免緩存沒有寫出去,需要顯示地flush一下
      os.flush();
    } catch (FileNotFoundException e) {
      e.printStackTrace();
      System.out.println("文件不存在");
    } catch (IOException e) {
      e.printStackTrace();
      System.out.println("文件寫出失敗");
    } finally {
      try {
        // 4、釋放資源
        if (os != null) {
          os.close();
        }
      } catch (Exception e2) {
        System.out.println("關閉文件輸出流資源失敗");
      }
    }
  }

運行結果:


字節(jié)輸出流 FileOutputStream 的使用

3、字節(jié)輸入流 FileInputStream 的使用

上面的例子我們學會了字節(jié)輸出流的使用,下面用字節(jié)輸入流 FileInputStream 來讀取上面的文件內容。

@Test
  public void testInput() throws IOException {
    // 1、建立聯(lián)系
    File file = new File("D:/output.txt");
    // 2、選擇流
    InputStream is = new FileInputStream(file);
    // 3、讀操作:即不斷地讀取
    byte[] b = new byte[1024]; // 緩存數(shù)組
    int len = 0; // 接收實際讀取的大小
    while ((len = is.read(b)) != -1) {
      // 能讀取到數(shù)據(jù)則輸出,字節(jié)數(shù)組轉成字符串
      String info = new String(b, 0, len);
      System.out.println(info);
    }
    is.close();
  }

運行結果:

演示字節(jié)輸出流的使用
用 FileOutputStream 類操作!
演示字節(jié)輸出流的使用
用 FileOutputStream 類操作!

4、使用字節(jié)流,完成圖片文件的拷貝

下面的例子演示如何通過字節(jié)流對圖片文件進行拷貝操作,假設把 tomcat.png 拷貝成 tomcat1.jpg。

文件的拷貝操作的思路就是,用字節(jié)輸入流讀取圖片 tomcat.png 的內容,用字節(jié)輸出流寫出到 tomcat1.jpg 文件中,根據(jù)文件操作的套路,很容易就能寫出以下的代碼:

@Test
  public void testCopy() throws IOException {
    // 1、使用File類與文件建立聯(lián)系
    File srcFile = new File("D:/file/image/tomcat.png");
    File destFile = new File("D:/file/image/tomcat1.jpg");
    // 2、選擇對應的輸入流或者輸出流
    InputStream is = new FileInputStream(srcFile);
    OutputStream os = new FileOutputStream(destFile);
      // 3、進行讀或寫操作
      byte[] b = new byte[1024];
      int len = 0;
      while ((len = is.read(b)) != -1) {
        // 判斷每次讀取的內容長度,如果不等于-1,表示文件沒有讀完
        // 選擇帶參數(shù)的write方法,就是為了避免byte緩存比實際內容多的時候,輸出多余的空內容
        os.write(b, 0, len);
      }
    os.flush();
    // 4、關閉資源,先創(chuàng)建的后關閉
    os.close();
    is.close();
  }

運行結果:


使用字節(jié)流,完成圖片文件的拷貝

三、純文本操作字符流

1、認識字符流

字符流主要操作純文本類型數(shù)據(jù),只能處理 txt、html 等文本類型的數(shù)據(jù),在程序中一個字符等于兩個字節(jié),Java 提供了 Reader 類和 Writer 類用于專門操作字符流。

字符流也分為字符輸入流和字符輸出流,在 Java 中 字符輸入流用 Reader 表示,輸出流用 Writer 表示。

字符輸入流:Reader 是一個抽象類,必須依靠其子類 FileReader 來讀取純文本文件內容,輸入到程序中。我們常用的方法是:

int read(char cbuf[]) //讀取char數(shù)組中的內容,返回讀入的長度
close() //關閉資源

字符輸出流:Writer 是一個抽象類,必須依靠其子類 FileWriter 來讀取純文本文件內容,輸入到程序中。我們常用的方法是:

//將一個字符串輸出
void write(String str)
//將一個字符數(shù)組輸出
void write(char cbuf[], int off, int len)
close() //關閉資源
flush() // 在關閉資源的時候默認會調用刷新方法

2、字符輸出流 FileWriter 的使用

我們來看一個例子,把“演示字符輸出流的使用\r\n用 FileWriter 類操作!”的文本輸出到 D:/file/txt/output_char.txt 文件中。

@Test
  public void testWriter() throws IOException {
    // 1、使用File類與文件建立聯(lián)系
    File file = new File("D:/file/txt/output_char.txt");
    // 2、選擇對應的輸入流或者輸出流
    Writer writer = new FileWriter(file, true);
    String info = "演示字符輸出流的使用\r\n用 FileWriter 類操作!\r\n";
    // 3、進行寫操作
    writer.write(info); //將一個字符串組輸出
    writer.flush();
    // 4、關閉資源
    writer.close();
  }

運行結果:


字符輸出流 FileWriter 的使用

3、字符輸入流 FileReader 的使用

上面的例子我們學會了字符輸出流的使用,下面用字符輸入流 FileReader 來讀取上面的文件內容。

@Test
  public void testReader() throws IOException {
    // 1、使用File類與文件建立聯(lián)系
    File file = new File("D:/file/txt/output_char.txt");
    // 2、選擇對應的輸入流或者輸出流
    Reader reader = new FileReader(file);
    char[] cbuf = new char[1024];
    int len = 0;
    // 3、進行寫操作
    while ((len = reader.read(cbuf)) != -1) {
      String info = new String(cbuf, 0, len); // 字符數(shù)組轉成字符串
      System.out.println(info);
    }
    // 4、關閉資源
    reader.close();
  }

運行結果:


演示字符輸出流的使用
用 FileWriter 類操作!
演示字符輸出流的使用
用 FileWriter 類操作!

4、利用字符流,完成 txt文本文件的拷貝

下面的例子演示如何通過字符流對圖片文件進行拷貝操作,把 output_char.txt 拷貝成 output_char1.txt。

@Test
  public void testTxtCopy() throws IOException {
    // 1、使用File類與文件建立聯(lián)系
    File srcFile = new File("D:/file/txt/output_char.txt");
    File destFile = new File("D:/file/txt/output_char1.txt");
    // 2、選擇對應的輸入流或者輸出流
    Reader read = new FileReader(srcFile);
    Writer write = new FileWriter(destFile);
    // 3、進行讀寫操作
    char[] cbuf = new char[1024];
    int len = 0;
    while ((len = read.read(cbuf)) != -1) {
      write.write(cbuf, 0, len); //將一個字符數(shù)組輸出
    }
    write.flush();
    // 4、關閉資源
    write.close();
    read.close();
  }

運行結果:

利用字符流,完成 txt文本文件的拷貝

四、字節(jié)流與字符流的區(qū)別

1、字符輸出流在寫出文件時用到了緩存區(qū)

除去剛才講過的,字節(jié)流可以處理一切文件,字符流只能處理純文本文件,兩者還有一個明顯的差異,那就是字符輸出流在操作文件時使用了緩沖區(qū),通過緩沖區(qū)再寫出到文件,而字節(jié)輸出流直接操作文件。

1、通過源碼可以證明字符輸出流用到了緩存區(qū)

通過源碼可以證明字符輸出流用到了緩存區(qū)

2、通過兩段代碼的輸出結果證明字符輸出流用到了緩存區(qū)

  • 驗證字符流:
/**
   * 把flush方法和close方法去掉,觀察程序運行結果,用字符流輸出內容到文件是空的
   */
  @Test
  public void testWriter1() throws IOException {
    // 1、使用File類與文件建立聯(lián)系
    File file = new File("D:/file/txt/output_char_buffer.txt");
    // 2、選擇對應的輸入流或者輸出流
    Writer writer = new FileWriter(file, true);
    String info = "把flush方法和close方法去掉,觀察程序運行結果,輸出的內容文件是空的!\r\n";
    // 3、進行寫操作
    writer.write(info);
  }

運行結果:

驗證字符輸入流用到了緩存區(qū)
  • 驗證字節(jié)流:
/**
   * 把flush方法和close方法去掉,觀察程序運行結果,用字節(jié)流可以輸出內容到文件
   */
  @Test
  public void testOutput1() throws IOException {
    // 1、使用File類與文件建立聯(lián)系
    File file = new File("D:/file/txt/output_char_output.txt");
    // 2、選擇對應的輸入流或者輸出流
    OutputStream os = new FileOutputStream(file, true);
    // 3、進行寫操作
    String info = "把flush方法和close方法去掉,觀察程序運行結果,輸出的內容文件是空的!\r\n";
    byte[] b = info.getBytes();// 字符串轉字節(jié)數(shù)組
    os.write(b, 0, b.length);// 寫出
  }

運行結果:

驗證字節(jié)輸入流不用緩存區(qū)

通過以上的 2 段程序,可以看出,字符流是有緩存的,如果我們沒有調用 flush 方法,并且沒有調用 close 方法,是無法把內容寫到文件中的。但是同樣的沒有調用 flush 方法和 close 方法,字節(jié)流確可以把內容寫出到文件。

  • 驗證字符流調用 flush方法,不調用 close 方法的結果
/**
   * 調用flush方法,不調用close方法,觀察程序運行結果,用字符流輸出內容到文件是可以的,說明字符輸出流確實用到了緩沖區(qū)
   */
  @Test
  public void testWriter2() throws IOException {
    // 1、使用File類與文件建立聯(lián)系
    File file = new File("D:/file/txt/output_char_writer.txt");
    // 2、選擇對應的輸入流或者輸出流
    Writer writer = new FileWriter(file);
    String info = "調用flush方法,不調用close方法,觀察程序運行結果,用字符流輸出內容到文件是可以的,說明字符輸出流確實用到了緩沖區(qū)!\r\n";
    // 3、進行寫操作
    writer.write(info);
    // 4、強制刷出
    writer.flush();
  }

運行結果:

驗證字符流調用 flush方法,不調用 close 方法的結果
  • 驗證字符流調用 close 方法,不調用 flush 方法的結果
/**
   * 調用close方法,不調用flush方法,觀察程序運行結果,用字符流輸出內容到文件是可以的,說明字符輸出流確實用到了緩沖區(qū)
   */
  @Test
  public void testWriter3() throws IOException {
    // 1、使用File類與文件建立聯(lián)系
    File file = new File("D:/file/txt/output_char_writer.txt");
    // 2、選擇對應的輸入流或者輸出流
    Writer writer = new FileWriter(file);
    String info = "調用close方法,不調用flush方法,觀察程序運行結果,用字符流輸出內容到文件是可以的,說明字符輸出流確實用到了緩沖區(qū)!\r\n";
    // 3、進行寫操作
    writer.write(info);
    // 4、關閉資源
    writer.close();
  }

運行結果:

驗證字符流調用 close 方法,不調用 flush 方法的結果

通過以上的 2 段程序,可以看出,字符流是有緩存的,通過顯示調用 flush 方法可以把緩存內容輸出到文件,如果沒有調用 flush 方法,在調用 close 方法時,默認也是會把緩存內容輸出到文件。

切記字符輸出流在flush方法和close方法都沒有調用的時候,是無法輸出內容到文件的。為了避免出現(xiàn)此類問題,我們在使用輸出流的時候,不管是字節(jié)流還是字符流最好都顯示的調用一下 flush 方法。

講了這么多,大家覺得我們在操作文件的時候是用字節(jié)流好呢還是用字符流好呢,答案是使用字節(jié)流更好,因為所有的文件在磁盤中以及網絡傳輸都是以二進制的字節(jié)傳輸?shù)模?strong>在實際開發(fā)中,字節(jié)流用的比較廣泛。

我們再來明確一下,文件操作的套路只有4步:

1、使用File類與文件建立聯(lián)系
2、選擇對應的輸入流或者輸出流
3、進行讀或寫操作
4、關閉資源

另外讀寫操作也是有固定套路的:

 byte[] b = new byte[1024];
    int len = 0;
    while ((len = is.read(b)) != -1) {
      os.write(b, 0, len);
 }
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

  • 1、IO流 1.1、概述 之前學習的File類它只能操作文件或文件夾,并不能去操作文件中的數(shù)據(jù)。真正保存數(shù)據(jù)的是文...
    Villain丶Cc閱讀 2,793評論 0 5
  • 五、IO流 1、IO流概述 (1)用來處理設備(硬盤,控制臺,內存)間的數(shù)據(jù)。(2)java中對數(shù)據(jù)的操作都是通過...
    佘大將軍閱讀 588評論 0 0
  • tags:io categories:總結 date: 2017-03-28 22:49:50 不僅僅在JAVA領...
    行徑行閱讀 2,304評論 0 3
  • 1.Java Io流的概念,分類,類圖。 1.1 Java Io流的概念 ? ? java的io是實現(xiàn)輸入和輸出...
    hedgehog1112閱讀 751評論 0 0
  • 1.java IO流的概念,分類,類圖 1.1. java IO 流的概念 java的io是實現(xiàn)輸入和輸出的基礎,...
    onlyHalfSoul閱讀 624評論 0 1

友情鏈接更多精彩內容