數據讀寫API——IO流

  • 2019 年 10 月 6 日
  • 筆記

理清一些概念

1.Java 中的IO是幹啥的?

IO指的是Input和Output,主要目的是實現數據在存儲介質之間的傳輸。【流:數據流,類比與水流的流動】

2.IO分類

按照操作單元來劃分,可以分為位元組流和字元流。

4.位元組流和字元流的區別

位元組流顧名思義操作的數據單元是1個位元組,也就是8位;

那麼問題來了,一個中文字元用GBK編碼佔用兩個位元組,用UTF-8佔用三個字元。

位元組流在傳輸有中文字元的數據時會產生編碼錯誤。

而字元流可操作2個位元組,也就是16位。那麼在GBK的編碼下,支援中文數據傳輸。

總結如下

  • 位元組流一次讀取一個位元組,字元流兩個
  • 位元組流可以處理所有類型數據,字元流只能處理字元類數據

4.字元編碼的前世今生

ASCII 碼

電腦內部,所有資訊最終都是一個二進位值。每一個二進位位(bit)有01兩種狀態,因此八個二進位位就可以組合出256種狀態,這被稱為一個位元組(byte)。

也就是說,一個位元組一共可以用來表示256種不同的狀態,每一個狀態對應一個符號,就是256個符號,從0000000011111111

上個世紀60年代,美國制定了一套字元編碼,對英語字元與二進位位之間的關係,做了統一規定。這被稱為 ASCII 碼,一直沿用至今。

【ASCII 碼==英文字元和二進位之間的映射關係】

Unicode

世界上存在著多種編碼方式,同一個二進位數字可以被解釋成不同的符號。因此,要想打開一個文本文件,就必須知道它的編碼方式,否則用錯誤的編碼方式解讀,就會出現亂碼。電子郵件常常出現亂碼就是因為發信人和收信人使用的編碼方式不一樣。

可以想像,如果有一種編碼,將世界上所有的符號都納入其中。每一個符號都給予一個獨一無二的編碼,那麼亂碼問題就會消失。這就是 Unicode,就像它的名字都表示的,這是一種所有符號的編碼。

Unicode 當然是一個很大的集合,現在的規模可以容納100多萬個符號。每個符號的編碼都不一樣,比如,U+0639表示阿拉伯字母AinU+0041表示英語的大寫字母AU+4E25表示漢字。具體的符號對應表,可以查詢unicode.org,或者專門的漢字對應表

Unicode 的問題

Unicode雖然實現了編碼的一統,但Unicode 只是一個符號集,它只規定了符號的二進位程式碼,卻沒有規定這個二進位程式碼應該如何存儲。

比如,漢字的 Unicode 是十六進位數4E25,轉換成二進位數足足有15位(100111000100101),也就是說,這個符號的表示至少需要2個位元組。表示其他更大的符號,可能需要3個位元組或者4個位元組,甚至更多。

這裡就有兩個嚴重的問題,第一個問題是,如何才能區別 Unicode 和 ASCII ?電腦怎麼知道三個位元組表示一個符號,而不是分別表示三個符號呢?第二個問題是,我們已經知道,英文字母只用一個位元組表示就夠了,如果 Unicode 統一規定,每個符號用三個或四個位元組表示,那麼每個英文字母前都必然有二到三個位元組是0,這對於存儲來說是極大的浪費,文本文件的大小會因此大出二三倍,這是無法接受的。

它們造成的結果是:

1)出現了 Unicode 的多種存儲方式,也就是說有許多種不同的二進位格式,可以用來表示 Unicode。

2)Unicode 在很長一段時間內無法推廣,直到互聯網的出現。

UTF-8

互聯網的普及,強烈要求出現一種統一的編碼方式。UTF-8 就是在互聯網上使用最廣的一種 Unicode 的實現方式。其他實現方式還包括 UTF-16(字元用兩個位元組或四個位元組表示)和 UTF-32(字元用四個位元組表示),不過在互聯網上基本不用。

這裡的關係是,UTF-8 是 Unicode 的實現方式之一。

UTF-8 最大的一個特點,就是它是一種變長的編碼方式。它可以使用1~4個位元組表示一個符號,根據不同的符號而變化位元組長度。

5.節點流,處理流

節點流:直接從數據源或目的地讀寫數據

處理流:不直接連接到數據源或目的地,而是「連接」在已存 在的流(節點流或處理流)之上,通過對數據的處理為程式提 供更為強大的讀寫功能。

常用流

InputStream/Reader:向外邊讀數據

OutpusStream/Writer:向外邊寫數據

節點流(文件流)

import java.io.*;    public class Main {        public static void main(String args[]) throws IOException      {          /**           * 讀取文件           * 1.建立一個流對象,將已存在的一個文件載入進流。           *  FileReader fr = new FileReader(new File(「Test.txt」));           * 2.創建一個臨時存放數據的數組。           *  char[] ch = new char[1024];           * 3.調用流對象的讀取方法將流中的數據讀入到數組中。           *  fr.read(ch);           * 4. 關閉資源。           *  fr.close();           */            FileReader fr = null;                fr = new FileReader(new File("c:\file.txt"));              char[] buf = new char[1024];              int len;              while ((len = fr.read(buf)) != -1)              {                  System.out.print(new String(buf, 0, len));              }              fr.close();              /**           * 寫入文件           * 1.創建流對象,建立數據存放文件           *  FileWriter fw = new FileWriter(new File(「Test.txt」));           * 2.調用流對象的寫入方法,將數據寫入流           *  fw.write(「atguigu-songhongkang」);           * 3.關閉流資源,並將流中的數據清空到文件中。           *  fw.close();           */            FileWriter fw = new FileWriter(new File("d:\filewriter.txt"));              fw.write("9999999999999");                fw.close();              /**           * 在寫入一個文件時,如果使用構造器FileOutputStream(file),則目錄下有同名文           * 件將被覆蓋。           *  如果使用構造器FileOutputStream(file,true),則目錄下的同名文件不會被覆蓋,           * 在文件內容末尾追加內容。           */        }  }

緩衝流(一種處理流)

為了提高數據讀寫的速度,Java API提供了帶緩衝功能的流類,在使用這些流類 時,會創建一個內部緩衝區數組,預設使用8192個位元組(8Kb)的緩衝區。

緩衝流要「套接」在相應的節點流之上,根據數據操作單位可以把緩衝流分為:

BufferedInputStream BufferedOutputStream

BufferedReader BufferedWriter

只要關閉最外層流即可,關閉最外層流也 會相應關閉內層節點流。

flush()方法的使用:手動將buffer中內容寫入文件。

import java.io.*;    public class Main {        public static void main(String args[]) throws IOException      {                  BufferedReader br =  new BufferedReader(new FileReader("d:\br.txt"));              BufferedWriter bw =  new BufferedWriter(new FileWriter("d:\bw.txt"));                String str;              while ((str = br.readLine()) != null) { // 一次讀取字元文本文件的一行字元                  System.out.println(str);                  bw.write(str); // 一次寫入一行字元串                  bw.newLine(); // 寫入行分隔符              }              bw.flush(); // 刷新緩衝區            br.close();          bw.close();      }  }

轉換流

轉換流提供了在位元組流和字元流之間的轉換

Java API提供了兩個轉換流:

InputStreamReader:將InputStream轉換為Reader

OutputStreamWriter:將Writer轉換為OutputStream

  • 位元組流中的數據都是字元時,轉成字元流操作更高效。
  • 很多時候我們使用轉換流來處理文件亂碼問題。實現編碼和 解碼的功能。
import java.io.*;    public class Main {        public static void main(String args[]) throws IOException      {          /**           * 將old.txt中的文件讀出寫入到new.txt里           */              FileInputStream fis = new FileInputStream("d:\old.txt");          FileOutputStream fos = new FileOutputStream("d:\new.txt");            InputStreamReader isr = new InputStreamReader(fis, "GBK");          OutputStreamWriter osw = new OutputStreamWriter(fos, "GBK");            BufferedReader br = new BufferedReader(isr);          BufferedWriter bw = new BufferedWriter(osw);            String str = null;          while ((str = br.readLine()) != null) {              bw.write(str);              bw.newLine();              bw.flush();          }          bw.close();          br.close();      }  }

參考資料:

字元編碼筆記:ASCII,Unicode 和 UTF-8https://link.zhihu.com/?target=http%3A//www.ruanyifeng.com/blog/2007/10/ascii_unicode_and_utf-8.html