深入理解Java中的IO
引言:
對程序語言的設計者來說,創建一個好的輸入/輸出(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流類的類結構圖:
1、流的概念和作用
流:代表任何有能力產出數據的數據源對象或者是有能力接受數據的接收端對象<Thinking in Java>
流的本質:數據傳輸,根據數據傳輸特性將流抽象為各種類,方便更直觀的進行數據操作。
流的作用:為數據源和目的地建立一個輸送通道。
Java中將輸入輸出抽象稱為流,就好像水管,將兩個容器連接起來。流是一組有順序的,有起點和終點的字節集合,是對數據傳輸的總稱或抽象。即數據在兩設備間的傳輸稱為流.
2、Java IO所采用的模型
Java的IO模型設計非常優秀,它使用Decorator(裝飾者)模式,按功能划分Stream,您可以動態裝配這些Stream,以便獲得您需要的功能。
例如,您需要一個具有緩沖的文件輸入流,則應當組合使用FileInputStream和BufferedInputStream。
3、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、只讀或只寫,每個流只能是輸入流或輸出流的一種,不能同時具備兩個功能,輸入流只能進行讀操作,對輸出流只能進行寫操作。在一個數據傳輸通道中,如果既要寫入數據,又要讀取數據,則要分別提供兩個流。
4、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
運行結果:
- 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操作被中斷時,拋出這種類型的異常。
轉載:http://blog.csdn.net/qq_25184739/article/details/51205186