Java:IO:深入理解

花和尚也有春天發表於2018-09-17

引言:

對程式語言的設計者來說,建立一個好的輸入/輸出(I/O)系統是一項艱難的任務 < Thinking in Java >

本文的目錄檢視如下:

Java IO概要

   a.Java IO中常用的類

     b.Java流類的類結構圖

1.流的概念和作用

2.Java IO所採用的模型  :

3.IO流的分類

4.Java IO流物件

     1.輸入位元組流InputStream

     2.輸出位元組流OutputStream

     3.字元輸入流Reader

     4.字元輸出流Writer

5.字元流的輸入與輸出的對應

6.字元流與位元組流轉換

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

8.File類

9.RandomAccessFile類

 

Java IO概要

為了方便理解與闡述,先引入兩張圖:

a、Java IO中常用的類

 在整個Java.io包中最重要的就是5個類和一個介面。5個類指的是File、OutputStream、InputStream、Writer、Reader;一個介面指的是Serializable.掌握了這些IO的核心操作那麼對於Java中的IO體系也就有了一個初步的認識了

 

Java I/O主要包括如下幾個層次,包含三個部分:

  1.流式部分――IO的主體部分;

  2.非流式部分――主要包含一些輔助流式部分的類,如:File類、RandomAccessFile類和FileDescriptor等類;

  3.其他類--檔案讀取部分的與安全相關的類,如:SerializablePermission類,以及與本地作業系統相關的檔案系統的類,如:FileSystem類和Win32FileSystem類和WinNTFileSystem類。

 

主要的類如下:

1. File(檔案特徵與管理):用於檔案或者目錄的描述資訊,例如生成新目錄,修改檔名,刪除檔案,判斷檔案所在路徑等。

2. InputStream(二進位制格式操作):抽象類,基於位元組的輸入操作,是所有輸入流的父類。定義了所有輸入流都具有的共同特徵。

3. OutputStream(二進位制格式操作):抽象類。基於位元組的輸出操作。是所有輸出流的父類。定義了所有輸出流都具有的共同特徵。

4.Reader(檔案格式操作):抽象類,基於字元的輸入操作。

5. Writer(檔案格式操作):抽象類,基於字元的輸出操作。

6. RandomAccessFile(隨機檔案操作):一個獨立的類,直接繼承至Object.它的功能豐富,可以從檔案的任意位置進行存取(輸入輸出)操作。

 

Java中IO流的體系結構如圖:

 

b、Java流類的類結構圖:

一、流的概念和作用

流:代表任何有能力產出資料的資料來源物件或者是有能力接受資料的接收端物件<Thinking in Java>

流的本質:資料傳輸,根據資料傳輸特性將流抽象為各種類,方便更直觀的進行資料操作。 

流的作用:為資料來源和目的地建立一個輸送通道。

 

     Java中將輸入輸出抽象稱為流,就好像水管,將兩個容器連線起來。流是一組有順序的,有起點和終點的位元組集合,是對資料傳輸的總稱或抽象。即資料在兩裝置間的傳輸稱為流.

 

二、Java IO所採用的模型  


     Java的IO模型設計非常優秀,它使用Decorator(裝飾者)模式,按功能劃分Stream,您可以動態裝配這些Stream,以便獲得您需要的功能。

       例如,您需要一個具有緩衝的檔案輸入流,則應當組合使用FileInputStream和BufferedInputStream。 

 

三、IO流的分類

·        根據處理資料型別的不同分為:字元流和位元組流

·        根據資料流向不同分為:輸入流和輸出流

·        按資料來源(去向)分類:

         1、File(檔案): FileInputStream, FileOutputStream, FileReader, FileWriter 
         2、byte[]:ByteArrayInputStream, ByteArrayOutputStream 
         3、Char[]: CharArrayReader,CharArrayWriter 
         4、String:StringBufferInputStream, StringReader, StringWriter 
         5、網路資料流:InputStream,OutputStream, Reader, Writer 

 

字元流和位元組流

流序列中的資料既可以是未經加工的原始二進位制資料,也可以是經一定編碼處理後符合某種格式規定的特定資料。因此Java中的流分為兩種:


 1)  位元組流:資料流中最小的資料單元是位元組
 2)  字元流:資料流中最小的資料單元是字元, Java中的字元是Unicode編碼,一個字元佔用兩個位元組。

 

字元流的由來: Java中字元是採用Unicode標準,一個字元是16位,即一個字元使用兩個位元組來表示。為此,JAVA中引入了處理字元的流。因為資料編碼的不同,而有了對字元進行高效操作的流物件。本質其實就是基於位元組流讀取時,去查了指定的碼錶。

 

輸入流和輸出流

根據資料的輸入、輸出方向的不同對而將流分為輸入流和輸出流。

1) 輸入流

     程式從輸入流讀取資料來源。資料來源包括外界(鍵盤、檔案、網路…),即是將資料來源讀入到程式的通訊通道

 

    

2) 輸出流

   程式向輸出流寫入資料。將程式中的資料輸出到外界(顯示器、印表機、檔案、網路…)的通訊通道。

    

        

採用資料流的目的就是使得輸出輸入獨立於裝置。

輸入流( Input  Stream )不關心資料來源來自何種裝置(鍵盤,檔案,網路)。
輸出流( Output Stream )不關心資料的目的是何種裝置(鍵盤,檔案,網路)。

3)特性

  相對於程式來說,輸出流是往儲存介質或資料通道寫入資料,而輸入流是從儲存介質或資料通道中讀取資料,一般來說關於流的特性有下面幾點:

1、先進先出,最先寫入輸出流的資料最先被輸入流讀取到。

2、順序存取,可以一個接一個地往流中寫入一串位元組,讀出時也將按寫入順序讀取一串位元組,不能隨機訪問中間的資料。(RandomAccessFile可以從檔案的任意位置進行存取(輸入輸出)操作

3、只讀或只寫,每個流只能是輸入流或輸出流的一種,不能同時具備兩個功能,輸入流只能進行讀操作,對輸出流只能進行寫操作。在一個資料傳輸通道中,如果既要寫入資料,又要讀取資料,則要分別提供兩個流。 

 

四、Java IO流物件

 

1.輸入位元組流InputStream

 

IO 中輸入位元組流的繼承圖可見上圖,可以看出:

1.    InputStream是所有的輸入位元組流的父類,它是一個抽象類。

2.    ByteArrayInputStream、StringBufferInputStream(上圖的StreamBufferInputStream)、FileInputStream是三種基本的介質流,它們分別從Byte陣列、StringBuffer、和本地檔案中讀取資料。

3.    PipedInputStream是從與其它執行緒共用的管道中讀取資料.

4.    ObjectInputStream和所有FilterInputStream的子類都是裝飾流(裝飾器模式的主角)。

 

 

 

InputStream中的三個基本的讀方法
      abstract int read() :讀取一個位元組資料,並返回讀到的資料,如果返回-1,表示讀到了輸入流的末尾。
      intread(byte[]?b) :將資料讀入一個位元組陣列,同時返回實際讀取的位元組數。如果返回-1,表示讀到了輸入流的末尾。
      intread(byte[]?b, int?off, int?len) :將資料讀入一個位元組陣列,同時返回實際讀取的位元組數。如果返回-1,表示讀到了輸入流的末尾。off指定在陣列b中存放資料的起始偏移位置;len指定讀取的最大位元組數。

流結束的判斷:方法read()的返回值為-1時;readLine()的返回值為null時。
 

其它方法
      long skip(long?n):在輸入流中跳過n個位元組,並返回實際跳過的位元組數。
      int available() :返回在不發生阻塞的情況下,可讀取的位元組數。
      void close() :關閉輸入流,釋放和這個流相關的系統資源。
      voidmark(int?readlimit) :在輸入流的當前位置放置一個標記,如果讀取的位元組數多於readlimit設定的值,則流忽略這個標記。
      void reset() :返回到上一個標記。
      booleanmarkSupported() :測試當前流是否支援mark和reset方法。如果支援,返回true,否則返回false。

2.輸出位元組流OutputStream

 

 

IO 中輸出位元組流的繼承圖可見上圖,可以看出:

1.    OutputStream是所有的輸出位元組流的父類,它是一個抽象類。

2.    ByteArrayOutputStream、FileOutputStream是兩種基本的介質流,它們分別向Byte陣列、和本地檔案中寫入資料。PipedOutputStream是向與其它執行緒共用的管道中寫入資料。

3.    ObjectOutputStream和所有FilterOutputStream的子類都是裝飾流。

 

 

outputStream中的三個基本的寫方法
   abstract void write(int?b):往輸出流中寫入一個位元組。
     void write(byte[]?b) :往輸出流中寫入陣列b中的所有位元組。
     void write(byte[]?b, int?off, int?len) :往輸出流中寫入陣列b中從偏移量off開始的len個位元組的資料。


其它方法
   void flush() :重新整理輸出流,強制緩衝區中的輸出位元組被寫出。
     void close() :關閉輸出流,釋放和這個流相關的系統資源。
 

3.字元輸入流Reader

 

在上面的繼承關係圖中可以看出:

1.    Reader是所有的輸入字元流的父類,它是一個抽象類。

2.    CharReader、StringReader是兩種基本的介質流,它們分別將Char陣列、String中讀取資料。PipedReader是從與其它執行緒共用的管道中讀取資料。

3.    BufferedReader很明顯就是一個裝飾器,它和其子類負責裝飾其它Reader物件。

4.    FilterReader是所有自定義具體裝飾流的父類,其子類PushbackReader對Reader物件進行裝飾,會增加一個行號。

5.    InputStreamReader是一個連線位元組流和字元流的橋樑,它將位元組流轉變為字元流。FileReader可以說是一個達到此功能、常用的工具類,在其原始碼中明顯使用了將FileInputStream轉變為Reader的方法。我們可以從這個類中得到一定的技巧。Reader中各個類的用途和使用方法基本和InputStream中的類使用一致。後面會有Reader與InputStream的對應關係。

 

主要方法:

     (1) public int read() throws IOException; //讀取一個字元,返回值為讀取的字元 

     (2) public int read(char cbuf[]) throws IOException; /*讀取一系列字元到陣列cbuf[]中,返回值為實際讀取的字元的數量*/ 
     (3) public abstract int read(char cbuf[],int off,int len) throws IOException; 
/*讀取len個字元,從陣列cbuf[]的下標off處開始存放,返回值為實際讀取的字元數量,該方法必須由子類實現*/ 

 

4.字元輸出流Writer

 

在上面的關係圖中可以看出:

1.    Writer是所有的輸出字元流的父類,它是一個抽象類。

2.    CharArrayWriter、StringWriter是兩種基本的介質流,它們分別向Char陣列、String中寫入資料。PipedWriter是向與其它執行緒共用的管道中寫入資料,

3.    BufferedWriter是一個裝飾器為Writer提供緩衝功能。

4.    PrintWriter和PrintStream極其類似,功能和使用也非常相似。

5.    OutputStreamWriter是OutputStream到Writer轉換的橋樑,它的子類FileWriter其實就是一個實現此功能的具體類(具體可以研究一SourceCode)。功能和使用和OutputStream極其類似.

 

 

 

主要方法:

(1)  public void write(int c) throws IOException; //將整型值c的低16位寫入輸出流 
(2)  public void write(char cbuf[]) throws IOException; //將字元陣列cbuf[]寫入輸出流 
(3)  public abstract void write(char cbuf[],int off,int len) throws IOException; //將字元陣列cbuf[]中的從索引為off的位置處開始的len個字元寫入輸出流 
(4)  public void write(String str) throws IOException; //將字串str中的字元寫入輸出流 
(5)  public void write(String str,int off,int len) throws IOException; //將字串str 中從索引off開始處的len個字元寫入輸出流 

 

5.位元組流的輸入與輸出的對應

 

圖中藍色的為主要的對應部分,紅色的部分就是不對應部分。從上面的圖中可以看出JavaIO中的位元組流是極其對稱的。“存在及合理”我們看看這些位元組流中不太對稱的幾個類吧!

 

1.    LineNumberInputStream主要完成從流中讀取資料時,會得到相應的行號,至於什麼時候分行、在哪裡分行是由改類主動確定的,並不是在原始中有這樣一個行號。在輸出部分沒有對應的部分,我們完全可以自己建立一個LineNumberOutputStream,在最初寫入時會有一個基準的行號,以後每次遇到換行時會在下一行新增一個行號,看起來也是可以的。好像更不入流了。

2.    PushbackInputStream的功能是檢視最後一個位元組,不滿意就放入緩衝區。主要用在編譯器的語法、詞法分析部分。輸出部分的BufferedOutputStream幾乎實現相近的功能。

3.    StringBufferInputStream已經被Deprecated,本身就不應該出現在InputStream部分,主要因為String應該屬於字元流的範圍。已經被廢棄了,當然輸出部分也沒有必要需要它了!還允許它存在只是為了保持版本的向下相容而已。

4.    SequenceInputStream可以認為是一個工具類,將兩個或者多個輸入流當成一個輸入流依次讀取。完全可以從IO包中去除,還完全不影響IO包的結構,卻讓其更“純潔”――純潔的Decorator模式。

5.    PrintStream也可以認為是一個輔助工具。主要可以向其他輸出流,或者FileInputStream寫入資料,本身內部實現還是帶緩衝的。本質上是對其它流的綜合運用的一個工具而已。一樣可以踢出IO包!System.out和System.out就是PrintStream的例項!

 

字元流的輸入與輸出的對應

 

6.字元流與位元組流轉換

轉換流的特點:

1.    其是字元流和位元組流之間的橋樑

2.    可對讀取到的位元組資料經過指定編碼轉換成字元

3.    可對讀取到的字元資料經過指定編碼轉換成位元組

 

何時使用轉換流?

1.    當位元組和字元之間有轉換動作時;

2.    流操作的資料需要編碼或解碼時。

 

具體的物件體現:

轉換流:在IO中還存在一類是轉換流,將位元組流轉換為字元流,同時可以將字元流轉化為位元組流。

 

1.    InputStreamReader:位元組到字元的橋樑

2.     OutputStreamWriter:字元到位元組的橋樑

 

OutputStreamWriter(OutStreamout):將位元組流以字元流輸出。

InputStreamReader(InputStream in):將位元組流以字元流輸入。

 

這兩個流物件是字元體系中的成員,它們有轉換作用,本身又是字元流,所以在構造的時候需要傳入位元組流物件進來。

7.位元組流和字元流的區別(重點)

位元組流和字元流的區別:(詳細可以參見http://blog.csdn.net/qq_25184739/article/details/51203733)    

         節流沒有緩衝區,是直接輸出的,而字元流是輸出到緩衝區的。因此在輸出時,位元組流不呼叫colse()方法時,資訊已經輸出了,而字元流只有在呼叫close()方法關閉緩衝區時,資訊才輸出。要想字元流在未關閉時輸出資訊,則需要手動呼叫flush()方法。

·        讀寫單位不同:位元組流以位元組(8bit)為單位,字元流以字元為單位,根據碼錶對映字元,一次可能讀多個位元組。

·        處理物件不同:位元組流能處理所有型別的資料(如圖片、avi等),而字元流只能處理字元型別的資料。

 

結論:只要是處理純文字資料,就優先考慮使用字元流。除此之外都使用位元組流。

 

8.非流式檔案類--File類

  從定義看,File類是Object的直接子類,同時它繼承了Comparable介面可以進行陣列的排序。

File類的操作包括檔案的建立、刪除、重新命名、得到路徑、建立時間等,以下是檔案操作常用的函式。

 

 

File類是對檔案系統中檔案以及資料夾進行封裝的物件,可以通過物件的思想來操作檔案和資料夾。File類儲存檔案或目錄的各種後設資料資訊,包括檔名、檔案長度、最後修改時間、是否可讀、獲取當前檔案的路徑名,判斷指定檔案是否存在、獲得當前目錄中的檔案列表,建立、刪除檔案和目錄等方法。 

File類共提供了三個不同的建構函式,以不同的引數形式靈活地接收檔案和目錄名資訊。

建構函式:
1)File (String   pathname)   
     例:File  f1=new File("FileTest1.txt"); //建立檔案物件f1,f1所指的檔案是在當前目錄下建立的FileTest1.txt
2)File (String  parent  ,  String child)
     例:File f2=new  File(“D:\\dir1","FileTest2.txt") ;//  注意:D:\\dir1目錄事先必須存在,否則異常
3)File (File    parent  , String child)
     例:File  f4=new File("\\dir3");
          File  f5=new File(f4,"FileTest5.txt");  //在如果 \\dir3目錄不存在使用f4.mkdir()先建立

        一個對應於某磁碟檔案或目錄的File物件一經建立, 就可以通過呼叫它的方法來獲得檔案或目錄的屬性。    
       1)public boolean exists( ) 判斷檔案或目錄是否存在
       2)public boolean isFile( ) 判斷是檔案還是目錄 
       3)public boolean isDirectory( ) 判斷是檔案還是目錄
       4)public String getName( ) 返回檔名或目錄名
       5)public String getPath( ) 返回檔案或目錄的路徑。
       6)public long length( ) 獲取檔案的長度 
       7)public String[ ] list ( ) 將目錄中所有檔名儲存在字串陣列中返回。 
       File類中還定義了一些對檔案或目錄進行管理、操作的方法,常用的方法有:
       1) public boolean renameTo( File newFile );    重新命名檔案
       2) public void delete( );   刪除檔案
       3)  public boolean mkdir( ); 建立目錄

例子:

1.  public class FileDemo1 {   
2.      public static void main(String[] args) {  
3.          File file = new File("D:" + File.separator + "test.txt");   
4.          if (file.exists()) {   
5.              file.delete();  
6.          } else {   
7.              try {   
8.                  file.createNewFile();  
9.              } catch (IOException e) {  
10.                 // TODO Auto-generated catch block   
11.                 e.printStackTrace();  
12.             }  
13.         }  
14.     }  
15. }  

 

9.RandomAccessFile類

 

該物件並不是流體系中的一員,其封裝了位元組流,同時還封裝了一個緩衝區(字元陣列),通過內部的指標來操作字元陣列中的資料。該物件特點:

 

 

1.    該物件只能操作檔案,所以建構函式接收兩種型別的引數:a.字串檔案路徑;b.File物件。

2.    該物件既可以對檔案進行讀操作,也能進行寫操作,在進行物件例項化時可指定操作模式(r,rw)

 

注意:該物件在例項化時,如果要操作的檔案不存在,會自動建立;如果檔案存在,寫資料未指定位置,會從頭開始寫,即覆蓋原有的內容。 可以用於多執行緒下載或多個執行緒同時寫資料到檔案。

 

10、System類對IO的支援

 針對一些頻繁的裝置互動,Java語言系統預定了3個可以直接使用的流物件,分別是:

·        System.in(標準輸入),通常代表鍵盤輸入。

·        System.out(標準輸出):通常寫往顯示器。

·        System.err(標準錯誤輸出):通常寫往顯示器。

 標準I/O
      Java程式可通過命令列引數與外界進行簡短的資訊交換,同時,也規定了與標準輸入、輸出裝置,如鍵盤、顯示器進行資訊交換的方式。而通過檔案可以與外界進行任意資料形式的資訊交換。


1. 命令列引數
 

public class TestArgs {  
    public static void main(String[] args) {  
        for (int i = 0; i < args.length; i++) {  
            System.out.println("args[" + i + "] is <" + args[i] + ">");  
        }  
    }  
}  



執行命令:java Java C VB


執行結果:
 

[java] view plain copy

args[0] is <Java>
 
 
args[1] is <C>
 
 
args[2] is <VB>

2. 標準輸入,輸出資料流


java系統自帶的標準資料流:java.lang.System:

java.lang.System   
public final class System  extends Object{   
   static  PrintStream  err;//標準錯誤流(輸出)  
   static  InputStream  in;//標準輸入(鍵盤輸入流)  
   static  PrintStream  out;//標準輸出流(顯示器輸出流)  
}  


注意:
(1)System類不能建立物件,只能直接使用它的三個靜態成員。
(2)每當main方法被執行時,就自動生成上述三個物件。

1) 標準輸出流 System.out

   System.out向標準輸出裝置輸出資料,其資料型別為PrintStream。方法:

      Void print(引數)
      Void println(引數)
2)標準輸入流 System.in

    System.in讀取標準輸入裝置資料(從標準輸入獲取資料,一般是鍵盤),其數 據型別為InputStream。方法:

        int read()  //返回ASCII碼。若,返回值=-1,說明沒有讀取到任何位元組讀取工作結束。
        int read(byte[] b)//讀入多個位元組到緩衝區b中返回值是讀入的位元組數
例如:
 

import java.io.*;  
public class StandardInputOutput {  
    public static void main(String args[]) {  
        int b;  
        try {  
            System.out.println("please Input:");  
            while ((b = System.in.read()) != -1) {  
                System.out.print((char) b);  
            }  
        } catch (IOException e) {  
            System.out.println(e.toString());  
        }  
    }  
}  

 

等待鍵盤輸入,鍵盤輸入什麼,就列印出什麼:

3)標準錯誤流

   System.err輸出標準錯誤,其資料型別為PrintStream。可查閱API獲得詳細說明。

    標準輸出通過System.out呼叫println方法輸出引數並換行,而print方法輸出引數但不換行。println或print方法都通 過過載實現了輸出基本資料型別的多個方法,包括輸出引數型別為boolean、char、int、long、float和double。同時,也過載實現 了輸出引數型別為char[]、String和Object的方法。其中,print(Object)和println(Object)方法在執行時將調 用引數Object的toString方法。

import java.io.BufferedReader;  
import java.io.IOException;  
import java.io.InputStreamReader;  
  
public class StandardInputOutput {  
    public static void main(String args[]) {  
        String s;  
        // 建立緩衝區閱讀器從鍵盤逐行讀入資料  
        InputStreamReader ir = new InputStreamReader(System.in);  
        BufferedReader in = new BufferedReader(ir);  
        System.out.println("Unix系統: ctrl-d 或 ctrl-c 退出"  
                + "\nWindows系統: ctrl-z 退出");  
        try {  
            // 讀一行資料,並標準輸出至顯示器  
            s = in.readLine();  
            // readLine()方法執行時若發生I/O錯誤,將丟擲IOException異常  
            while (s != null) {  
                System.out.println("Read: " + s);  
                s = in.readLine();  
            }  
            // 關閉緩衝閱讀器  
            in.close();  
        } catch (IOException e) { // Catch any IO exceptions.  
            e.printStackTrace();  
        }  
    }  
}  

 

在Java語言中使用位元組流和字元流的步驟基本相同,以輸入流為例,首先建立一個與資料來源相關的流物件,然後利用流物件的方法從流輸入資料,最後執行close()方法關閉流。


附加:

 IOException異常類的子類
1.public class  EOFException :   非正常到達檔案尾或輸入流尾時,丟擲這種型別的異常。    

2.public class FileNotFoundException:   當檔案找不到時,丟擲的異常。

3.public class InterruptedIOException: 當I/O操作被中斷時,丟擲這種型別的異常。

 

原文參考:https://blog.csdn.net/qq_25184739/article/details/51205186

相關文章