Java的IO流

chenhongyong發表於2019-05-24

 

在Java中,把不同的輸入/輸出源(鍵盤、檔案、網路連線等)中的有序資料抽象為流(stream)。

stream(流)是從起源(source)到接收(sink)的有序資料。

通過流的方式,Java可以使用相同的方式來訪問、操作不同型別的輸入/輸出源,不管輸入、輸出節點是磁碟檔案、網路連線,還是其他的IO裝置,只要將這些節點包裝成流,我們就可以使用相同的方式來進行輸入、輸出操作。

原本進行檔案讀寫要用檔案讀寫的一套方法,進行網路讀寫要用網路讀寫的一套方法.....不同型別的IO節點,用的方法體系不同,要單獨寫程式碼。

java統一了不同型別節點的IO操作,都把輸入、輸出節點包裝成流,使用一套方法就可以進行檔案讀寫、網路讀寫等不同型別節點的讀寫,不必單獨寫程式碼,從而簡化了IO操作。

 

 

 

 

流的分類:

1、按流的方向分:

  • 輸入流:從磁碟、網路等讀取到記憶體,只能讀,不能寫。
  • 輸出流:從記憶體寫出到磁碟、網路,只能寫,不能讀。

 

2、按操作的資料單元分:

  • 位元組流:以位元組為基本操作單位
  • 字元流:以字元為基本操作單位

位元組流、字元流的用法基本一樣,只是操作的資料單元不同。

 

3、按照流的角色來分:

  • 節點流:向某個IO裝置/節點(磁碟檔案、網路等)直接讀寫資料,也稱為低階流。
  • 處理流:用於包裝一個已存在的流,通過這個已存在的流來進行讀寫操作,並不直接操作IO節點,也稱為高階流、包裝流。

處理流是一種典型的裝飾器設計模式,通過使用處理流來包裝不同的節點流,可以消除不同節點流的實現差異,訪問不同的資料來源。

 

 

 

 

 

InputStream是位元組輸入流的頂級父類,常用方法:

  • int  read()     //讀取一個位元組,返回該位元組資料的Unicode碼值
  • int  read(byte[]  buff)    //最多讀取buff.length個位元組,將讀取的資料放在buff陣列中,返回實際讀取的位元組數
  • int  read(byte[]  buff, int off, int length)    //最多讀取length個位元組,放在buff陣列中,從陣列的off位置開始放置資料,返回實際讀取的位元組數。off一般設定為0,length一般設定為buff的長度(這樣其實和第二個函式的功能完全一樣)。

 

Reader時字元輸入流的頂級父類,常用方法:

  • int  read()     //讀取一個字元,返回該字元的Unicode碼值,注意並不是返回該字元。
  • int  read(char[]  buff)   //最多讀取buff.length個字元,放在buff陣列中,返回實際讀取的字元數
  • int  read(char[]  buff, int  off, int  length) //最多讀取length個位元組,放在buff陣列中,從陣列的off位置開始放置資料,返回實際讀取的字元數。off一般設定為0,length一般設定為buff的長度(這樣其實和第二個函式的功能完全一樣)。

 

位元組流和字元流的用法差不多,只是操作的資料單元不同。

如果已讀完(沒有資料可讀),以上方法均返回 -1 。

InputStream、Reader都是抽象類,不能直接建立示例,要用已實現的子類建立例項,比如FileInputStream類、FileReader類。

 

示例:

 1 FileInputStream fis=new FileInputStream("./1.txt");
 2         /*
 3         因為是以位元組為單位操作的,如果要將byte[]轉換為String,中文、byte[]長度設定不對,就可能讀取到中文字元的半個位元組,最終得到的String會亂碼。
 4         有2種解決中文亂碼的方式:
 5         1、將陣列長度設定為較大的值(大於等於輸入流的內容長度)
 6         2、如果輸入節點以GBK、GB2312編碼(2位元組碼),將陣列長度設定為2的倍數;如果輸入節點以UTF-8編碼(3位元組碼),將陣列長度設定為3的倍數
 7         注意:在Windows的資源管理器中新建文字檔案,預設字符集是ASCII,儲存時注意修改編碼方式。
 8         */
 9        byte[] buff=new byte[1024];
10        int length=0;
11         while ((length=fis.read(buff))!=-1){
12             //使用String的建構函式將byte[]轉化為String。此處用new String(buff)也是一樣的
13             System.out.println(new String(buff,0,length));     
14         }
15         //使用完,要關閉流
16         fis.close();

 

1 FileReader fr=new FileReader("./1.txt");
2         //因為操作單位是一個字元,所以陣列大小設定為多少都行,不會亂碼。
3         char[] buff=new char[1024];
4         while (fr.read(buff)!=-1){
5             System.out.println(new String(buff));
6         }
7         fr.close();

 

 

InputStream、Reader均提供了移動記錄指標的方法:

  • long  skip(long  n)     //指標向前移動n個位元組/字元
  • void  mark(int maxReadLimit)    //在當前指標處作一個標記。引數指定此標記的有效範圍,mark()做標記後,再往後讀取maxReadLimte位元組,此標記就失效。

實際上,並不完全由maxReadLimte引數決定,也和BufferedInputStream類的緩衝區大小有關。只要緩衝區夠大,mark()後讀取的資料沒有超出緩衝區的大小,mark標記就不會失效。如果不夠大,mark後又讀取了大量的資料,導致緩衝區更新,原來標記的位置自然找不到了。

  • void  reset()   將指標重置到上一個mark()標記的位置
  • boolean  markSupported()     //檢查這個流是否支援mark()操作

以上方法只能用於輸入流,不能用於輸出流。

 

 

 

 

 

 

OutputStream是位元組輸出流的頂級父類,常用方法:

  • void  write(int  i)    \\輸出一個位元組,i是碼值,即read()得到的碼值,輸出i對應的位元組
  • void  write(byte[]  buff)   //輸出整個位元組陣列的內容
  • void  write(byte[]  buff, int  off, int  length)    //把位元組陣列從off位置開始,輸出長度為length位元組的內容

 

Writer是字元輸出流的頂級父類,常用方法:

  • void  write(int  i)    //輸出一個字元,i是碼值,輸出的是i對應的字元
  • void  write(char[]  buff)    //輸出整個char[]的內容
  • void  write(char[]  buff,  int  off,  int  length)      //把char[]從off位置開始,輸出長度為length字元的內容

可以用String代替char[],所以Writer還具有以下2個方法:

  • void  write(String str)
  • void  write(String str, int off, int length)

 

OutputStream、Writer是抽象類,不能例項化,要建立物件,需使用已實現的子類,比如FileOutputStream、FileWriter。

 

示例:

1  //預設是覆蓋
2         FileOutputStream fos=new FileOutputStream("./2.txt");
3         fos.write("你好!\n".getBytes());
4         fos.close();
5 
6         //可指定是否是追加模式,true——是追加模式,false——不是追加模式(即覆蓋)
7         fos=new FileOutputStream("./2.txt",true);
8         fos.write("我是chenhongyong。".getBytes());
9         fos.close();

 

1 //預設是覆蓋
2         FileWriter fw=new FileWriter("./2.txt");
3         fw.write("hello\n");
4         fw.close();
5 
6         //追加
7         fw=new FileWriter("./2.txt",true);
8         fw.write("world!");
9         fw.close();

 

 1 //如果輸出流的檔案物件不存在,會報錯
 2         FileInputStream fis=new FileInputStream("./1.txt");
 3         //如果輸出流的檔案不存在,會自動建立
 4         FileOutputStream fos=new FileOutputStream("./2.txt");
 5         byte[] buff=new byte[1024];
 6         //檔案複製
 7         while (fis.read(buff)!=-1)
 8             fos.write(buff);
 9         fis.close();
10         fos.close();

 

Windows的換行符是\r\n,Linux、Unix的換行符是\n。最好用\n,\n是所有OS通用的換行符。

 

 

 

 

計算機中的檔案可以分為二進位制檔案、文字檔案,能用記事本開啟、並可以看到字元內容的檔案就是文字檔案,反之稱為二進位制檔案。

實際上,從底層來看,計算機上的所有檔案都是基於二進位制儲存、讀寫的。計算機上的檔案都是二進位制檔案,文字檔案是一種特殊的二進位制檔案。

 

位元組流的功能更強大,因為位元組流可以操作所有的二進位制檔案(計算機上的所有檔案)。

但如果用位元組流處理文字檔案,要進行字元、位元組之間的轉換,要麻煩一些。

所以一般情況下,我們用字元流處理文字檔案,用位元組流處理二進位制檔案。

 

 

 

 

上面使用的FileInputStream、FileWriter、FileOutputStream、FileWriter都是直接操作IO節點(磁碟檔案),建構函式裡是IO節點,它們都是節點流(低階流)。

處理流用來包裝一個已存在的流,示例:

 1  FileOutputStream fos=new FileOutputStream("./1.txt");
 2         //以一個已存在的流物件作為引數
 3         PrintStream ps=new PrintStream(fos);
 4 
 5         //可以位元組為單位進行IO操作
 6         ps.write("你好!".getBytes());
 7         //可以字元為單位進行IO操作
 8         ps.print("很高興認識你!");
 9         //println()輸出後直接換行,不用我們在寫\n,更加簡便
10         ps.println("我是chenhongyong。");
11         //可輸出各種型別的資料
12         ps.write(12);
13         ps.print(12);
14         ps.print(12.34);
15 
16         //關閉處理流時,會自動關閉其包裝的節點流,程式碼更簡潔。
17         ps.close();
18 
19         //處理流提供了更多的輸入/輸出方法,更加簡單、強大

PrintStream類的輸出功能很強大,可以把輸出的低階流包裝成PrintStream使用。

 

處理流的優點:

  • 更簡單、更強大
  • 執行效率更高

 

 

 

 

處理流之緩衝流:

前面提到可以用byte[]、char[]做緩衝區,提高讀寫效率。Java提供了緩衝流,來實現相同的效果,使用方式基本相同。

 1  //以相應的節點流物件作為引數
 2         BufferedInputStream bis = new BufferedInputStream(new FileInputStream("./1.txt"));
 3         BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("./2.txt"));
 4         //檔案複製
 5         int i = 0;
 6         while ((i = bis.read()) != -1){   //length是讀取到的內容的碼值,不是讀取到的內容的長度。一次只讀一個位元組。
 7             System.out.println(length);
 8             bos.write(length);   //將讀取的資料寫到輸出流
 9     }
10         //關閉處理流即可,會自動關閉對應的節點流
11         bis.close();
12         bos.close();

 

 1  BufferedInputStream bis=new BufferedInputStream(new FileInputStream("./1.txt"));
 2        BufferedOutputStream bos=new BufferedOutputStream(new FileOutputStream("./2.txt"));
 3        byte[] buff=new byte[1024];
 4        int length=0;
 5        while ((length=bis.read(buff))!=-1){   //length是讀取到的內容的長度
 6            System.out.println(length);
 7             bos.write(buff);
 8        }
 9        bis.close();
10        bos.close();

 

緩衝區:

計算機訪問外部裝置或檔案,要比直接訪問記憶體慢的多。如果我們每次呼叫read()方法或者writer()方法訪問外部的裝置或檔案,CPU就要花上最多的時間是在等外部裝置響應,而不是資料處理。
為此,我們開闢一個記憶體緩衝區的記憶體區域,程式每次呼叫read()方法或writer()方法都是讀寫在這個緩衝區中。當這個緩衝區被裝滿後,系統才將這個緩衝區的內容一次集中寫到外部裝置或讀取進來給CPU。使用緩衝區可以有效的提高CPU的使用率,能提高讀寫效率。

緩衝流:

讀寫資料時,讓資料在緩緩衝區能減少系統實際對原始資料來源的存取次數,因為一次能做多個資料單位的操作,相較而言,對於從檔案讀取資料或將資料寫入檔案,比起緩衝區的讀寫要慢多了。所以使用緩衝區的 流,一般都會比沒有緩衝區的流效率更高,擁有緩衝區的流別稱為緩衝流。緩衝流本身沒有輸入、輸出功能,它只是在普通IO流上加一個緩衝區。

 

緩衝流是和4級頂級父類對應的:加字首Buffered

InputStream   BufferedInputStream   位元組輸入緩衝流,可作為所有位元組輸入流類的緩衝流

OutputStream   BufferedOutputStream    位元組輸出緩衝流

Reader     BufferedReader    字元輸入緩衝流

Writer  BufferedWriter    字元輸出緩衝流

 

 執行效率:

普通IO流,逐個位元組/字元操作,效率極低。緩衝流自帶緩衝區,就算是逐個位元組/字元操作,效率也是很高的。

普通IO流,用陣列作為緩衝區,效率極高。緩衝流使用陣列,效率極高。二者實現方式是相同的。

大致上:使用陣列的緩衝流 ≈ 使用陣列的普通流 >>  逐個位元組/字元操作的緩衝流 >>  逐個位元組/字元操作的普通IO流。

儘量不要逐個位元組/字元操作,太慢了。

 

由於使用了緩衝區,寫的資料不會立刻寫到物理節點,而是會寫到緩衝區,緩衝區滿了才會把緩衝區的資料一次性寫到物理節點。

可以使用流物件的flush()方法,強制刷出緩衝區的資料到物理節點。

當然,呼叫close()關閉流物件時,會先自動呼叫flush()刷出緩衝區的資料到物理節點。

 

 

 

 

 

處理流之轉換流:Java提供了2個轉化流,用於將位元組流轉換為字元流。

1、InputStreamReader類,顧名思義,是從InputStream到Reader。InputStreamReader是Reader的子類,操作的資料單元是字元。

InputStreamReader  isr=new  InputStreamReader(InputStream is);     //引數是InputStream類的物件

 

2、OutputStreamWriter類,顧名思義,是從OutputStream到Writer。OutputStreamWriter是Writer的子類,操作的資料單元是字元。

OutputStreamWriter  isr=new  OutputStreamWriter(OutputStream os);     //引數是OutputStream類的物件

 

因為字元流本就比較方便,所以沒有將字元流轉換為位元組流的類。

 

 

 

 

 

 

預定義的標準流物件:

  • System.in     標準輸入流物件(一般指鍵盤輸入),是InputStream的一個物件,可使用InputStream的一切方法。
  • System.out    標準輸出流物件(一般指顯示器),是OutputStream的一個物件
  • System.err    標準錯誤輸出流。
1  byte[] buff=new byte[100];
2         //將鍵盤輸入(從控制檯輸入)讀取到byte[]中
3         System.in.read(buff);
4         //轉換為String輸出
5         System.out.println(new String(buff));

 

 

 重定向標準流物件:

  • System.setIn(InputStream  is);     //將標準輸入流切換到指定的InputStream物件
  • System.setOut(PrintStream   ps);   
  • System.setErr(PrintStream  ps);
1 PrintStream ps=new PrintStream("./err.txt");
2         //重定向標準錯誤輸出流,錯誤資訊不再輸出到控制檯,而是輸出到err.txt檔案
3         System.setErr(ps);
4         int a=4/0;

 

1  PrintStream ps=new PrintStream("./out.txt");
2         //重定向標準輸出流,標準輸出不再輸出到螢幕(控制檯),而是輸出到out.txt檔案
3         System.setOut(ps);
4         //1會輸出到out.txt檔案
5         System.out.println(1);

 

 

 

 

 

 

Java常用的流:

 

分類  位元組輸入流  位元組輸出流 字元輸入流 字元輸出流
抽象基類   InputStream   OutputStream Reader Writer
操作檔案 FileInputStream    FileOutputStream   FileReader   FileWriter  
運算元組   ByteArrayInputStream   ByteArrayOutputStream   CharArrayReader   CharArrayWriter  
操作字串       StringReader StringWriter
緩衝流       BufferedInputStream BufferedOutputStream BufferedReader BufferedWriter
轉換流     InputStreamReader     OutputStreamWriter
物件流(用於序列化) ObjectInputStream ObjectOutputStream    
抽象基類(用於過濾)     FilterInputStream FilterOutputStream FilterReader FilterWriter
列印流(輸出功能極其大)   PrintStream(實際上也可用於輸出字元)   PrintWriter(不能輸出byte)  

 

部分流可以在建構函式中以引數String charset或Charset  charset的形式指定字符集。

 

此外,還有其他的IO流,比如:

  • AudioInputStream、AudioOutputStream     音訊輸入、輸出
  • CipherInputStream、CipherOutputStream      加密、解密
  • DeflaterInputStream、ZipInputStream、ZipOutputStream    檔案壓縮、解壓

後一篇隨筆再介紹。

 

相關文章