JAVA IO 輸入輸出流
1、概要
1)java IO 中常用的類:
在整個Java.io包中最重要的就是5個類和一個介面。
5個類指的是 File、OutputStream、InputStream、Writer、Reader;
一個介面指的是Serializable;
2)Java I/O主要包括如下幾個層次,包含三個部分:
1.流式部分――IO的主體部分;
2.非流式部分――主要包含一些輔助流式部分的類,如:File類、RandomAccessFile類和FileDescriptor等類;
3.其他類--檔案讀取部分的與安全相關的類,如:SerializablePermission類,以及與本地作業系統相關的檔案系統的類,如:FileSystem類和Win32FileSystem類和WinNTFileSystem類。
3)主要的類如下:
1. File(檔案特徵與管理):用於檔案或者目錄的描述資訊,例如生成新目錄,修改檔名,刪除檔案,判斷檔案所在路徑等。
2. InputStream(二進位制格式操作):抽象類,基於位元組的輸入操作,是所有輸入流的父類。定義了所有輸入流都具有的共同特徵。
3. OutputStream(二進位制格式操作):抽象類。基於位元組的輸出操作。是所有輸出流的父類。定義了所有輸出流都具有的共同特徵。
4.Reader(檔案格式操作):抽象類,基於字元的輸入操作。
5. Writer(檔案格式操作):抽象類,基於字元的輸出操作。
6. RandomAccessFile(隨機檔案操作):一個獨立的類,直接繼承至Object.它的功能豐富,可以從檔案的任意位置進行存取(輸入輸出)操作。
4)Java中IO流的體系結構如圖:
5)Java流類的類結構圖:
6)Java流類的類結構圖:
流:代表任何有能力產出資料的資料來源物件或者是有能力接受資料的接收端物件<Thinking in Java>
流的本質:資料傳輸,根據資料傳輸特性將流抽象為各種類,方便更直觀的進行資料操作。
流的作用:為資料來源和目的地建立一個輸送通道。
Java中將輸入輸出抽象稱為流,就好像水管,將兩個容器連線起來。流是一組有順序的,有起點和終點的位元組集合,是對資料傳輸的總稱或抽象。即資料在兩裝置間的傳輸稱為流.
7)Java IO所採用的模型
Java的IO模型設計非常優秀,它使用Decorator(裝飾者)模式,按功能劃分Stream,您可以動態裝配這些Stream,以便獲得您需要的功能。
例如,您需要一個具有緩衝的檔案輸入流,則應當組合使用FileInputStream和BufferedInputStream。
8)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
2、字元流和位元組流
流序列中的資料既可以是未經加工的原始二進位制資料,也可以是經一定編碼處理後符合某種格式規定的特定資料。因此Java中的流分為兩種:
位元組流:資料流中最小的資料單元是位元組。
字元流:資料流中最小的資料單元是字元, Java中的字元是Unicode編碼,一個字元佔用兩個位元組。
字元流的由來: Java中字元是採用Unicode標準,一個字元是16位,即一個字元使用兩個位元組來表示。為此,JAVA中引入了處理字元的流。因為資料編碼的不同,而有了對字元進行高效操作的流物件。本質其實就是基於位元組流讀取時,去查了指定的碼錶。因字元一般佔用兩個位元組,字元流方法一般都會有緩衝區以緩衝字元資料。位元組流類的方法就不一定有緩衝區(如:FileInputStream/FileOutputStream)。
1-1)輸入位元組流InputStream
IO 中輸入位元組流的繼承圖可見上圖,可以看出:
1. InputStream是所有的輸入位元組流的父類,它是一個抽象類。
2. ByteArrayInputStream、StringBufferInputStream(上圖的StreamBufferInputStream)(已廢棄)、FileInputStream是三種基本的介質流,它們分別從Byte陣列、StringBuffer、和本地檔案中讀取資料。
3. PipedInputStream是從與其它執行緒共用的管道中讀取資料.
4. ObjectInputStream和所有FilterInputStream的子類都是裝飾流(裝飾器模式的主角)。
5. I/O類庫提供StringBufferInputStream類的本意是把字串轉換為位元組流,然後進行讀操作,但是在這個類的實現中僅僅使用了字元編碼的低8位,不能把字串中的所有字元(比如中文字元)正確轉換為位元組,因此這個類已經被廢棄,取而代之的是StringReader類,該類已屬於字元流範圍。
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。1-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() :關閉輸出流,釋放和這個流相關的系統資源。1-3)程式碼示例
ByteArrayInputStream,FileInputStream區別在於操作物件不一樣,前者操作byte陣列,後者操作檔案。read操作讀byte陣列、檔案進入流,write操作寫流資料進入byte陣列、檔案。
這裡以FileInputStream操作檔案為例,且常用的也是FileInputStream類。
前面講FileInputStream,FileOutputStream是基本的介質流,用他們已經完全可以實現流的操作,其他子類(如bufferdOutStream )都是其裝飾類,用之可有效率上的優化,不用亦可。
package com.javaTest.io; import java.io.*; public class IoTest { public static void main(String[] args) { File file = null; File ofile = null; InputStream in = null; OutputStream out = null; try { file = new File("D:\\test" + File.separator + "111.txt"); ofile = new File("D:\\test" + File.separator + "222.txt"); in = new FileInputStream(file); out = new FileOutputStream(ofile); Integer r = null; while ((r = in.read()) != -1) { out.write(r); } } catch (Exception e) { e.printStackTrace(); } finally { try { in.close(); out.close(); } catch (IOException e) { e.printStackTrace(); } } } }View Code
2-1)字元輸入流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處開始存放,返回值為實際讀取的字元數量,該方法必須由子類實現*/2-2)字元輸出流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個字元寫入輸出流2-3)程式碼示例
由於檔案編碼的原因,以下程式碼輸出的檔案中文可能為亂碼,要解決需要知道原檔案編碼並以字流輸入,轉化為字元流後可以以正確的編碼方式輸出。(程式碼見3、字元流和位元組流的轉換)
package com.javaTest.io; import java.io.*; public class IoTest3 { public static void main(String[] args) { Reader file = null; Writer ofile = null; try { file = new FileReader("D:\\test" + File.separator + "111.txt"); ofile = new FileWriter("D:\\test" + File.separator + "222.txt"); int r; while ((r = file.read()) != -1) { ofile.write(r); } } catch (Exception e) { e.printStackTrace(); } finally { try { file.close(); ofile.close(); } catch (IOException e) { e.printStackTrace(); } } } }View Code
3、字元流和位元組流的轉換
1)轉換流的特點:
1. 其是字元流和位元組流之間的橋樑
2. 可對讀取到的位元組資料經過指定編碼轉換成字元
3. 可對讀取到的字元資料經過指定編碼轉換成位元組
2)何時使用轉換流?
1. 當位元組和字元之間有轉換動作時;
2. 流操作的資料需要編碼或解碼時。
3)具體的物件體現:
轉換流:在IO中還存在一類是轉換流,將位元組流轉換為字元流,同時可以將字元流轉化為位元組流。
1. InputStreamReader:位元組到字元的橋樑
2. OutputStreamWriter:字元到位元組的橋樑
OutputStreamWriter(OutStreamout):將位元組流以字元流輸出。
InputStreamReader(InputStream in):將位元組流以字元流輸入。
這兩個流物件是字元體系中的成員,它們有轉換作用,本身又是字元流,所以在構造的時候需要傳入位元組流物件進來
4)位元組流和字元流的區別
1. 讀寫單位不同:位元組流以位元組(8bit)為單位,字元流以字元為單位,根據碼錶對映字元,一次可能讀多個位元組。
2. 處理物件不同:位元組流能處理所有型別的資料(如圖片、avi等),而字元流只能處理字元型別的資料。
結論:只要是處理純文字資料,就優先考慮使用字元流。除此之外都使用位元組流。
5)程式碼示例
該段程式碼關鍵在讀入檔案時需要以正確的編碼讀入,測試時本機txt檔案編碼為GB2312,經測試以GB2312或GBK編碼方式讀入檔案後輸出UTF-8格式的檔案中文均不會亂碼。
網上搜到很多可以判斷輸入檔案編碼格式的java程式碼,可供使用,只是不知其原理,待日後探索
package com.javaTest.io; import java.io.*; public class IoTest2 { public static void main(String[] args) { File file = null; File ofile = null; InputStream in = null; OutputStream out = null; InputStreamReader reader = null; OutputStreamWriter writer = null; try { file = new File("D:\\test" + File.separator + "111.txt"); ofile = new File("D:\\test" + File.separator + "222.txt"); in = new FileInputStream(file); out = new FileOutputStream(ofile); reader = new InputStreamReader(in,"GB2312"); writer = new OutputStreamWriter(out,"UTF-8"); int r; while ((r = reader.read()) != -1) { writer.write(r); writer.flush(); } } catch (Exception e) { e.printStackTrace(); } finally { try { in.close(); out.close(); writer.close(); reader.close(); } catch (IOException e) { e.printStackTrace(); } } } }View Code
4、緩衝流(基本介質流的包裝類)
1-1)位元組緩衝流BufferedInputStream
BufferedInputStream繼承於FilterInputStream,提供緩衝輸入流功能。緩衝輸入流相對於普通輸入流的優勢是,它提供了一個緩衝陣列,每次呼叫read方法的時候,它首先嚐試從緩衝區裡讀取資料,若讀取失敗(緩衝區無可讀資料),則選擇從物理資料來源(譬如檔案)讀取新資料(這裡會嘗試儘可能讀取多的位元組)放入到緩衝區中,最後再將緩衝區中的內容部分或全部返回給使用者.由於從緩衝區裡讀取資料遠比直接從物理資料來源(譬如檔案)讀取速度快。
1-2)方法介紹
BufferedInputStream
BufferedInputStream:位元組緩衝輸入流,提高了讀取效率。
//構造方法 BufferedInputStream(InputStream in) BufferedInputStream(InputStream in, int size) //下一位元組是否可讀 synchronized int available() //關閉 void close() //標記, readlimit為mark後最多可讀取的位元組數 synchronized void mark(int readlimit) //是否支援mark, true boolean markSupported() //讀取一個位元組 synchronized int read() //讀取多個位元組到b synchronized int read(byte[] b, int off, int len) //重置會mark位置 synchronized void reset() //跳過n個位元組 synchronized long skip(long n)
BufferedOutputStream
BufferedOutputStream:位元組緩衝輸出流,提高了寫出效率。
構造方法: // 建立一個新的緩衝輸出流,以將資料寫入指定的底層輸出流 BufferedOutputStream(OutputStream out) // 建立一個新的緩衝輸出流,以將具有指定緩衝區大小的資料寫入指定的底層輸出流 BufferedOutputStream(OutputStream out, int size) 常用方法: // 將指定 byte 陣列中從偏移量 off 開始的 len 個位元組寫入此緩衝的輸出流 void write(byte[] b, int off, int len) // 將指定的位元組寫入此緩衝的輸出流 void write(int b) // 重新整理此緩衝的輸出流 void flush()
1-3)程式碼示例
package com.javaTest.io; import java.io.*; public class IoTest { public static void main(String[] args) { File file = null; File ofile = null; InputStream in = null; OutputStream out = null; BufferedInputStream input = null; BufferedOutputStream output = null; try { file = new File("D:\\test" + File.separator + "111.txt"); ofile = new File("D:\\test" + File.separator + "222.txt"); in = new FileInputStream(file); out = new FileOutputStream(ofile); input = new BufferedInputStream(in,1024); output = new BufferedOutputStream(out); Integer r = null; while ((r = input.read()) != -1) { output.write(r); } } catch (Exception e) { e.printStackTrace(); } finally { try { in.close(); out.close(); input.close(); output.close(); } catch (IOException e) { e.printStackTrace(); } } } }View Code2-1)字元緩衝流BufferedReader
BufferedReader
BufferedReader:字元緩衝流,從字元輸入流中讀取文字,緩衝各個字元,從而實現字元、陣列和行的高效讀取。
構造方法: // 建立一個使用預設大小輸入緩衝區的緩衝字元輸入流 BufferedReader(Reader in) // 建立一個使用指定大小輸入緩衝區的緩衝字元輸入流 BufferedReader(Reader in, int sz) 特有方法: // 讀取一個文字行 String readLine()
BufferedWriter
BufferedWriter:字元緩衝流,將文字寫入字元輸出流,緩衝各個字元,從而提供單個字元、陣列和字串的高效寫入。
構造方法: // 建立一個使用預設大小輸出緩衝區的緩衝字元輸出流 BufferedWriter(Writer out) // 建立一個使用給定大小輸出緩衝區的新緩衝字元輸出流 BufferedWriter(Writer out, int sz) 特有方法: // 寫入一個行分隔符 void newLine()
2-3)程式碼示例
package com.javaTest.io; import java.io.*; public class IoTest3 { public static void main(String[] args) { Reader file = null; Writer ofile = null; BufferedReader breader = null; BufferedWriter bwrite = null; try { file = new FileReader("D:\\test" + File.separator + "111.txt"); ofile = new FileWriter("D:\\test" + File.separator + "222.txt"); breader = new BufferedReader(file, 1024); bwrite = new BufferedWriter(ofile); int r; while ((r = breader.read()) != -1) { bwrite.write(r); bwrite.flush(); } } catch (Exception e) { e.printStackTrace(); } finally { try { file.close(); ofile.close(); breader.close(); bwrite.close(); } catch (IOException e) { e.printStackTrace(); } } } }View Code3).FileReader、FileWriter
FileReader:InputStreamReader類的直接子類,用來讀取字元檔案的便捷類,使用預設字元編碼。 FileWriter:OutputStreamWriter類的直接子類,用來寫入字元檔案的便捷類,使用預設字元編碼。
5、非流式檔案類--File類
從定義看,File類是Object的直接子類,同時它繼承了Comparable介面可以進行陣列的排序。
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( ); 建立目錄
6、RandomAccessFile類
這個是JDK上的截圖,我們可以看到它的父類是Object,沒有繼承位元組流、字元流家族中任何一個類。並且它實現了DataInput、DataOutput這兩個介面,也就意味著這個類既可以讀也可以寫。
注意:該物件在例項化時,如果要操作的檔案不存在,會自動建立;如果檔案存在,寫資料未指定位置,會從頭開始寫,即覆蓋原有的內容。 可以用於多執行緒下載或多個執行緒同時寫資料到檔案。
RandomAccessFile包含兩個方法來操作檔案記錄指標:
- long getFilePointer():返回檔案記錄指標的當前位置
- void seek(long pos):將檔案記錄指標定位到pos位置(初始位置)
RandomAccessFile類在建立物件時,除了指定檔案本身,還需要指定一個mode引數,該引數指定RandomAccessFile的訪問模式,該引數有如下四個值:
- r:以只讀方式開啟指定檔案。如果試圖對該RandomAccessFile指定的檔案執行寫入方法則會丟擲IOException
- rw:以讀取、寫入方式開啟指定檔案。如果該檔案不存在,則嘗試建立檔案
- rws:以讀取、寫入方式開啟指定檔案。相對於rw模式,還要求對檔案的內容或元資料的每個更新都同步寫入到底層儲存裝置,預設情形下(rw模式下),是使用buffer的,只有cache滿的或者使用RandomAccessFile.close()關閉流的時候兒才真正的寫到檔案
- rwd:與rws類似,只是僅對檔案的內容同步更新到磁碟,而不修改檔案的元資料
程式碼示例:
package com.javaTest.io; import java.io.*; public class IoTest4 { public static void main(String[] args) { File file = null; File ofile = null; RandomAccessFile acinfile = null; RandomAccessFile acoutfile = null; try { file = new File("D:\\test" + File.separator + "111.txt"); ofile = new File("D:\\test" + File.separator + "222.txt"); acinfile = new RandomAccessFile(file, "r"); acoutfile = new RandomAccessFile(ofile, "rw"); byte[] b = new byte[8]; int len; int point = acinfile.getFilePointer();//獲取檔案讀寫位置 acinfile.seek(8);//設定檔案從第8個位元組開始讀寫 acoutfile.seek(0);//設定檔案從第0個位元組開始讀寫 len = acinfile.read(b, 0, 8);//從讀寫位置開始讀取檔案8個位元組 acoutfile.write(b, 0, len);//從讀寫位置開始寫入讀取的位元組數 } catch (Exception e) { e.printStackTrace(); } finally { try { acinfile.close(); acoutfile.close(); } catch (IOException e) { e.printStackTrace(); } } } }View Code