java的I/O系統是一個大塊頭,包含的東西太多了(就貼介面表格就能貼的讓人頭暈目眩),而且內容順序不好安排,橫切有位元組流字元流,豎切有輸入輸出,再加新舊幾輪迭代,三個維度縱橫交錯,真的不好寫,以至於寫這篇部落格之前心裡糾結了很久才下定的決心。
因為不繫統的梳理出來仔仔細細的過一遍,就不算是理解了。
本篇部落格只是自己整理,記錄的是自己覺得有必要記錄的東西,所以有一些偏冷門的類和用法是沒有收錄的,因為類實在太多了,記那麼多根本沒有必要,反倒讓人學起來找不著東南西北。
總之一句話:走大路,看輔路,不用管岔路。岔路留著讓百度去查。
I/O基礎知識
流
java程式通過流執行I/O(I/O的意思就是 輸入/輸出)。
流,是一種抽象,要麼產生資訊,要麼使用資訊,流通過java的IO系統連結到物理裝置。
儘管所有的流連結的物理裝置是不同的,但是他們的行為方式都是相同的,因此,可以為任何型別的裝置應用相同的IO類和方法。這意味著可以將許多不同型別的輸入:磁碟檔案、鍵盤或網路socket,抽象為輸入流。與之對應,輸出流可以引用控制檯、磁碟檔案或網路連線。
流是處理輸入/輸出的一種清晰方式,例如程式碼中所有的部分都不需要去理解鍵盤和網路之間的區別,只需要操作流就行了,也就是說,‘流’遮蔽了實際IO裝置中處理資料的細節。
位元組流和字元流
java定義了兩種型別的流:位元組流和字元流,兩種流都有輸入、輸出兩個內容。
InputStream和OutStream針對位元組流和而設計,為處理位元組的輸入和輸出提供了方法。
Reader和Writer針對字元流而設計,為處理字元的輸入和輸出提供了方便的方法。
最初版本的java 1.0並沒有提供字元流,因此,所有IO都是面向位元組的。字元流是java 1.1新增的,並且某些面向位元組的類和方法不再推薦使用。
另外一點:在最底層,所有IO仍然是面向位元組的,基於字元的流只是為處理字元提供了一種方便和高效的方法。
關閉流的兩種方式
手動關閉
通常,當不需要時,流必須關閉,如果沒有關閉,就可能會導致記憶體洩漏以及資源緊張。
從java最初發布以來就一直有關閉流的close()方法,對於這種方式,通常是在finally程式碼塊中呼叫close()方法。
自動關閉
到JDK7的時候,官方增加了一個新特性,該特性提供了另外一種管理資源的方式,這種方式能自動關閉檔案。
該特性以try語句的擴充套件版為基礎:try(在這裡生成流){}.
當try程式碼塊結束時,資源會被自動關閉,它的優點是當不再需要檔案或者其他資源時,可以防止無意中忘記釋放他們。
下面是帶資源的try語句的3個關鍵點:
1.由帶資源的try語句管理的資源必須是實現了AutoCloseable介面的類的物件。
2.在try程式碼中宣告的資源被隱式宣告為final。
3.通過使用分號分割每個宣告可以管理多個資源。
帶資源的try語句流線化了釋放資源的過程,並消除了可能在無意中忘記釋放資源的風險,所以如果可能的話,儘量在開發中使用這種方式。
位元組流
頂級抽象類
InputStream
位元組流的頂層抽象類,定義了java的位元組流輸入模型。
方法列表:
方法 | 描述 |
int available() | 返回當前可讀取的輸入位元組數 |
void close | 關閉輸入流 |
boolean markSupported() | 當前流是否支援打標記 |
void mark(int limit) | 在輸入流的當前位置放一個標記(並不是所有的流都支援這個特性),該標記在讀入limit個位元組之前一直都有效。 |
void reset() | 將輸入流的指標重置為前面設定的標記,如果當前沒有任何標記,則指標重置到開頭。 |
int read() | 讀入一個位元組,並返回該位元組。(這個read方法在碰到輸入流的結尾時返回-1) |
int read(byte b[]) | 讀入一個位元組陣列,返回實際讀入的位元組數。(在碰到資料流的結尾時返回-1,這個方法最多讀入b.length個位元組) |
int read(byte[] b,int off,int len) | 讀入一個位元組陣列,從byte[off]開始讀,最多讀取len個位元組,返回實際讀入的位元組數,碰到輸入流的結尾時返回-1. |
long skip(long n) | 在輸入流中跳過n個位元組,返回實際跳過的位元組數。 |
OutputStream
位元組流的頂層抽象類,定義了java的位元組流輸出模型。
方法列表:
方法 | 描述 |
void write(int n) | 寫出一個位元組的資料 |
void write(byte[] b) | 寫出一個位元組陣列 |
void write(byte[] b,int off,int len) | 寫出一個位元組陣列,從b[off]開始寫,最多寫len個位元組。 |
void flush() | 沖刷輸出流,結束輸出狀態。 |
void close() | 沖刷並關閉輸出流。 |
檔案流
FileInputStream類
FileInputStream繼承於InputStream,該物件可以用於從檔案中讀取位元組。
建構函式:
FileInputStream(String filePath);//檔案的完整路徑 FileInputStream(File fileObj);//檔案的File物件
這兩個構造器都會丟擲FileNotFoundException異常。
從流中讀取內容的兩種方式:
try(FileInputStream f = new FileInputStream("./src/bytes/en.txt")){ int size; System.out.println("總位元組是:"+(size = f.available())); int num = 3; while(num>0){ System.out.println("單個位元組的讀:"+(char) f.read()); num--; } //注意:此時io流的指標已經到了第4個位元組了,所以下面列印的內容不包含前3個位元組。 byte b[] = new byte[size]; f.read(b); System.out.println("檔案內容是:"+new String(b)); //System.out.println("檔案內容是:"+new String(b,0,size));//也可以這樣 }catch (IOException e){ e.printStackTrace(); }
FileOutputStream類
FileOutputStream繼承於OutputStream,該物件可以將位元組寫入檔案。
建構函式:
FileOutputStream(String filePath) FileOutputStream(String filePath,boolean append) FileOutputStream(File fileObj) FileOutputStream(File fileObj,boolean append)
和FileInputStream一樣,不同的是有一個append引數,這個引數如果為true,則以追加的方式開啟檔案,否則,這個方法會刪除同名的已有檔案建立一個新的輸出流。
另外,如果試圖開啟只讀檔案會丟擲異常。
以下演示將資料寫入檔案:
try(FileOutputStream f1 = new FileOutputStream("./io/src/bytes/file1.txt"); FileOutputStream f2 = new FileOutputStream("./io/src/bytes/file2.txt"); FileOutputStream f3 = new FileOutputStream("./io/src/bytes/file3.txt") ){ String source = "1234567890"; byte[] buf = source.getBytes(); //每跳一個位元組寫 for(int i=0;i<buf.length;i+=2){ f1.write(buf[i]); } //寫全部 f2.write(buf); //寫最後四分之一 (寫出來的是90,因為7.5就是第8個位元組,也就是會從第9個位元組開始寫) f3.write(buf,buf.length-buf.length/4,buf.length/4); }catch (IOException e){ e.printStackTrace(); }
位元組陣列流
ByteArrayInputStream類
ByteArrayInputStream類繼承於InputStream,是使用位元組陣列作為源的輸入流的一個實現,這個類有兩個建構函式,都需要一個位元組陣列來提供資料來源。
ByteArrayInputStream(byte array[]) ByteArrayInputStream(byte array[],int start,int num)
建構函式演示:
String str = "1234567890"; byte[] b = str.getBytes(); ByteArrayInputStream in1 = new ByteArrayInputStream(b); ByteArrayInputStream in2 = new ByteArrayInputStream(b,0,3);//從第0個位元組開始讀 讀3個
ByteArrayInputStream實現了mark()和reset()方法,用法如下:
String str = "1234567890"; byte[] b = str.getBytes(); ByteArrayInputStream in1 = new ByteArrayInputStream(b); int num = 5; for(int i=0;i<5;i++){ System.out.print((char) in1.read());//輸出:12345 if(i == 2){ in1.mark(i); } } in1.reset(); System.out.println(); for(int i=0;i<5;i++){ System.out.print((char) in1.read());//輸出:45678 }
如果沒有mark標記,則呼叫reset()會將指標重置到開頭。(如果沒有in1.mark(i)這行,那麼第二個輸出也是123456)
ByteArrayOutputStream類
ByteArrayOutputStream是使用位元組陣列作為目標的輸出流的一個實現。
它有兩個建構函式,如下所示:
ByteArrayOutputStream() ByteArrayOutputStream(int num)
第一個建構函式,建立一個32位元組的緩衝區。
第二個建構函式,建立一個num大小的緩衝區。
緩衝區會被儲存在ByteArrayOutputStream中受保護的屬性buf變數中。
如果需要的話,緩衝區的大小會自動增加,緩衝區能夠儲存的位元組數量包含在ByteArrayOutputStream中受保護的屬性count變數中。
下面演示ByteArrayOutputStream類的使用:
String source = "1234567890"; ByteArrayOutputStream out1 = new ByteArrayOutputStream(); byte[] b = source.getBytes(); try{ out1.write(b); }catch (IOException e){ e.printStackTrace(); } System.out.println(out1.toString());//輸出:1234567890 byte[] bb = out1.toByteArray(); for(int i=0;i<bb.length;i++){ System.out.print((char)bb[i]); } //輸出:1234567890 System.out.println(); try(FileOutputStream f = new FileOutputStream("./io/src/bytes/f.txt")){ out1.writeTo(f);//把out1的內容寫入f }catch (IOException e){ e.printStackTrace(); } out1.reset(); for(int i=0;i<3;i++){ out1.write('X'); } System.out.println(out1.toString());//輸出:XXX
注意,最後的輸出是三個X,也就是說,它並不是在插入內容,而是把後面的擦掉了。
位元組緩衝流
對於面向位元組的流,緩衝流通過將記憶體緩衝區附加到IO系統來擴充套件過濾流。這種流允許java一次對多個位元組執行多次IO操作,從而提升效能。
因為可以使用緩衝區,所以略過、標記、或重置流都是可能發生的,BufferedInputStream類、BufferedOutputStream類、PushbackInputStream類都實現了緩衝流。
BufferedInputStream類
緩衝IO是很常見的效能優化手段,BufferedInputStream類允許將任何InputStream物件封裝到緩衝流中以提高效能,因為帶緩衝區的輸入流從流中讀入字元時,不會每次都對裝置訪問。
建構函式:
BufferedInputStream(InputStream inputStream) BufferedInputStream(InputStream inputStream,int bufSize)
緩衝輸入流除了任何InputStream都實現了的read()和skip()方法外,還支援mark()和reset()方法。
下面演示如何把一個字串讀兩遍,第二遍略過前5個位元組:
String s = "1234567890"; byte[] buf = s.getBytes(); ByteArrayInputStream in = new ByteArrayInputStream(buf); try(BufferedInputStream f = new BufferedInputStream(in)){ int c = f.available(); int limit = 4; for(int i=0;i<c;i++){ int r = f.read(); if(i == limit){ f.mark(limit); } System.out.print((char) r);//輸出1234567890 } System.out.println(); f.reset(); for(int i=0;i<c-(limit+1);i++){ int r = f.read(); System.out.print((char) r);//輸出67890 } }catch (IOException e){ e.printStackTrace(); }
BufferedOutputStream類
建立一個帶緩衝區的輸出流,帶緩衝區的輸出流在收集要寫出的字元時,不會每次都對裝置訪問,當緩衝區填滿或者當流被沖刷時,資料就被寫出,因此呼叫flush()方法才是資料刷盤。
BufferedOutputStream(OutputStream outputStream) BufferedOutputStream(OutputStream outputStream,int bufSize)
使用示例如下:
try(FileOutputStream f = new FileOutputStream("./io/src/bytes/bufOutput.txt")){ String source = "1234567890"; BufferedOutputStream buf = new BufferedOutputStream(f); byte[] b = source.getBytes(); for(int i=0;i<b.length;i++){ if(i == 3){ buf.flush(); buf.write('-'); } if(i == 6){ buf.flush(); buf.write('-'); } buf.write(b[i]); } }catch (IOException e){ e.printStackTrace(); }
最後寫入檔案的內容是:123-456
可見最後的7890一直在緩衝輸出流中,不呼叫flush()就不會寫入檔案。
PushbackInputStream類
一個可以預覽位元組的輸入流,它讀取位元組,但並不破壞他們,讀取後可以再將他們回推到輸入流中,下次呼叫read()時可以再次被讀取。
建構函式:
PushbackInputStream(InputStream inputStream) PushbackInputStream(InputStream inputStream,int num)
第一個建構函式建立的流物件允許將一個位元組回推到輸入流。
第二個建構函式建立的流物件具有一個長度為num的回推緩衝區,從而允許將多個位元組回推到輸入流中。
除了來自InputStream的方法外,PushbackInputStream類提供了回推方法:unread(int b)
String source = "12345"; byte[] b = source.getBytes(); ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(b); PushbackInputStream push = new PushbackInputStream(byteArrayInputStream); int bytes; try{ int size = push.available(); for(int i=0;i<size;i++){ bytes = push.read(); if(i == 1){ push.unread(bytes); } if(i == 3){ push.unread(bytes); } System.out.print((char) bytes); } }catch (IOException e){ e.printStackTrace(); }
最後的輸出結果是:12233
另外也可以回推位元組陣列unread(byte buf[])
注意:PushbackInputStream會使得標記重置功能無效,因此在使用mark()和reset()之前應該先用markSupported()檢查。
列印流
PrintStream類
用的最多的System.out.println()就是PrintStream類裡的方法。
PrintStream最初的目的是為了以視覺化格式列印所有的基本資料型別以及String物件,它的最重要的兩個方法:print()和println()可以列印出各種型別,如果引數不是基本型別,那麼會自動呼叫物件的toString()方法並顯示結果。
字元流
雖然位元組流為了處理各種型別的IO操作提供了充足的功能,但是老的IO流繼承層次結構僅支援8位位元組流,並不能直接操作16位的Unicode字元。
因為java的一個主要目的就是實現程式碼的“一次編寫,到處執行”,為了國際化的大業,需要為字元提供直接的IO支援,因此新增了Reader和Writer繼承層次結構,另外,新類庫的設計使得它的操作比舊類庫更快。
頂級抽象類
Reader
字元流的頂級抽象類,定義了java字元流的輸入模型。
方法列表:
方法 | 描述 |
int read() | 讀取一個字元,返回表示該字元的證照,如果達到檔案末尾,則返回-1 |
int read(char buff[]) | 讀取buff.length個字元,返回成功讀取的字元數,如果達到檔案末尾,則返回-1 |
int read(CharBuffer buff) | 讀取字元,返回成功讀取的字元數,如果達到檔案末尾,則返回-1 |
int read(char buff[],int offset,int num) | 從buff[offset]開始讀,讀取num個字元,如果檔案達到末尾,則返回-1 |
long skip(long num) | 跳過num個字元,返回實際跳過的字元數 |
void close() | 關閉輸入流,如果試圖繼續讀取,會產生IO異常 |
boolean ready() | 如果下一個輸入請求不等待,就返回true,否則返回false |
boolean markSupported() | 如果這個流支援mark或者reset,就返回true |
void mark(int num) | 在當前流的位置放置標記,該標記在reset()之前一直有效 |
void reset() | 將輸入指標重新設定為前面設定的標記位置 |
Writer
字元流的頂級抽象類,定義了java字元流的輸出模型。
方法列表:
方法 | 描述 |
Writer append(char cn) | 將cn追加到呼叫輸出流的末尾,返回對呼叫流的引用 |
Writer append(CharSequence chars) | 將chars追加到呼叫輸出流的末尾,返回對呼叫流的引用 |
Writer append(CharSequence chars,int begin,int end) | 將chars從begin到end-1之間的字元追加到輸出流的末尾,返回對呼叫流的引用 |
void close() | 關閉輸出流,如果試圖繼續向其中寫入內容,將產生IO異常 |
void flush() | 完成輸出狀態,從而清空所有緩衝區,即重新整理輸出緩衝區 |
void write(int ch) | 寫入一個字元到輸出流中 |
void write(char buff[]) | 將整個字元陣列寫入輸出流中 |
void write(char buff[],int offset,int num) | 將buff陣列中從buff[offset]開始的num個字元寫入輸出流中 |
void write(String str) | 將str寫到輸出流中 |
void write(String str,int offset,int num) | 將字串str中從offset開始寫,寫num個字元 |
檔案流
FileReader類
FileReader類可以建立用於讀取檔案內容的Reader物件。
注意:FileReader 用於讀取字元流。要讀取原始位元組流,請考慮使用 FileInputStream。
建構函式:
FileReader(String filePath)
FileReader(File fileObj)
下面演示如何從檔案中讀取資料並在標準輸出裝置上進行顯示:
try(FileReader f = new FileReader("./io/src/chars/fileReader.txt")){ int c; while ((c = f.read()) != -1){ System.out.print((char) c); } }catch (IOException e){ e.printStackTrace(); }
FileWriter類
FileWriter類可以建立能夠用於寫入檔案的Writer物件。
檔案是否可用或是否可以被建立取決於底層平臺。特別是某些平臺一次只允許一個 FileWriter(或其他檔案寫入物件)開啟檔案進行寫入。在這種情況下,如果所涉及的檔案已經開啟,則此類中的構造方法將失敗。
FileWriter
用於寫入字元流。要寫入原始位元組流,請考慮使用 FileOutputStream
。
建構函式:
FileWriter(String filePath) FileWriter(String filePath,boolean append) FileWriter(File fileObj) FileWriter(File fileObj,boolean append)
append引數代表是否追加到檔案末尾。
以下演示使用FileWriter將字串寫入檔案的用法:
try(FileWriter f1 = new FileWriter("./io/src/chars/file1.txt"); FileWriter f2 = new FileWriter("./io/src/chars/file2.txt"); FileWriter f3 = new FileWriter("./io/src/chars/file3.txt") ){ String source = "1234567890"; int length = source.length(); char[] buff = source.toCharArray(); for(int i=0;i<length;i+=2){ f1.write(buff[i]); } f2.write(source); //f2.write(buff);//等價 //寫最後四分之一 (寫出來的是90,因為7.5就是第8個位元組,也就是會從第9個位元組開始寫) f3.write(buff,buff.length-buff.length/4,buff.length/4); }catch (IOException e){ e.printStackTrace(); }
字元陣列流
CharArrayReader類
CharArrayReader類是使用字元陣列作為源的一個輸入流的實現,該類具有兩個建構函式,每個建構函式都需要一個字元陣列來提供資料來源
CharArrayReader(char array[]) CharArrayReader(char array[],int start,int num)
第二個建構函式根據字元陣列建立Reader物件,指定從start的索引位置的字元開始,一共num個字元。
以下演示使用字元陣列來讀取資料:
String source = "1234567890"; int length = source.length(); char[] c = source.toCharArray(); try(CharArrayReader r = new CharArrayReader(c)){ for(int i = 0;i < length; i++){ System.out.print((char) r.read()); } }catch (IOException e){ e.printStackTrace(); }
CharArrayWriter類
CharArrayWriter類是使用陣列作為目標的一個輸出流實現。
CharArrayWriter() CharArrayWriter(int num)
在第一種形式中,建立使用預設大小的緩衝區,在第二種形式中,建立由num指定大小的緩衝區。
緩衝區儲存在CharArrayWtier類的buf屬性變數中,如果需要,緩衝區的大小可以自動增加,緩衝區能夠容納的字元數量儲存在CharArrayWriter類的count屬性變數中,兩個屬性都是受保護型別。
寫法都是一樣的,以下演示CharArrayWriter把字元寫入檔案:
String source = "1234567890"; char[] c = source.toCharArray(); CharArrayWriter w = new CharArrayWriter(); try{ w.write(c); }catch (IOException e){ e.printStackTrace(); return; } try(FileWriter f = new FileWriter("./io/src/chars/charArray.txt")){ w.writeTo(f); }catch (IOException e){ e.printStackTrace(); } w.reset(); for(int i=0;i<3;i++){ w.write('X'); } System.out.println(w.toString());//輸出XXX
緩衝流
BufferedReader類
BufferedReader類通過緩衝輸入提高效能,該類具有兩個建構函式:
BufferedReader(Reader inputStream) BufferedReader(Reader inputStream,int bufSize)
第二個建構函式指定建立bufSize大小的緩衝字元流。
與面向位元組的流一樣,緩衝的輸入字元流也實現了mark()和reset()方法,並且markSupported()會返回true.
下面例子重寫BufferedInputStream的示例,使之輸出相同:
String s = "1234567890"; char[] buf = s.toCharArray(); CharArrayReader in = new CharArrayReader(buf); try(BufferedReader f = new BufferedReader(in)){ int c = buf.length; int limit = 4; for(int i=0;i<c;i++){ int r = f.read(); if(i == limit){ f.mark(limit); } System.out.print((char) r);//輸出1234567890 } System.out.println(); f.reset(); for(int i=0;i<c-(limit+1);i++){ int r = f.read(); System.out.print((char) r);//輸出67890 } }catch (IOException e){ e.printStackTrace(); }
BufferedWriter類
BufferedWriter是緩衝輸出的Writer,使用BufferedWriter可以通過減少實際向輸出裝置物理的寫入資料的次數來提高效能。
建構函式如下:
BufferedWriter(Writer outputStream) BufferedWriter(Writer outputStream,int bufSize)
第一種形式建立的緩衝流使用具有預設大小的緩衝區,在第二種形式中緩衝區的大小是由bufSize指定的。
同樣的,改寫BufferedOutputStream的示例:
try(FileWriter f = new FileWriter("./io/src/chars/bufOutput.txt")){ String source = "1234567890"; BufferedWriter buf = new BufferedWriter(f); byte[] b = source.getBytes(); for(int i=0;i<b.length;i++){ if(i == 3){ buf.flush(); buf.write('-'); } if(i == 6){ buf.flush(); buf.write('-'); } buf.write(b[i]); } }catch (IOException e){ e.printStackTrace(); }
寫入檔案的內容是:123-456
PushbackReader類
PushbackReader類允許將字元回推到輸入流,下面是該類的兩個建構函式:
PushbackReader(Reader inputStream) PushbackReader(Reader inputStream,int bufSize)
第一個建構函式建立的緩衝流允許回推一個字元,第二種形式回推緩衝區的大小由bufSize指定。
PushbackReader類提供了unread()方法,該方法實現了回推動作,有如下三種形式:
void unread(in ch); void unread(char buffer[]); void unread(char buffer[],int offset,int num);
第一種形式回推cn傳遞的字元,後續呼叫read()就將返回這個被回推的字元。
第二種形式返回buffer中的字元,第三種形式回推buffer中從offset位置開始的num個字元。
當回推緩衝區已滿時,如果試圖返回字元,則會丟擲IO異常。
下面示例PushbackReader的使用方法:
String source = "12345"; char[] c = source.toCharArray(); CharArrayReader charArrayReader = new CharArrayReader(c); PushbackReader push = new PushbackReader(charArrayReader); int bytes; try{ int size = source.length(); for(int i=0;i<size;i++){ bytes = push.read(); if(i == 1){ push.unread(bytes); } if(i == 3){ push.unread(bytes); } System.out.print((char) bytes); } }catch (IOException e){ e.printStackTrace(); }
輸出的結果是:12233
列印流
PrintWriter類
PrintWriter本質上是PrintStream的面向字元的版本,
建構函式如下:
PrintWriter(OutputStream outputStream) PrintWriter(OutputStream outputStream,boolean autoFlushingOn) PrintWriter(Writer outputStream) PrintWriter(Writer outputStream,boolean autoFlushingOn)
autoFlushingOn引數控制每次呼叫println()/printf()或format()方法時,是否自動重新整理輸出緩衝區,如果為true,就自動重新整理,否則不自動重新整理,沒有指定的建構函式不自動重新整理。
PrintWriter(File outputFile)
PrintWriter(File outputFile,String charSet)
PrintWriter(String outputFileName)
PrintWriter(String outputFileName,String charSet)
這幾個建構函式允許從File物件或根據檔案路徑建立PrintWriter物件,對於每種形式,都會自動建立檔案,所有之前存在的同名檔案都會被銷燬。一旦建立PrintWriter物件,就將所有輸出定向到指定檔案,可以通過charSet傳遞的名稱來指定字元編碼。
以下演示PrintWriter的簡單用法:
String source = "123456"; try(FileWriter f = new FileWriter("./io/src/chars/print.txt")){ PrintWriter printWriter = new PrintWriter(f); printWriter.print(source); }catch (Exception e){ e.printStackTrace(); }
隨機訪問檔案
RandomAccessFile類
RandomAccessFile是一個完全獨立的類,從Object派生而來,它不屬於InputStream/OutputStream也不屬於Reader/Writer。
它擁有和別的IO型別本質不同的行為,因為我們可以在一個檔案裡將指標向前和向後移動。
建構函式:
RandomAccessFile(File fileObj,String mode)
RandomAccessFile(String filename,String mode)
第一個建構函式fileObj指定了作為File物件開啟的檔案。
第二個建構函式檔名稱是通過filename傳遞的。
兩種方式都有mode指定訪問模式:
r 以只讀方式開啟 呼叫結果物件的任何write方法都將丟擲IOException
rw以讀寫方式開啟,如果該檔案不存在,則建立該檔案
rws以讀寫方式開啟,如果該檔案不存在,則建立該檔案,並且對檔案的內容或後設資料的每個更新都將同步寫入到底層的儲存裝置
rwd以讀寫方式開啟,如果該檔案不存在,則建立該檔案,並且對檔案的內容的每個更新都將同步寫入到底層的儲存裝置
rws和rwd顯然是不存在緩衝環節的,如果該檔案位於本地儲存裝置上,那麼可以保證呼叫對此檔案所做的所有更新均被寫入該裝置,這對確保在系統崩潰時不會丟失重要資訊特別有用,如果該檔案不在本地裝置上,則無法提供這樣的保證。
其中rwd模式可用於減少執行IO的運算元量,僅要求更新寫入裝置。
很重要的方法:
void seek(long newPos) //設定指標位置 long getFilePointer()//返回當前指標位置 long length()//返回檔案長度
控制檯I/O
Console類
Console類可訪問與當前 Java 虛擬機器關聯的基於字元的控制檯裝置,也就是說用於從控制檯讀取內容以及向控制檯寫入內容,該類主要是提供方便,因為大部分功能都可以通過System.in和System.out得到。
Console類沒有提供建構函式,可以通過呼叫System.console()方法獲取Console物件。
如果控制檯可用,就返回對控制檯的引用,否則返回null。並不是在所有情況下控制檯都是可用的,如果返回null,就不能進行控制檯I/O。(虛擬機器是否具有控制檯取決於底層平臺,還取決於呼叫虛擬機器的方式。如果虛擬機器從一個互動式命令列開始啟動,且沒有重定向標準輸入和輸出流,那麼其控制檯將存在,並且通常連線到鍵盤並從虛擬機器啟動的地方顯示。如果虛擬機器是自動啟動的(例如,由後臺作業排程程式啟動),那麼它通常沒有控制檯。)
方法列表:
方法 | 描述 |
void flush() | 重新整理控制檯,並強制立即寫入所有緩衝的輸出。 |
Console format(String fmtString,Object...args) | 使用fmtString指定的格式將args寫到控制檯 |
Console printf(String fmtString,Object...args) | 使用fmtString指定的格式將args寫到控制檯的便捷方法 |
Reader reader() | 返回對連線到控制檯的Reader物件的引用 |
String readLine() | 從控制檯讀取單行文,當使用者按下Enter鍵時,輸入結束。 |
String readLine(String fmtString,Object...args) | 提供一個格式化提示,然後從控制檯讀取單行文字。 |
char[] readPassword() | 從控制檯讀取密碼,禁用回顯。 |
char[] readPassword(String fmtString,Object...args) | 提供一個格式化提示,然後從控制檯讀取密碼,禁用回顯。 |
PrintWriter writer() | 返回對連線到控制檯的Writer物件的引用 |
注意:在IDE中啟動時,控制檯引用是null。
序列化
序列化的應用
序列化是將物件寫入位元組流的過程,可以實現將物件儲存到檔案中,並且可以通過反序列化過程來恢復這些物件。
實現遠端方法呼叫也需要序列化,遠端方法呼叫允許在一個機器上的java物件呼叫在另外一臺機器上的java物件的方法,可以將物件作為引數提供給遠端方法,傳送機器序列化物件並且進行傳遞。
假設將要序列化的物件具有指向其他物件的引用,而這些物件又具有更多物件的引用。
這些物件以及他們之間的關係形成了一張物件網,在這個物件網中,還可能存在環形引用,物件也可能包含指向他們自身的引用,在這些情形中,物件的序列化和反序列化都能夠正確的工作。
如果試圖序列化位於物件圖頂部的物件,那麼所有其他引用的物件都會被遞迴的定位和序列化,類似的,在反序列化的過程中,能夠正確的恢復所有這些物件以及對他們的引用。
物件的預設序列化機制寫入的內容是:物件的類,類簽名,以及非瞬態和非靜態欄位的值。其他物件的引用(瞬態和靜態欄位除外)也會導致寫入那些物件。可使用引用共享機制對單個物件的多個引用進行編碼,這樣即可將物件的圖形恢復為最初寫入它們時的形狀。
Serializable
只有實現了Serializable介面的類才能夠通過序列化功能進行儲存和恢復。
Serializable介面沒有定義成員,只是簡單的用於指示類可以被序列化,如果一個類是可序列化的,那麼這個類的所有子類也都是可序列化的。
儲存和載入序列化物件
ObjectOutputStream類
ObjectOutputStream類負責將物件寫入流中,只能將支援 java.io.Serializable 介面的物件寫入流中,每個 serializable 物件的類都被編碼,編碼內容包括類名和類簽名、物件的欄位值和陣列值,以及從初始物件中引用的其他所有物件的閉包。
建構函式:
ObjectOutputStream(OutputStream outStream)
引數outStream是將向其中寫入序列化物件的輸出流,關閉ObjectOutputStream物件會自動關閉OutStream指定的底層流。
void writerObject()方法用於將物件寫入流中。所有物件(包括 String 和陣列)都可以通過 writeObject 寫入。可將多個物件或基元寫入流中。必須使用與寫入物件時相同的型別和順序從相應 ObjectInputstream 中讀回物件。
ObjectInputStream類
ObjectInputStream 對以前使用 ObjectOutputStream 寫入的基本資料和物件進行反序列化。
ObjectOutputStream 和 ObjectInputStream 分別與 FileOutputStream 和 FileInputStream 一起使用時,可以為應用程式提供對物件圖形的持久儲存。ObjectInputStream 用於恢復那些以前序列化的物件。其他用途包括使用套接字流在主機之間傳遞物件,或者用於編組和解組遠端通訊系統中的實參和形參。
只有支援 java.io.Serializable 或 java.io.Externalizable 介面的物件才能從流讀取。
讀取物件類似於執行新物件的構造方法。為物件分配記憶體並將其初始化為零 (NULL)。為不可序列化類呼叫無引數構造方法,然後從以最接近 java.lang.object 的可序列化類開始和以物件的最特定類結束的流恢復可序列化類的欄位。
下面示例從檔案存取物件:
try(FileOutputStream file = new FileOutputStream("./io/src/serializables/1.txt")){ ObjectOutputStream oos = new ObjectOutputStream(file); oos.writeInt(12345); oos.writeObject("Today2"); oos.writeObject(new Date()); oos.close(); }catch (Exception e){ e.printStackTrace(); } try(FileInputStream file = new FileInputStream("./io/src/serializables/1.txt")){ ObjectInputStream ois = new ObjectInputStream(file); int i = ois.readInt(); String name = (String) ois.readObject(); Date date = (Date) ois.readObject(); ois.close(); System.out.println("i:"+i+",name:"+name+",date:"+date); }catch (Exception e){ e.printStackTrace(); }
序列化的控制
Externalizable介面
java對序列化和反序列化的功能都可以自動進行,然而在有些情況,我們可能需要控制這些過程,比如可能希望使用壓縮和加密技術,此時就需要Externalizable介面了。
在這些特殊情況下,可以通過實現Externalizable介面來代替實現Serializable介面,
它有兩個方法WriterExternal()和ReadExternal()必須被實現,這兩個方法會在序列化和反序列化的過程中被自動呼叫,以便執行一些特殊操作。
比如下面這個PersonExternalizableVo類,它的存取過程都必須手動實現:
public class PersonExternalizableVo implements Externalizable { private int id; private String name; @Override public void writeExternal(ObjectOutput out) throws IOException { out.writeInt(id); out.writeObject(name); } @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { id = in.readInt(); name = (String)in.readObject(); } //getter... //setter... //toString... }
瞬態和靜態
當我們隊序列化進行控制時,可能某個特定的子物件不想讓java的序列化機制自動儲存與恢復,如果子物件表示的是我們不希望將其序列化的敏感資訊(如密碼),通常就會面臨這種情況,有兩種辦法可以實現這種需求。
第一種是瞬態,關鍵字transient。
比如以下password欄位將不會被序列化:
private transient String password;
第二種是靜態,即static靜態屬性。
序列化機制在遇到瞬態和靜態屬性時候會自動忽略。
注意:實現Externalizable的物件在預設情況下是不會儲存任何欄位的,所以transient欄位只能用再實現了Serializable介面的類中。
File類
檔案操作
儘管java.io定義的大多數類都是操作流,但是File類卻不是,File類直接處理檔案和檔案系統,也就是說,File類沒有指定如何從檔案檢索資訊以及如何向檔案中儲存資訊。
精確的說,File類是檔案和目錄路徑名的抽象表示形式。
File類的例項是不可變的;也就是說,一旦建立,File 物件表示的抽象路徑名將永不改變。
構造方法:
File(String directoryPath)
File(String directoryPath,String filename)
File(File dirObj,String filename)
File(URI uriObj)
其中,directoryPath是檔案的路徑名,filename是檔案或者子目錄的名稱,dirObj是指定目錄的File物件,uriObj是描述檔案的URI物件。
下面的示例建立了3個File物件:
File f1 = new File("/"); File f2 = new File("/","1.txt") File f3 = new File(f1,"1.txt")
一般的方法如下:
File file = new File("./io/src/files/1.txt"); System.out.println("名稱:"+file.getName()); System.out.println("路徑名稱:"+file.getPath()); System.out.println("絕對路徑名稱:"+file.getAbsolutePath()); System.out.println("父目錄的路徑名稱:"+file.getParent()); System.out.println("是否存在:"+file.exists()); System.out.println("是否可寫:"+file.canWrite()); System.out.println("是否可讀:"+file.canRead()); System.out.println("是否隱藏:"+file.isHidden()); System.out.println("是否是目錄:"+file.isDirectory()); System.out.println("是否是檔案:"+file.isFile()); System.out.println("是否是絕對路徑:"+file.isAbsolute()); System.out.println("最後一次修改時間:"+file.lastModified()); System.out.println("檔案大小:"+file.length()+"bytes");
當File表示的路徑是一個目錄時,可以呼叫list()方法獲取目錄下的路徑列表
如:
File file1 = new File(file.getParent()); String[] s = file1.list(); for(String str : s){ System.out.println(str); }
還有一些有用的方法:
boolean renameTo(File newName);//重新命名 boolean mkdir();//建立該名稱 boolean mkdirs();//遞迴建立該路徑 boolean delete();刪除檔案,如果目錄為空的話可以刪除目錄。
檔案篩選
list()方法還有一個使用形式
String[] list(FilenameFilter fiter);
在這種形式中,fiter是一個實現了FilenameFilter介面的物件,該物件中實現了accept(File dir, String name)方法,該方法返回boolean值,會針對每個檔案呼叫一次,返回true代表選擇,返回false代表過濾掉。
使用示例如下 只返回.txt字尾的檔案:
public class OnlyExt implements FilenameFilter { protected String ext; public OnlyExt(String str){ ext = "."+str; } @Override public boolean accept(File dir, String name) { return name.endsWith(ext); } }
FilenameFilter filenameFilter = new OnlyExt("txt"); String[] strs = file1.list(filenameFilter); for(String t : strs){ System.out.println(t); }
檔案列表物件
list()方法還有一種形式:
File[] listFiles()
File[] listFiles(FilenameFiter fiter)
File[] listFiles(FileFiter fiter)
它返回的是File物件陣列。
第三個建構函式的實現方法是accept(File path),也是對每個檔案呼叫一次
以下改寫OnlyExt類,使之篩選出同樣的檔案:
public class FilterExt implements FileFilter { protected String ext; public FilterExt(String str){ ext = "."+str; } @Override public boolean accept(File pathname) { String name = pathname.getName(); return name.endsWith(ext); } }
FileFilter fileFilter = new FilterExt("txt"); File[] files = file1.listFiles(fileFilter); for(File f :files){ System.out.println(f.getName()); }
寫在最後
原始碼分享:https://gitee.com/zhao-baolin/java_learning/tree/master/io
io只是一部分,後面還會整理新io的知識,系統的知識儲備是非常非常非常重要的事情。
學習這條路,永無止境。
你好,再見。