1. 程式人生 > >java輸入輸出流詳細講解(入門經典),詳細講解JAVA中的IO流

java輸入輸出流詳細講解(入門經典),詳細講解JAVA中的IO流

今天我們開始進入學習 java 中比較讓人頭疼的事, 那就是 I/O 流、多執行緒、網路程式設計。這裡對 I/O 流的一個詳細講解。希望對大家有點用吧。(不看後悔哦)

一、什麼是IO

Java中I/O操作主要是指使用Java進行輸入,輸出操作。 Java所有的I/O機制都是基於資料流進行輸入輸出,這些資料流表示了字元或者位元組資料的流動序列。

Java的I/O流提供了讀寫資料的標準方法。任何Java中表示資料來源的物件都會提供以資料流的方式讀寫它的資料的方法。

  Java.io是大多數面向資料流的輸入/輸出類的主要軟體包。

  此外,Java也對塊傳輸提供支援,在核心庫 java.nio中採用的便是塊IO。 
  JDK1.4版本開始引入了新I/O類庫,它位於java.nio包中,新I/O類庫利用通道和緩衝區等來提高I/O操作的效率。


  流IO簡單易用但效率較低。

  塊IO效率很高但程式設計比較複雜。 

二、Java IO模型 :

java I/O 的設計使用到了 Decorator(裝飾器)模式,按功能劃分Stream,您可以動態裝配這些 Stream,以便獲得您需要的功能。

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

三、資料流的基本概念

資料流是一串連續不斷的資料的集合,就象水管裡的水流,在水管的一端一點一點地供水,而在水管的另一端看到的是一股連續不斷的水流。

資料寫入程式可以是一段、一段地向資料流管道中寫入資料,這些資料段會按先後順序形成一個長的資料流。

對資料讀取程式來說,看不到資料流在寫入時的分段情況,每次可以讀取其中的任意長度的資料,但只能先讀取前面的資料後,再讀取後面的資料。

不管寫入時是將資料分多次寫入,還是作為一個整體一次寫入,讀取時的效果都是完全一樣的。

  “流是磁碟或其它外圍裝置中儲存的資料的源點或終點。”

在電腦上的資料有三種儲存方式,一種是外存,一種是記憶體,一種是快取。

比如電腦上的硬碟,磁碟,U盤等都是外存,在電腦上有記憶體條,快取是在CPU裡面的。

外存、記憶體、快取的比較

儲存量(依次遞減):    外存-->記憶體-->快取

讀取速度(依次遞減):  快取-->記憶體-->外存

對於記憶體和外存的理解,我們可以簡單的理解為容器,即外存是一個容器,記憶體又是另外一個容器。

在Java類庫中,IO部分的內容是很龐大的,因為它涉及的領域很廣泛:

     標準輸入輸出,檔案的操作,網路上的資料流,字串流,物件流,zip檔案流等等,java中將輸入輸出抽象稱為流,就好像水管,將兩個容器連線起來。將資料衝外存中讀取到記憶體中的稱為輸入流,將資料從記憶體寫入外存中的稱為輸出流。

流是一個很形象的概念,當程式需要讀取資料的時候,就會開啟一個通向資料來源的流,這個資料來源可以是檔案,記憶體,或是網路連線。類似的,當程式需要寫入資料的時候,就會開啟一個通向目的地的流。

基本流:

一組有序,有起點和終點的位元組的資料序列。包括輸入流和輸出流。

輸入流:

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

輸出流:

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

為什麼設計成資料流呢?

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

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

四、I/O體系結構

這裡寫圖片描述

簡單介紹下上圖:

你有沒有發現,都是成對出現的, 初學者就很容易混淆,分不清是位元組流還是字元流,大家只需要看最後這個單詞,如果是 Stream 的話就是位元組流,如果是 Reader/Writer 的話就是字元流。

在整個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類。

流式部分主要類:

Java中字元是採用Unicode標準,一個字元是16位,即一個字元使用兩個位元組來表示。為此,JAVA中引入了處理字元的流。

對檔案進行操作:

FileInputStream(位元組輸入流),FileOutputStream(位元組輸出流),FileReader(字元輸入流),FileWriter(字元輸出流)

對管道進行操作:

PipedInputStream(位元組輸入流),PipedOutStream(位元組輸出流),PipedReader(字元輸入流),PipedWriter(字元輸出流)

PipedInputStream的一個例項要和PipedOutputStream的一個例項共同使用,共同完成管道的讀取寫入操作。主要用於執行緒操作。

位元組/字元陣列:

ByteArrayInputStream,ByteArrayOutputStream,CharArrayReader,CharArrayWriter是在記憶體中開闢了一個位元組或字元陣列。

Buffered緩衝流:

BufferedInputStream,BufferedOutputStream,BufferedReader,BufferedWriter,是帶緩衝區的處理流,緩衝區的作用的主要目的是:避免每次和硬碟打交道,提高資料訪問的效率。

轉化流:

InputStreamReader/OutputStreamWriter,把位元組轉化成字元。

資料流:

DataInputStream,DataOutputStream。

因為平時若是我們輸出一個8個位元組的long型別或4個位元組的float型別,那怎麼辦呢?

可以一個位元組一個位元組輸出,也可以把轉換成字串輸出,但是這樣轉換費時間,若是直接輸出該多好啊,因此這個資料流就解決了我們輸出資料型別的困難。

資料流可以直接輸出float型別或long型別,提高了資料讀寫的效率。

列印流:

printStream,printWriter,一般是列印到控制檯,可以進行控制列印的地方。

物件流:

ObjectInputStream,ObjectOutputStream,把封裝的物件直接輸出,而不是一個個在轉換成字串再輸出。

序列化流:

SequenceInputStream。

物件序列化:把物件直接轉換成二進位制,寫入介質中。

注:

使用物件流需要實現Serializable介面,否則會報錯。

而若用transient關鍵字修飾成員變數,不寫入該成員變數,若是引用型別的成員變數為null,值型別的成員變數為0.

非流式部分主要類:

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

RandomAccessFile(隨機檔案操作):它的功能豐富,可以從檔案的任意位置進行存取(輸入輸出)操作。

File類:

在Java語言的java.io包中,由File類提供了描述檔案和目錄的操作與管理方法。 
但File類不是InputStream、OutputStream或Reader、Writer的子類,因為它不負責資料的輸入輸出,而專門用來管理磁碟檔案與目錄。

作用:File類主要用於命名檔案、查詢檔案屬性和處理檔案目錄。

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

1)File (String pathname)

 例:File  f1=new File("FileTest1.txt"); //建立檔案物件f1,f1所指的檔案是在當前目錄下建立的FileTest1.txt

2)File(URI uri)

3)File (String parent , String child)

 例:File f2=new  File(“D:\\dir1","FileTest2.txt") ;//  注意:D:\\dir1目錄事先必須存在,否則異常

4)File (File parent , String child)

 例:  File  f4=new File("E:\\dir3");
      File  f5=new File(f4,"FileTest5.txt");  //在如果 E:\\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 ( ) 將目錄中所有檔名和目錄名儲存在字串陣列中返回。 

   8)public File[] listFiles() 返回某個目錄下所有檔案和目錄的絕對路徑,返回的是File陣列

   9)public String getAbsolutePath() 返回檔案或目錄的絕對路徑

   ....
   File類中還定義了一些對檔案或目錄進行管理、操作的方法,常用的方法有:
   1) public boolean renameTo( File newFile );    重新命名檔案

   2) public void delete( );   刪除檔案

   3) public boolean mkdir( ); 建立目錄

   4)public boolean createNewFile(); 建立檔案

例子:輸出一個目錄中的所有檔名(目錄可能是多級目錄,如a目錄中有b、c目錄。。。)

FileUtils.java

public class FileUtils {

    public static void listDir(String dir) throws IOException {
        File file = new File(dir);
        //傳進來的可能不是一個目錄
        if (!file.isDirectory()) {
            throw new IOException(dir+"不是目錄");
        }
        //傳進來的可能是一個錯誤的路徑
        if (file == null) {
            throw new IOException("沒有此路徑");
        }
        File[] files = file.listFiles();
        for (File f : files) {
            //有可能是一個多級目錄,遞迴呼叫
            if (f.isDirectory()) {
                listDir(f.getAbsolutePath());
                //是檔案就直接輸出該檔案的絕對路徑
            }else {
                System.out.println(f.getAbsolutePath());
            }
        }
    }
}

Main.java

public class Main {

    public static void main(String[] args) throws IOException {
        FileUtils.listDir("E:\\ssh");
    }
}

結果:

這裡寫圖片描述

這裡就不多解釋了,應該比較簡單,程式碼中也有詳細註釋。

五、幾種流的介紹

1、InputStream抽象類

InputStream 為位元組輸入流,它本身為一個抽象類,必須依靠其子類實現各種功能,此抽象類是表示位元組輸入流的所有類的超類。 
繼承自InputStream 的流都是向程式中輸入資料的,且資料單位為位元組(8bit)

InputStream是輸入位元組資料用的類,所以InputStream類提供了3種過載的read方法.Inputstream類中的常用方法: 
   
  (1) public abstract int read( ):讀取一個byte的資料,返回值是高位補0的int型別值。若返回值=-1說明沒有讀取到任何位元組讀取工作結束。 
   
  (2) public int read(byte b[ ]):讀取b.length個位元組的資料放到b陣列中。返回值是讀取的位元組數。該方法實際上是呼叫下一個方法實現的 
   
  (3) public int read(byte b[ ], int off, int len):從輸入流中最多讀取len個位元組的資料,存放到偏移量為off的b陣列中。 
   
  (4) public int available( ):返回輸入流中可以讀取的位元組數。注意:若輸入阻塞,當前執行緒將被掛起,如果InputStream物件呼叫這個方法的話,它只會返回0,這個方法必須由繼承InputStream類的子類物件呼叫才有用, 
   
  (5) public long skip(long n):忽略輸入流中的n個位元組,返回值是實際忽略的位元組數, 跳過一些位元組來讀取 
   
  (6) public int close( ) :我們在使用完後,必須對我們開啟的流進行關閉.

主要的子類:

 1) FileInputStream:把一個檔案作為InputStream,實現對檔案的讀取操作     

 2) ByteArrayInputStream:把記憶體中的一個緩衝區作為InputStream使用 

 3) StringBufferInputStream:把一個String物件作為InputStream 

 4) PipedInputStream:實現了pipe的概念,主要線上程中使用 

 5) SequenceInputStream:把多個InputStream合併為一個InputStream 

2、OutputStream抽象類

OutputStream提供了3個write方法來做資料的輸出,這個是和InputStream是相對應的。 
   
  (1) public void write(byte b[ ]):將引數b中的位元組寫到輸出流。 
   
  (2)public void write(byte b[ ], int off, int len) :將引數b的從偏移量off開始的len個位元組寫到輸出流。 
   
  (3) public abstract void write(int b) :先將int轉換為byte型別,把低位元組寫入到輸出流中。 
   
  (4)public void flush( ) : 將資料緩衝區中資料全部輸出,並清空緩衝區。 
   
  (5) public void close( ) : 關閉輸出流並釋放與流相關的系統資源。

主要的子類:

 1) ByteArrayOutputStream:把資訊存入記憶體中的一個緩衝區中 

 2) FileOutputStream:把資訊存入檔案中 

 3) PipedOutputStream:實現了pipe的概念,主要線上程中使用 

 4) SequenceOutputStream:把多個OutStream合併為一個OutStream 

注:

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

3、FileInputStream檔案輸入流

FileInputStream可以使用read()方法一次讀入一個位元組,並以int型別返回,或者是使用read()方法時讀入至一個byte陣列,byte陣列的元素有多少個,就讀入多少個位元組。

在將整個檔案讀取完成或寫入完畢的過程中,這麼一個byte陣列通常被當作緩衝區,因為這麼一個byte陣列通常扮演承接資料的中間角色。

作用:以檔案作為資料輸入源的資料流。或者說是開啟檔案,從檔案讀資料到記憶體的類。

使用方法:

FileInputStream fis = new FileInputStream(“E:\a.txt”);

或 FileInputStream fis = new FileInputStream(“E:/a.txt”);

當然也可以傳一個 File ,它還有好多個構造器。大家可以看看原始碼應該就懂了。

例子: 
將a.txt的內容顯示在顯示器上

public class FileInputStreamDemo {

    public static void main(String[] args) throws IOException {

        FileInputStream fis = new FileInputStream("a.txt");
        int a;
        while ((a = fis.read()) != -1) {
            System.out.print((char)a);
        }
    }
}

結果:

這裡寫圖片描述

如果將 System.out.print((char)a) 改成 System.out.print(a) 又會是怎樣呢?

這裡寫圖片描述

怎麼回事呢?不是位元組流嗎?讀出來不就是一個位元組,怎麼輸出一個整數了呢?

答:read()方法確實是讀取一個位元組,但返回值是 int 型別的,會將取出來的一個位元組高位會補 0 成一個 int 型。所以輸出來的應該這個資料的 ASCII 碼。

使用鍵盤輸入一段中文並輸出到控制檯,看看會是怎樣?

public class FileInputStreamDemo {

    public static void main(String[] args) throws IOException {
        int a;
        while ((a = System.in.read()) != -1) {
            System.out.print(a);
        }
    }
}

可以猜到應該會輸出一些整數即 ASCII 碼,因為讀的時候是一個位元組一個位元組的讀,而中文又不止一個位元組,到底要幾個位元組來表示,這要取決於編碼集(我這裡的是拆成了三個位元組,兩個位元組是用來標記一行的)。反正會拆成幾個位元組來讀。

結果:

這裡寫圖片描述

那麼如果強轉成 char 型別又會怎樣呢?

public class FileInputStreamDemo {

    public static void main(String[] args) throws IOException {
        int a;
        while ((a = System.in.read()) != -1) {
            System.out.print((char)a);
        }
    }
}

結果:

這裡寫圖片描述

我們看到輸入哈,輸出?,但這並不是我們想要的。為什麼會這樣呢?我們來看一下 ? 的 ASCII 碼,剛好是 229,這我們就知道它只取了第一位元組ASCII碼對應的字元。

ASCII 碼錶可在文章最後檢視。

4、FileOutputStream檔案輸出流

FileOutputStream類用來處理以檔案作為資料輸出目的資料流;一個表示檔名的字串,也可以是File或FileDescriptor物件。

作用:用來處理以檔案作為資料輸出目的資料流;或者說是從記憶體區讀資料到檔案

建立檔案流的方式:

1)FileOutputStream(File file) 
建立一個向指定 File 物件表示的檔案中寫入資料的檔案輸出流。 
例: File f=new File (“d:/myjava/write.txt “); 
FileOutputStream out= new FileOutputStream (f);

2)FileOutputStream(File file, boolean append) 
建立一個向指定 File 物件表示的檔案中寫入資料的檔案輸出流。 append表示內容是否追加

3)FileOutputStream(FileDescriptor fdObj) 
建立一個向指定檔案描述符處寫入資料的輸出檔案流,該檔案描述符表示一 
個到檔案系統中的某個實際檔案的現有連線。

4)FileOutputStream(String name) 
建立一個向具有指定名稱的檔案中寫入資料的輸出檔案流。 
例:FileOutputStream out=new FileOutputStream(“d:/myjava/write.txt “);

5)FileOutputStream(String name, boolean append) 
建立一個向具有指定 name 的檔案中寫入資料的輸出檔案流。 append表示內容是否追加

注:

(1)檔案中寫資料時,若檔案已經存在,則覆蓋存在的檔案;

(2)的讀/寫操作結束時,應呼叫close方法關閉流。

例子:

使用鍵盤輸入一段內容,將內容儲存在檔案write.txt中

public class FileOutputStreamDemo {

    public static void main(String[] args) throws IOException {

        FileOutputStream fos = new FileOutputStream("write.txt");
        int a;
        while ((a = System.in.read()) != -1) {
            fos.write(a);
        }
    }
}

結果:

這裡寫圖片描述

這裡寫圖片描述

有同學會問了,剛才上面漢字的讀取不是一個位元組一個位元組讀,存到檔案中的應該也是一個位元組一個位元組的,那麼檢視的時候不應該是亂碼嗎?

答:從鍵盤讀取的時候確實是一個位元組一個位元組讀取,存的時候也是一個位元組一個位元組存,只不過它會加點標誌(不同的編碼集標記的方式可能不一樣)。(我這裡的編碼是每個漢字用三個位元組編碼,用兩個位元組來標記一行),到時候輸出的時候它就會根據標記和漢字所佔位元組數來拼接成漢字。

我們來輸出看一下:

public class FileInputStreamDemo {

    public static void main(String[] args) throws IOException {
        //哈:   229 147 136 13 10   
        //哈哈:  229 147 136 229 147 136 13 10  
        //哈哈哈: 229 147 136 229 147 136 229 147 136 13 10  
        //哈哈(兩行)哈哈:229 147 136 229 147 136 13 10 229 147 136 229 147 136 13 10 
        FileInputStream fis = new  FileInputStream("a.txt");
        int a;
        while ((a = fis.read()) != -1) {
            System.out.print(a+" ");
        }
    }
}

結果:

這裡寫圖片描述

這裡的結果是哈哈(兩行)哈哈的輸出結果,當然也分別測了四種情況,為了更好的對比:

當 a.txt 的內容為哈、哈哈、哈哈、哈哈(兩行)哈哈的時候,存的時候各是什麼情況,程式碼中都寫了,大家可以看看。可以發現標記一行的是13 10 這兩個位元組。不要問我這明明就是個整數不是佔4個位元組,為什麼說是一個位元組?read() 的返回值為 int,取出的 byte 高位補0成int。那麼怎麼拼接的呢?比如:內容為哈的時候,首先找到標記的位置,用(所佔位元組數-2)/3=漢字的個數,而每個漢字佔三個位元組,這不就可以三個三個位元組拼接出來了嗎。

5、緩衝輸入輸出流 BufferedInputStream/ BufferedOutputStream(也稱包裝流)

這裡寫圖片描述

計算機訪問外部裝置非常耗時。訪問外存的頻率越高,造成CPU閒置的概率就越大。

為了減少訪問外存的次數,應該在一次對外設的訪問中,讀寫更多的資料。

為此,除了程式和流節點間交換資料必需的讀寫機制外,還應該增加緩衝機制。

緩衝流就是每一個數據流分配一個緩衝區,一個緩衝區就是一個臨時儲存資料的記憶體。

這樣可以減少訪問硬碟的次數,提高傳輸效率。

BufferedInputStream:當向緩衝流寫入資料時候,資料先寫到緩衝區,待緩衝區寫滿後,系統一次性將資料傳送給輸出裝置。

BufferedOutputStream :當從向緩衝流讀取資料時候,系統先從緩衝區讀出資料,待緩衝區為空時,系統再從輸入裝置讀取資料到緩衝區。

a、將檔案讀入記憶體:

將BufferedInputStream與FileInputStream相接

FileInputStream in=new FileInputStream( “file1.txt “);

BufferedInputStream bin=new BufferedInputStream(in);

b、將記憶體寫入檔案:

將BufferedOutputStream與 FileOutputStream相接

FileOutputStreamout=new FileOutputStream(“file2.txt”);

BufferedOutputStream bin=new BufferedInputStream(out);

c、鍵盤輸入流讀到記憶體 
將BufferedReader與標準的資料流相接

InputStreamReader sin=new InputStreamReader (System.in) ;

BufferedReader bin=new BufferedReader(sin);

例子:從鍵盤輸入一串內容存到file1.txt檔案中

public class FileInputStreamDemo {

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        BufferedWriter bw = new BufferedWriter(new FileWriter(new File("file1.txt")));
        String s;
        while ((s = br.readLine()).length() > 0) {
            bw.write(s, 0, s.length());
        }
    }
}

結果:

這裡寫圖片描述

這裡寫圖片描述

大家可以看到,我們剛才在鍵盤上輸入的內容並沒有存入到這個檔案中,這不是操蛋嗎?這樣只是為了引出包裝流的一個特性即因為它是包裝流擁有快取區,每次的讀取的資料都存在快取區中,當快取區滿了的時候才會寫入到硬碟上。但是預設的快取區大小為8192個位元組,當然你也可以指定快取區的大小。顯然剛才輸入的字串並沒有裝滿。

 

 

這裡寫圖片描述

指定快取區的大小:包裝流的構造器

如 BufferedWriter(Writer out, int size) 
建立一個使用給定大小輸出緩衝區的新緩衝字元輸出流。size為快取區的大小

所以就需要我們手動的去重新整理緩衝區。呼叫包裝流的 flush() 方法。這個方法的作用就是將快取區的內容寫入到硬碟上並清空快取區。

當然能不能不寫 flush()方法也讓它重新整理呢?

答:可以,呼叫 close() 方法關閉流即可。當你呼叫 close()方法時它會先重新整理快取區。這就是剛才上面所說要記得用完之後要關閉流。不過最好有使用到包裝流的時候兩個方法都記得寫上。

程式說明:

從鍵盤讀入字元,並寫入到檔案中BufferedReader類的方法:String readLine()

作用:讀一行字串,以回車符為結束。

BufferedWriter類的方法:bout.write(String s,offset,len)

作用:從緩衝區將字串s從offset開始,len長度的字串寫到某處。

當然包裝流中還有許多方法(主要方法):

void write(char ch);//寫入單個字元。

void write(char []cbuf,int off,int len)//寫入字元資料的某一部分。
void write(String s,int off,int len)//寫入字串的某一部分。

void newLine()//寫入一個行分隔符。

void flush();//重新整理該流中的緩衝。將緩衝資料寫到目的檔案中去。

void close();//關閉此流,再關閉前會先重新整理他。

6、Writer/Reader字元流

a、Reader抽象類

用於讀取字元流的抽象類。子類必須實現的方法只有 read(char[], int, int) 和 close()。但是,多數子類將重寫此處定義的一些方法,以提供更高的效率和/或其他功能。

子類簡單介紹:

(1) 用指定字元陣列作為引數:CharArrayReader(char[]) 

    將字元陣列作為輸入流:CharArrayReader(char[], int, int) 
讀取字串,建構函式如下: public StringReader(String s); 

2) CharArrayReader:與ByteArrayInputStream對應 

3) StringReader : 與StringBufferInputStream對應 

4) InputStreamReader 
從輸入流讀取位元組,在將它們轉換成字元:Public inputstreamReader(inputstream is); 

5) FilterReader: 允許過濾字元流 
protected filterReader(Reader r); 

6) BufferReader :接受Reader物件作為引數,並對其新增字元緩衝器,使用readline()方法可以讀取一行。 
Public BufferReader(Reader r); 

主要方法:

  (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處開始存放,返回值為實際讀取的字元數量,該方法必須由子類實現*/ 

b、 Writer抽象類

寫入字元流的抽象類。子類必須實現的方法僅有 write(char[], int, int)、flush() 和 close()。但是,多數子類將重寫此處定義的一些方法,以提供更高的效率和/或其他功能。

子類簡單介紹:

1) FileWrite: 與FileOutputStream對應  

  將字元型別資料寫入檔案,使用預設字元編碼和緩衝器大小。 
  Public FileWrite(file f);

2)  chararrayWrite:與ByteArrayOutputStream對應 ,將字元緩衝器用作輸出。 

   Public CharArrayWrite(); 
   
3) PrintWrite:生成格式化輸出 
   public PrintWriter(outputstream os); 
   
4) filterWriter:用於寫入過濾字元流 
   protected FilterWriter(Writer w); 
   
5) PipedWriter:與PipedOutputStream對應

6) StringWriter:無與之對應的以位元組為導向的stream  

主要方法:

  (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個字元寫入輸出流 
  
  (6)  flush( ) //刷空輸出流,並輸出所有被快取的位元組。 
  
  (7)close()    關閉流 public abstract void close() throws IOException

當然這些子類我這裡就不一一去詳細介紹了,我相信大家看懂了字元流的用法,再去學習字元流的話應該不在話下的。

7、輸入流和輸出流的應用

這裡就介紹的檔案輸入流和檔案輸出流一起使用的情況。

例子:利用程式將檔案a.CHM 拷貝到a.chm中。

這裡分別用了四種方式,也算對這些的流的速率的一個對比,這四種情況分別是:

1)基本位元組流每次讀一個位元組

2)基本位元組流每次讀一組位元組

3)高效位元組流每次讀一個位元組(即包裝流)

4)高效位元組流每次讀一組位元組(即包裝流)

public class CopyDemo {

    public static void main(String[] args) throws IOException {
        String source = "a.CHM";
        String dict = "copy.chm";
        long startTime = System.currentTimeMillis();
        method4(source, dict);
        long endTime = System.currentTimeMillis();
        System.out.println((endTime - startTime)+"毫秒");
    }

    // 基本位元組流每次讀一個位元組
    public static void method1(String source, String dict) throws IOException {
        FileInputStream fis = new FileInputStream(source);
        FileOutputStream fos = new FileOutputStream(dict);
        int a;
        while ((a = fis.read()) != -1) {
            fos.write(a);
        }
        fos.close();
        fis.close();
    }

    // 基本位元組流每次讀一組位元組
    public static void method2(String source, String dict) throws IOException {
        FileInputStream fis = new FileInputStream(source);
        FileOutputStream fos = new FileOutputStream(dict);
        byte[] b = new byte[1024];
        int len;
        while ((len = fis.read(b)) != -1) {
            fos.write(b, 0, len);
        }
        fos.close();
        fis.close();
    }

    // 高效位元組流每次讀一個位元組
    public static void method3(String source, String dict) throws IOException {
        FileInputStream fis = new FileInputStream(source);
        BufferedInputStream bis = new BufferedInputStream(fis);
        FileOutputStream fos = new FileOutputStream(dict);
        BufferedOutputStream bos = new BufferedOutputStream(fos);
        int a;
        while ((a = fis.read()) != -1) {
            fos.write(a);
        }
        bos.close();
        bis.close();
    }

    // 基本位元組流每次讀一組位元組
    public static void method4(String source, String dict) throws IOException {
        FileInputStream fis = new FileInputStream(source);
        BufferedInputStream bis = new BufferedInputStream(fis);
        FileOutputStream fos = new FileOutputStream(dict);
        BufferedOutputStream bos = new BufferedOutputStream(fos);
        byte[] b = new byte[1024];
        int len;
        while ((len = fis.read(b)) != -1) {
            fos.write(b, 0, len);
        }
        bos.close();
        bis.close();
    }
}

當然測試的檔案儘量大點,不然差別不是很明顯,我這裡的a.CHM檔案的大小為35.2 MB。當然用時也跟計算機效能有點關係。當然檔案大點差別就更加明顯點。

在我電腦上的四種情況的用時為:

1.221196毫秒

2.346毫秒

3.220454毫秒

4.335毫秒

這裡1和3,2和4比較,可以發現包裝流更加高效,為什麼呢?

比較下2和4,把這個搞懂,其他的也就懂了。

這裡寫圖片描述

這兩個從硬碟上讀取內容的時間應該差不多,但時間就差在寫的時間上了,快取區一般在記憶體,記憶體的速度是很快的,將資料寫入到硬碟的速度是很慢的,所以我們只需要減少寫入硬碟的次數即可。包裝流的預設快取區大小為8192位元組,包裝流讀取8次才寫一次,所以包裝流的效率是大大增加了。

六、Java IO 的一般使用原則

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

1 、是檔案: FileInputStream, FileOutputStream, ( 位元組流 )FileReader, FileWriter( 字元 )

2 、是 byte[] : ByteArrayInputStream, ByteArrayOutputStream( 位元組流 )

3 、是 Char[]: CharArrayReader, CharArrayWriter( 字元流 )

4 、是 String: StringBufferInputStream, StringBufferOuputStream ( 位元組流 )StringReader, StringWriter( 字元流 )

5 、網路資料流: InputStream, OutputStream,( 位元組流 ) Reader, Writer( 字元流 )

二)按是否格式化輸出分:

1 、要格式化輸出: PrintStream, PrintWriter

三)按是否要緩衝分:

1 、要緩衝: BufferedInputStream, BufferedOutputStream,( 位元組流 ) BufferedReader, BufferedWriter( 字元流 )

四)按資料格式分:

1 、二進位制格式(只要不能確定是純文字的,比如圖片、音訊、視訊) : InputStream, OutputStream 及其所有帶 Stream 結尾的子類

2 、純文字格式(含純英文與漢字或其他編碼方式); Reader, Writer 及其所有帶 Reader, Writer 的子類

五)按輸入輸出分:

1 、輸入: Reader, InputStream 型別的子類

2 、輸出: Writer, OutputStream 型別的子類

六)特殊需要:

1 、從 Stream 到 Reader,Writer 的轉換類: InputStreamReader, OutputStreamWriter

2 、物件輸入輸出: ObjectInputStream, ObjectOutputStream

3 、程序間通訊: PipeInputStream, PipeOutputStream, PipeReader, PipeWriter

4 、合併輸入: SequenceInputStream

5 、更特殊的需要: PushbackInputStream, PushbackReader, LineNumberInputStream, LineNumberReader

 

===============================================================================================

詳細講解JAVA中的IO流

 

一、流的概念

       流(stream)的概念源於UNIX中管道(pipe)的概念。在UNIX中,管道是一條不間斷的位元組流,用來實現程式或程序間的通訊,或讀寫外圍裝置、外部檔案等。
       一個流,必有源端和目的端,它們可以是計算機記憶體的某些區域,也可以是磁碟檔案,甚至可以是Internet上的某個URL。
        流的方向是重要的,根據流的方向,流可分為兩類:輸入流和輸出流。使用者可以從輸入流中讀取資訊,但不能寫它。相反,對輸出流,只能往輸入流寫,而不能讀它。
       實際上,流的源端和目的端可簡單地看成是位元組的生產者和消費者,對輸入流,可不必關心它的源端是什麼,只要簡單地從流中讀資料,而對輸出流,也可不知道它的目的端,只是簡單地往流中寫資料。 

       形象的比喻——水流 ,檔案======程式 ,檔案和程式之間連線一個管道,水流就在之間形成了,自然也就出現了方向:可以流進,也可以流出.便於理解,這麼定義流: 流就是一個管道里面有流水,這個管道連線了檔案和程式。

二、流的分類

  1. java.io包中的類對應兩類流,一類流直接從指定的位置(如磁碟檔案或記憶體區域)讀或寫,這類流稱為結點流(node stream),其它的流則稱為過濾器(filters)。過濾器輸入流往往是以其它輸入流作為它的輸入源,經過過濾或處理後再以新的輸入流的形式提供給使用者,過濾器輸出流的原理也類似。 

  2. Java的常用輸入、輸出流

java.io包中的stream類根據它們操作物件的型別是字元還是位元組可分為兩大類: 字元流和位元組流。

 

  • Java的位元組流

InputStream是所有位元組輸入流的祖先,而OutputStream是所有位元組輸出流的祖先。

  • Java的字元流

Reader是所有讀取字串輸入流的祖先,而writer是所有輸出字串的祖先。

結合開始所說的輸入/輸出流 ,出現了個一小框架。

                     位元組流                         字元流
輸入流        InputStream               Reader
輸出流        OutputStream            Writer

JAVA位元組流

  • FileInputStream和FileOutputStream
    這兩個類屬於結點流,第一個類的源端和第二個類的目的端都是磁碟檔案,它們的構造方法允許通過檔案的路徑名來構造相應的流。如: 
    FileInputStream infile = new FileInputStream("myfile.dat");
    FileOutputStream outfile = new FileOutputStream("results.dat");

要注意的是,構造FileInputStream, 對應的檔案必須存在並且是可讀的,而構造FileOutputStream時,如輸出檔案已存在,則必須是可覆蓋的。

  • BufferedInputStream和BufferedOutputStream
    它們是過濾器流,其作用是提高輸入輸出的效率。
  • DataInputStream和DataOutputStream
    這兩個類建立的物件分別被稱為資料輸入流和資料輸出流。這是很有用的兩個流,它們允許程式按與機器無關的風格讀寫Java資料。所以比較適合於網路上的資料傳輸。這兩個流也是過濾器流,常以其它流如InputStream或OutputStream作為它們的輸入或輸出。

Java的字元流


    字元流主要是用來處理字元的。Java採用16位的Unicode來表示字串和字元,對應的字元流按輸入和輸出分別稱為readers和writers。

  • InputStreamReader和OutputStreamWriter
    在構造這兩個類對應的流時,它們會自動進行轉換,將平臺預設的編碼集編碼的位元組轉換為Unicode字元。對英語環境,其預設的編碼集一般為ISO8859-1。
  • BufferedReader和BufferedWriter
    這兩個類對應的流使用了緩衝,能大大提高輸入輸出的效率。這兩個也是過濾器流,常用來對InputStreamReader和OutputStreamWriter進行處理。如:

複製程式碼

 1 import java.io.*;
 2 public class Echo {
 3   public static void main(String[] args) {
 4     BufferedReader in =
 5       new BufferedReader(
 6         new InputStreamReader(System.in));
 7     String s;
 8     try {
 9       while((s = in.readLine()).length() != 0)
10         System.out.println(s);
11       // An empty line terminates the program
12     } catch(IOException e) {
13       e.printStackTrace();
14     }
15   }
16 } 

複製程式碼

該程式接受鍵盤輸入並回顯。

對BufferedReader類,該類的readLine()方法能一次從流中讀入一行,但對於BufferedWriter類,就沒有一次寫一行的方法,所以若要向流中一次寫一行,可用PrintWriter類將原來的流改造成新的列印流,PrintWriter類有一個方法println(),能一次輸出一行。如: 

............
PrintWriter out = new PrintWriter(new BufferedWriter(
      new FileWriter("D:\javacode\test.txt")));
out.println("Hello World!");
out.close();
............

 

 

例子:

1,與控制檯相關。的讀入/寫出。 實現了字串的複製。

 

複製程式碼

 1 import java.io.*;
 2 public class TextRead{
 3 
 4 public static void main(String[] args){
 5    BufferedReader bf = null;/*BufferedReader相當於一個大桶,其實就是記憶體,這裡實現了大量大量的讀寫 ,而不是讀一個位元組或字元就直接寫如硬碟,加強了對硬碟的保護。*/
 6    try{
 7     while(true){ // while(true){}迴圈保證程式不會結束
 8     
 9        bf = new BufferedReader(new InputStreamReader(System.in));
10        /*System.in 為標準輸入,System.out為標準輸出*/
11        /*InputStreamReader用語將位元組流到字元流的轉化,這也就是處理流了
12         *在這裡相當與2個管道接在System.in與程式之間。
13         *readLine()方法功能比較好用,也就通過處理流來實現更好功能。
14         **/
15      String line = bf.readLine();
16      System.out.println(line);
17     }  
18    }catch(Exception e){
19     e.printStackTrace();
20    }finally{
21     //一定要關閉流,用完後。最好放在
22 
23 filally 裡面。  
24     try{   
25      if(bf!=null){
26       bf.close();
27      }
28     }catch(Exception e){
29        e.printStackTrace();
30     }  
31    }  
32 }
33 }

複製程式碼

2,與檔案 相關的 讀寫。    實現了檔案的複製。

複製程式碼

 1 import java.io.*;
 2 public class TextRead{
 3 
 4 public static void main(String[] args){
 5    File fin,fout;  
 6    BufferedReader bf = null;
 7    PrintWriter pw = null;
 8    try{
 9     fin = new File("zzc.txt"); //注意檔案與程式都要在同一個資料夾下。zzc.txt為要被複制的檔案。
10     fout = new File("copyzzc.txt"); //如果沒有會自動建立。
11     bf = new BufferedReader(new FileReader(fin));
12     pw = new PrintWriter(fout); //PrintWriter為列印流,也可以使用BufferedWriter.
13     String line = bf.readLine();
14     while(line!=null){
15     pw.println(line);
16      line = bf.readLine();
17     }
18    }catch(Exception e){
19     e.printStackTrace();
20    }finally{
21     try{
22     //關閉 檔案。
23      if(bf!=null){
24       bf.close();
25       bf = null;
26      }
27      if(pw!=null){
28       pw.close();
29       pw = null;
30      }
31     }catch(Exception e){
32      e.printStackTrace();
33     }
34    }
35 }
36 }

複製程式碼

 

還有好多類:

像RandomAccessFile類,序列化介面,都十分重要。

Java有一種特殊型別的IO資料流——DataOutputStream——它可以保證“無論資料來自何種機器,只要使用一個DataInputStream收取這些資料,就可用本機正確的格式儲存它們.

以後在把示例加上,還有寫比較好的方法。

其實感覺這已經是固定模式了,一提到從鍵盤讀取資料 就會聯想到:

new BufferedReader(new InputStreamReader(System.in))

現在水平達不到,還是記些固定格式比較好,以至於會用。

某個老師也說過,創新是建立在紮實的基礎之上,越來越覺得基礎重要了。

===================================================================

===================================================================

===================================================================

Java中的流,可以從不同的角度進行分類。

按照數據流的方向不同可以分為:輸入流和輸出流。

按照處理資料單位不同可以分為:位元組流和字元流。

按照實現功能不同可以分為:節點流和處理流。

 

輸出流:

 

 

輸入流:

 

因此輸入和輸出都是從程式的角度來說的。

位元組流:一次讀入或讀出是8位二進位制。

字元流:一次讀入或讀出是16位二進位制。

位元組流和字元流的原理是相同的,只不過處理的單位不同而已。字尾是Stream是位元組流,而後綴是Reader,Writer是字元流。

 

節點流:直接與資料來源相連,讀入或讀出。

 

直接使用節點流,讀寫不方便,為了更快的讀寫檔案,才有了處理流。

處理流:與節點流一塊使用,在節點流的基礎上,再套接一層,套接在節點流上的就是處理流。

 

Jdk提供的流繼承了四大類:InputStream(位元組輸入流),OutputStream(位元組輸出流),Reader(字元輸入流),Writer(字元輸出流)。

以下是java中io中常用的流。

 

位元組輸入流:

位元組輸出流:

字元輸入流:

字元輸出流:

 

簡單介紹其上圖:

對檔案進行操作:FileInputStream(位元組輸入流),FileOutputStream(位元組輸出流),FileReader(字元輸入流),FileWriter(字元輸出流)

對管道進行操作:PipedInputStream(位元組輸入流),PipedOutStream(位元組輸出流),PipedReader(字元輸入流),PipedWriter(字元輸出流)

PipedInputStream的一個例項要和PipedOutputStream的一個例項共同使用,共同完成管道的讀取寫入操作。主要用於執行緒操作。

位元組/字元陣列:ByteArrayInputStream,ByteArrayOutputStream,CharArrayReader,CharArrayWriter是在記憶體中開闢了一個位元組或字元陣列。

Buffered緩衝流::BufferedInputStream,BufferedOutputStream,BufferedReader,BufferedWriter,是帶緩衝區的處理流,緩衝區的作用的主要目的是:避免每次和硬碟打交道,提高資料訪問的效率。

轉化流:InputStreamReader/OutputStreamWriter,把位元組轉化成字元。

資料流:DataInputStream,DataOutputStream。

因為平時若是我們輸出一個8個位元組的long型別或4個位元組的float型別,那怎麼辦呢?可以一個位元組一個位元組輸出,也可以把轉換成字串輸出,但是這樣轉換費時間,若是直接輸出該多好啊,因此這個資料流就解決了我們輸出資料型別的困難。資料流可以直接輸出float型別或long型別,提高了資料讀寫的效率。

列印流:printStream,printWriter,一般是列印到控制檯,可以進行控制列印的地方。

物件流:ObjectInputStream,ObjectOutputStream,把封裝的物件直接輸出,而不是一個個在轉換成字串再輸出。

序列化流:SequenceInputStream。

物件序列化:把物件直接轉換成二進位制,寫入介質中。

使用物件流需要實現Serializable介面,否則會報錯。而若用transient關鍵字修飾成員變數,不寫入該成員變數,若是引用型別的成員變數為null,值型別的成員變數為0.