I/O 指的是 input 和 output ,也就是輸入和輸出,我們說的是 Java 中的 I/O,那我們就在站在虛擬機器的角度去看看有哪些輸入和輸出。輸入又可以稱為資料來源端,能想到的會有,檔案,網路,控制台手動輸入。而輸出又可以稱為資料接收端,能想到依舊還是那幾個,輸出到檔案,網路,控制台。
那好,目前只是理清楚了資料從哪裡來到哪裡去,然而,我們的資料互動肯定不是這麼的簡單,我們還需要考慮資料轉送的多種方式,我是以字元傳輸還是位元組傳輸,或是二進位傳輸,要不要緩衝存取,等等問題。這樣一來,想要表示出資料的傳輸可想而知肯定會需要很多個物件。
為瞭解決上述存在的多種多樣的資料端和資料互動方式,Java 設計者們以避免設計過多的類為初衷(其實類並不少...)設計了 I/O 體系。
先來放整體圖,這個圖簡易卻不簡單,今天我也只是說其中的一小部分東西,好多的實作類別都沒有拿出來單獨說。
首先來看看 File 類,File 類以抽象的方式代表檔案名稱和目錄路徑名。該類主要用於檔案和目錄的建立、檔案的尋找和檔案的刪除等。看清楚了,File 類雖然名字看起來像是指檔案,實際上並非如此,它既能代表一個特定檔案的名稱,又能代表一個目錄下一組檔案的名稱。
我們來看個例子感受一下 File 類的使用。
public static void main(String args[]) { String dirname = "."; File f1 = new File(dirname); //當前項目工作目錄 if (f1.isDirectory()) { System.out.println("Directory of " + dirname); String s[] = f1.list(); for (int i = 0; i < s.length; i++) { File f = new File(dirname + "/" + s[i]); if (f.isDirectory()) { System.out.println(s[i] + " is a directory"); } else { System.out.println(s[i] + " is a file"); } } } else { System.out.println(dirname + " is not a directory"); } }
那我想要向檔案中讀取或是寫入內容怎麼辦呢 ?那就需要藉助輸入輸出資料流來完成了。
首先說一下流的概念,流代表任何有能力產出資料的資料來源對象或者是有能力接收資料的接收端對象。劃重點,流代表的是對象。這個對象有發送或接收資料的能力。所以說流的本質也就是將資料來源(資料來源端,資料接受端)和資料的傳輸方式(字元,位元組,二進位等)抽象成類的結果。作用就是為了傳輸資料。
在 I/O 體系中,因為需要的流有太多,Java 設計者又避免設計過多的類,所以最終採用裝飾者模式來對整個流結構進行設計,按功能劃分 Stream,還可以動態裝配這些 Stream,以便獲得需要的流。假如你想要獲得一個具有緩衝的檔案輸入位元組流,這樣即可。
import java.io.BufferedInputStream;import java.io.FileInputStream;import java.io.InputStream;public class IOTest3 { public static void main(String[] args) throws Exception { InputStream fis = new FileInputStream("test.txt"); BufferedInputStream bis = new BufferedInputStream(fis); }}
流分類:
位元組流。InputStream 是所有位元組輸入資料流的基類,而 OutputStream 是所有位元組輸出資料流的基類。
字元流。Reader 是所有讀取字串輸入資料流的基類,而 Writer 是所有輸出字串的基類。
另外 InputStream,OutputStream,Reader,Writer 都是抽象類別。
位元組流是最基本的,所有的 InputStream 和 OutputStream 的子類都是位元組流,主要用來處理位元據,它是按位元組來處理的,但實際中很多的資料是文本,所以又提出了字元流的概念,它是按虛擬機器的 Encode 來處理,也就是要按照字元集將位元組轉化為字元。Java 中預設的編碼是 Unicode 編碼。
位元組流和字元流通過 InputStreamReader,OutputStreamWriter 來關聯,實際上是通過 byte[ ] 和 String 來關聯。在實際開發中出現的漢字問題實際上都是在字元流和位元組流之間轉化不統一而造成的。在從位元組流轉化為字元流時,實際上就是 byte[ ] 轉化為 String
byte[] bytes = new byte[10];String charsetName = "UTF-8"String s1 = new String(bytes, charsetName);
有一個關鍵的參數字元集編碼,通常我們都省略了,而在字元流轉化為位元組流時,實際上是 String 轉化為 byte[ ]
String s = "你好";byte[] bytes = s.getBytes();
至於其他的流,主要是為了提高效能和使用方便,如:
// 位元組流相關FileInputStreamFileOutputStreamBufferedInputStreamBufferedOutputStream// 字元流相關FileReaderFilterWriterBufferedReaderBufferedWriter
位元組流和字元流的區別,除了類名稱的區別,還有就是字元流會使用緩衝區,而位元組流沒有使用緩衝區。
緩衝區可以簡單地理解為一段特殊的記憶體。某些情況下,如果一個程式頻繁地操作一個資源(如檔案或資料庫),則效能會很低,此時為了提升效能,就可以將一部分資料暫時讀入到記憶體的一塊地區之中,以後直接從此地區中讀取資料即可,因為讀取記憶體速度會比較快,這樣可以提升程式的效能。
在字元流的操作中,所有的字元都是在記憶體中形成的,在輸出前會將所有的內容暫時儲存在記憶體之中,所以使用緩衝區暫存資料。如果想在不關閉時也可以將字元流的內容全部輸出,則可以使用 Writer 類中的 flush() 方法。
位元組流和字元流的選擇
Reader 類的 read() 方法傳回型別為 int,面向的是字元(佔兩個位元組共 16 位),範圍在 0 到 65535 之間 ( 0x00 - 0xffff ),如果已到達流的末尾,則返回 -1。
InputStream 的 read() 方法雖然也返回 int,但由於此類是面向位元組流的,一個位元組占 8 位,所以返回 0 到 255 範圍內的 int 位元組值。如果因為已經到達流末尾而沒有可用的位元組,則傳回值 -1。因此對於不能用 0 - 255 來表示的值就得用字元流來讀取,比如說漢字。
字元( Reader 和 Writer ):中文,字元是只有在記憶體中才會形成的,操作字元,字元數組或字串。
位元組( InputStream 和 OutputStream ):音頻檔案,圖片,歌曲,所有的硬碟上儲存檔案或進行傳輸的時候,操作位元組和位元組數組或二進位對象。
最後來看一個例子,實現拷貝功能。代碼只是為了示範操作,合理的寫法不該這樣。
public static void main(String[] args) throws Exception { File inFile = new File("D:\\input.txt"); File outFile = new File("D:\\output.txt"); FileInputStream inputStream = new FileInputStream(inFile); FileOutputStream outputStream = new FileOutputStream(outFile); byte[] content = new byte[1024]; int len; while ((len = inputStream.read(content)) != -1) { outputStream.write(content, 0, len); } outputStream.flush(); outputStream.close(); inputStream.close();}