File類是java.io包下表明和平臺無關的文件和目錄,File不能訪問文件內容自己。java
File類基本操做:程序員
System.out.println("判斷文件是否存在:"+file.exists());//判斷文件是否存在,返回Boolean值 System.out.println("建立文件夾:"+file.mkdir());//建立文件夾,只能建立一層,返回Boolean值 System.out.println("文件目錄:"+file.getParent());//返回文件最後一級子目錄 System.out.println("建立文件夾:"+file2.mkdirs());//建立文件夾,建立多層,返回Boolean值 System.out.println("建立新文件:"+file3.createNewFile());//建立新文件,此處需處理異常,返回Boolean值 System.out.println("刪除文件:"+file3.delete());//刪除文件,返回Boolean值 System.out.println("文件更名:"+file.renameTo(file4));//文件更名,傳入另外一個文件 System.out.println("文件名:"+file.getName());//返回名 System.out.println("文件路徑:"+file.getPath());//返回文件路徑 System.out.println("絕對路徑:"+file.getAbsolutePath());//返回絕對路徑 System.out.println("文件夾:"+file.isDirectory());//返回是否文件夾 System.out.println("是否文件:"+file.isFile());//返回是否文件 System.out.println("是否文件夾:"+file.isDirectory());//返回是否文件夾 System.out.println("是否絕對路徑:"+file.isAbsolute());//返回是否絕對路徑 System.out.println("文件長度:"+file.length());//返回文件長度 System.out.println("最後修改時間:"+file.lastModified());//返回最後修改時間
// 以當前路徑來建立一個File對象 File file = new File("."); // 直接獲取文件名,輸出一點 System.out.println(file.getName()); // 獲取相對路徑的父路徑可能出錯,下面代碼輸出null System.out.println(file.getParent()); // 獲取絕對路徑 System.out.println(file.getAbsoluteFile()); // 獲取上一級路徑 System.out.println(file.getAbsoluteFile().getParent()); // 在當前路徑下建立一個臨時文件 File tmpFile = File.createTempFile("aaa", ".txt", file); // 指定當JVM退出時刪除該文件 tmpFile.deleteOnExit(); // 以系統當前時間做爲新文件名來建立新文件 File newFile = new File(System.currentTimeMillis() + ""); System.out.println("newFile對象是否存在:" + newFile.exists()); // 以指定newFile對象來建立一個文件 newFile.createNewFile(); // 以newFile對象來建立一個目錄,由於newFile已經存在, // 因此下面方法返回false,即沒法建立該目錄 newFile.mkdir(); // 使用list()方法來列出當前路徑下的全部文件和路徑 String[] fileList = file.list(); System.out.println("====當前路徑下全部文件和路徑以下===="); for (String fileName : fileList) { System.out.println(fileName); } // listRoots()靜態方法列出全部的磁盤根路徑。 File[] roots = File.listRoots(); System.out.println("====系統全部根路徑以下===="); for (File root : roots) { System.out.println(root); }
文件過濾器:算法
File類的list()方法中能夠接受一個FilenameFilter參數,經過該參數能夠只列出符合條件的文件。FilenameFilter接口裏包含了一個accept(**)方法,該方法將一次對指定File的全部子目錄或者文件進行迭代,若是該方法返回true,則list()方法會列出該子目錄或者文件。數組
File file = new File("."); // 使用Lambda表達式(目標類型爲FilenameFilter)實現文件過濾器。 // 若是文件名以.java結尾,或者文件對應一個路徑,返回true String[] nameList = file.list((dir, name) -> name.endsWith(".java") || new File(name).isDirectory()); for(String name : nameList) { System.out.println(name); }
FilenameFilter接口內只有一個抽象方法,所以改接口也是一個函數式接口,可以使用Lambda表達式建立實現該接口的對象。安全
Java的IO流是實現輸入輸出的基礎,在Java中把不一樣的輸入輸出源抽象表述爲流,經過流的方式容許Java使用相同的方式來訪問不一樣的輸入輸出源。網絡
stream是從起源(source)到接收(sink)的有序數據。併發
流向:app
輸入流 讀取數據dom
輸出流 寫出數據異步
數據類型:
字節流
一個字節佔8位, 以一個字節爲單位讀數據
八大數據類型所佔字節數:
byte(1), short(2), int(4), long(8),float(4), double(8),boolean(1),char(2)
字節輸入流 讀取數據 InputStream
字節輸出流 寫出數據 OutputStream
字符流
一個字符佔兩個字節, 以一個字符爲一個單位
字符輸入流 讀取數據 Reader
字符輸出流 寫出數據 Writer
字節流的基本抽象類
InputStream OutputStream
字符流的基本抽象類
Reader Writer
功能:
節點流: 只有一個根管道套在文件上進行傳輸
處理流: 將節點流處理一下, 加強管道的功能, 至關於在管道上套一層
InputStream和Reader是全部輸入流的抽象基類,自己並不能建立實例來執行輸入,但它們將成爲全部輸入流的模板,他們的方法是有輸入流均可以使用的方法。
這兩個基類的功能基本是同樣的。
他們分別有一個用於讀取文件的輸入流:FileInputStream和FileReader,他們都是節點流,會直接和指定文件關聯。
使用FileInputStream讀取文件自身:
public static void main(String[] args) throws IOException { // 建立字節輸入流 FileInputStream fis = new FileInputStream( "D:\\idea_project\\mybootbill\\src\\main\\java\\com\\jiangwenzhang\\mybootbill\\learn\\FileIO\\FileInputStreamTest.java"); // 建立一個長度爲1024的「竹筒」 byte[] bbuf = new byte[1024]; // 用於保存實際讀取的字節數 int hasRead = 0; // 使用循環來重複「取水」過程 while ((hasRead = fis.read(bbuf)) > 0 ) { // 取出「竹筒」中水滴(字節),將字節數組轉換成字符串輸入! System.out.print(new String(bbuf , 0 , hasRead )); } // 關閉文件輸入流,放在finally塊裏更安全 fis.close(); }
須要注意,若是bbuf字節數組的長度較小,遇到中文時可能會亂碼,由於若是文件自己保存時採用GBK編碼方式,在這種方式下,每一箇中文字符佔兩個字節,若是read方法讀取時只讀取到了半個中文就會亂碼。
程序裏打開的文件IO資源不屬於內存裏的資源,垃圾回收機制沒法回收該資源,因此要顯示關閉文件IO資源。
使用FileReader:
public static void main(String[] args) { try( // 建立字符輸入流 FileReader fr = new FileReader("D:\\idea_project\\mybootbill\\src\\main\\java\\com\\jiangwenzhang\\mybootbill\\learn\\FileIO\\FileReaderTest.java")) { // 建立一個長度爲32的「竹筒」 char[] cbuf = new char[32]; // 用於保存實際讀取的字符數 int hasRead = 0; // 使用循環來重複「取水」過程 while ((hasRead = fr.read(cbuf)) > 0 ) { // 取出「竹筒」中水滴(字符),將字符數組轉換成字符串輸入! System.out.print(new String(cbuf , 0 , hasRead)); } } catch (IOException ex) { ex.printStackTrace(); } }
InputStream和Reader還支持以下幾個方法來移動記錄指針:
方法 | 釋義 |
---|---|
void mark(int readlimit) |
Marks the current position in this input stream. |
boolean markSupported() |
Tests if this input stream supports the mark and reset methods. |
void reset() |
Repositions this stream to the position at the time the mark method was last called on this input stream. |
long skip(long n) |
Skips over and discards n bytes of data from this input stream. |
OuputStream和Writer:
public static void main(String[] args) { try( // 建立字節輸入流 FileInputStream fis = new FileInputStream( "D:\\idea_project\\mybootbill\\src\\main\\java\\com\\jiangwenzhang\\mybootbill\\learn\\FileIO\\FileOutputStreamTest.java"); // 建立字節輸出流 FileOutputStream fos = new FileOutputStream("newFile.txt"))//文件在項目根 { byte[] bbuf = new byte[32]; int hasRead = 0; // 循環從輸入流中取出數據 while ((hasRead = fis.read(bbuf)) > 0 ) { // 每讀取一次,即寫入文件輸出流,讀了多少,就寫多少。 fos.write(bbuf , 0 , hasRead); } } catch (IOException ioe) { ioe.printStackTrace(); } }
注意:使用IO流執行輸出時,不要忘記關閉輸出流,關閉輸出流處能夠保證流的物流自願被回收,還能夠將輸出流緩衝區的數據flush到物理節點裏。
若是但願直接輸出字符串內容,使用Writer更好:
public static void main(String[] args) { try( FileWriter fw = new FileWriter("poem.txt")) { fw.write("錦瑟 - 李商隱\r\n"); fw.write("錦瑟無故五十弦,一弦一柱思華年。\r\n"); fw.write("莊生曉夢迷蝴蝶,望帝春心託杜鵑。\r\n"); fw.write("滄海月明珠有淚,藍田日暖玉生煙。\r\n"); fw.write("此情可待成追憶,只是當時已惘然。\r\n"); } catch (IOException ioe) { ioe.printStackTrace(); } }
使用處理流的典型思路是,使用處理流來包裝節點流,程序經過處理流來執行輸入輸入,讓節點流與底層設備、文件交互。
使用PrintStream處理流包裝OutStream:
public static void main(String[] args) { try( FileOutputStream fos = new FileOutputStream("test.txt"); PrintStream ps = new PrintStream(fos)) { // 使用PrintStream執行輸出 ps.println("普通字符串"); // 直接使用PrintStream輸出對象 ps.println(new PrintStreamTest()); } catch (IOException ioe) { ioe.printStackTrace(); } }
一般若是要輸出文本內容,都應將輸出流包裝成PrintStream後輸出。
注意,在使用處理流包裝了底層節點以後,關閉輸入輸出流資源是,只要關閉最上層的處理流便可,關閉最上層處理流時,系統會自動關閉被該處理流包裝的節點流。
Java輸入輸出流體系經常使用流分類:
流分類 | 使用分類 | 字節輸入流 | 字節輸出流 | 字符輸入流 | 字符輸出流 |
抽象基類 | InputStream | OutputStream |
Reader | Writer | |
節點流 | 訪問文件 | FileInputStream | FileOutStream | FileReader | FileWriter |
訪問數值 | ByteArrayInputStream | ByteArrayOutStream | CharArrayReader | CharArrayWriter | |
訪問管道 | PipedInputStream | PipedOutStream | PipedReader | PipedWriter | |
訪問字符串 | StringReader | StringWriter | |||
處理流 | 緩衝流 | BufferedInputStream | BufferedOutputStream | BufferedReader | BufferedWriter |
轉換流 | InputStreamReader | OutputStreamWriter | |||
對象流 | ObjectInputStream | ObjectOutputStream | |||
抽象基類(過濾) | FilterInputStream | FilterOutputStream | FilterReader | FilterWriter | |
打印流 | PrintStream | PrintWriter | |||
推回輸入流 | PushbackInputStream | PushbackReader | |||
特殊流 | DataInputStream | DataOutputStream | |
一般來講,字節流的功能比字符流強大,由於計算機全部數據都是二進制的,字節流能夠處理全部的二進制文件,可是須要使用合適的方式把這些字節轉換成字符,一般:若是進行輸入輸出的內容是文本內容,則應該考慮使用字符流,若是是二進制內容,則使用字節流。
使用字符串做爲物理節點:
public static void main(String[] args) { String src = "從明天起,作一個幸福的人\n" + "餵馬,劈柴,周遊世界\n" + "從明天起,關心糧食和蔬菜\n" + "我有一所房子,面朝大海,春暖花開\n" + "從明天起,和每個親人通訊\n" + "告訴他們個人幸福\n"; char[] buffer = new char[32]; int hasRead = 0; try( StringReader sr = new StringReader(src)) { // 採用循環讀取的訪問讀取字符串 while((hasRead = sr.read(buffer)) > 0) { System.out.print(new String(buffer ,0 , hasRead)); } } catch (IOException ioe) { ioe.printStackTrace(); } try( // 建立StringWriter時,實際上以一個StringBuffer做爲輸出節點 // 下面指定的20就是StringBuffer的初始長度 StringWriter sw = new StringWriter()) { // 調用StringWriter的方法執行輸出 sw.write("有一個美麗的新世界,\n"); sw.write("她在遠方等我,\n"); sw.write("哪裏有天真的孩子,\n"); sw.write("還有姑娘的酒窩\n"); System.out.println("----下面是sw的字符串節點裏的內容----"); // 使用toString()方法返回StringWriter的字符串節點的內容 System.out.println(sw.toString()); } catch (IOException ex) { ex.printStackTrace(); } }
和前面使用FileReader和FileWriter類似,只是建立對象時傳入的是字符串節點,用於String是不可變得字符串對象,因此StringWriter使用StringBuffer做爲輸出節點。
轉換流
InputStreamReader將字節輸入流轉換成字符輸入流,OutputStreamWriter將字節輸出流轉換成字符輸出流。
以鍵盤輸入爲例,java使用System.in表明標準輸入也就是鍵盤輸入,但這個標準輸入流是InputStream類的實例,可使用InputStreamReader將其轉換成字符輸入流,再講普通的Reader再次包裝成BufferedReader:
public static void main(String[] args) { try( // 將Sytem.in對象轉換成Reader對象 InputStreamReader reader = new InputStreamReader(System.in); // 將普通Reader包裝成BufferedReader BufferedReader br = new BufferedReader(reader)) { String line = null; // 採用循環方式來一行一行的讀取 while ((line = br.readLine()) != null) { // 若是讀取的字符串爲"exit",程序退出 if (line.equals("exit")) { System.exit(1); } // 打印讀取的內容 System.out.println("輸入內容爲:" + line); } } catch (IOException ioe) { ioe.printStackTrace(); } }
推回輸入流:
PushbackInputStream和PushbackReader
推回輸入流都帶有一個推回緩衝區,當程序調用推回輸入流的unread()方法,系統將會把指定數組內容推回到該緩衝區,而推回輸入流每次調用read()方法老是先從推回緩衝區讀取,只有徹底讀取了推回緩衝區的內容後,但尚未裝滿read()所需的數組時纔會從原輸入流讀取。
public static void main(String[] args) { try( // 建立一個PushbackReader對象,指定推回緩衝區的長度爲64 PushbackReader pr = new PushbackReader(new FileReader( "D:\\idea_project\\mybootbill\\src\\main\\java\\com\\jiangwenzhang\\mybootbill\\learn\\FileIO\\PushbackTest.java") , 64)) { char[] buf = new char[32]; // 用以保存上次讀取的字符串內容 String lastContent = ""; int hasRead = 0; // 循環讀取文件內容 while ((hasRead = pr.read(buf)) > 0) { // 將讀取的內容轉換成字符串 String content = new String(buf , 0 , hasRead); int targetIndex = 0; // 將上次讀取的字符串和本次讀取的字符串拼起來, // 查看是否包含目標字符串, 若是包含目標字符串 if ((targetIndex = (lastContent + content) .indexOf("new PushbackReader")) > 0) { // 將本次內容和上次內容一塊兒推回緩衝區 pr.unread((lastContent + content).toCharArray());// 從新定義一個長度爲targetIndex的char數組 if(targetIndex > 32) { buf = new char[targetIndex]; } // 再次讀取指定長度的內容(就是目標字符串以前的內容) pr.read(buf , 0 , targetIndex); // 打印讀取的內容 System.out.print(new String(buf , 0 ,targetIndex)); System.exit(0); } else { // 打印上次讀取的內容 System.out.print(lastContent); // 將本次內容設爲上次讀取的內容 lastContent = content; } } } catch (IOException ioe) { ioe.printStackTrace(); } }
粗體下劃線部分實現了將制定內容推回到推回緩衝區,因而程序再次調用read()方法時,實際上只是讀取了推回緩衝區的部份內容,從而實現了只打印目標字符串前面內容的功能。
通常狀況下,System.in表明的是鍵盤、System.out是表明的控制檯(顯示器)。當程序經過System.in來獲取輸入的時候,默認狀況下,是從鍵盤讀取輸入;當程序試圖經過System.out執行輸出時,程序老是輸出到顯示器。若是咱們想對這樣的狀況作一個改變,例如獲取輸入時,不是來自鍵盤,而是來自文件或其餘的位置;輸出的時候,不是輸出到顯示器上顯示,而是輸出到文件或其餘位置,怎麼實現?因而,java重定標準輸入輸出應運而生。
static void setErr(PrintStream err)、重定向標準錯誤輸出流
static void setIn(InputStream in)、重定向標準輸入流
static void setOut(PrintStream out) 重定向標準輸出流
public static void main(String[] args) { try( // 一次性建立PrintStream輸出流 PrintStream ps = new PrintStream(new FileOutputStream("out.txt"))) { // 將標準輸出重定向到ps輸出流 System.setOut(ps); // 向標準輸出輸出一個字符串 System.out.println("普通字符串"); // 向標準輸出輸出一個對象 System.out.println(new RedirectOut()); } catch (IOException ex) { ex.printStackTrace(); } }
public static void main(String[] args) { try( FileInputStream fis = new FileInputStream("RedirectIn.java")) { // 將標準輸入重定向到fis輸入流 System.setIn(fis); // 使用System.in建立Scanner對象,用於獲取標準輸入 Scanner sc = new Scanner(System.in); // 增長下面一行將只把回車做爲分隔符 sc.useDelimiter("\n"); // 判斷是否還有下一個輸入項 while(sc.hasNext()) { // 輸出輸入項 System.out.println("鍵盤輸入的內容是:" + sc.next()); } } catch (IOException ex) { ex.printStackTrace(); } }
讀取其餘進程的輸出信息:
public static void main(String[] args) throws IOException { // 運行javac命令,返回運行該命令的子進程 Process p = Runtime.getRuntime().exec("javac"); try( // 以p進程的錯誤流建立BufferedReader對象 // 這個錯誤流對本程序是輸入流,對p進程則是輸出流 BufferedReader br = new BufferedReader(new InputStreamReader(p.getErrorStream()))) { String buff = null; // 採起循環方式來讀取p進程的錯誤輸出 while((buff = br.readLine()) != null) { System.out.println(buff); } } }
在Java程序中啓動Java虛擬機運行另外一個Java程序,並像另外一個Java程序中輸入數據:
package com.jiangwenzhang.mybootbill.learn.FileIO; import java.io.*; import java.util.*; public class WriteToProcess { public static void main(String[] args) throws IOException { // 運行java ReadStandard命令,返回運行該命令的子進程 Process p = Runtime.getRuntime().exec("java ReadStandard"); try( // 以p進程的輸出流建立PrintStream對象 // 這個輸出流對本程序是輸出流,對p進程則是輸入流 PrintStream ps = new PrintStream(p.getOutputStream())) { // 向ReadStandard程序寫入內容,這些內容將被ReadStandard讀取 ps.println("普通字符串"); ps.println(new WriteToProcess()); } } } // 定義一個ReadStandard類,該類能夠接受標準輸入, // 並將標準輸入寫入out.txt文件。 class ReadStandard { public static void main(String[] args) { try( // 使用System.in建立Scanner對象,用於獲取標準輸入 Scanner sc = new Scanner(System.in); PrintStream ps = new PrintStream( new FileOutputStream("out.txt"))) { // 增長下面一行將只把回車做爲分隔符 sc.useDelimiter("\n"); // 判斷是否還有下一個輸入項 while(sc.hasNext()) { // 輸出輸入項 ps.println("鍵盤輸入的內容是:" + sc.next()); } } catch(IOException ioe) { ioe.printStackTrace(); } } }
隨機流(RandomAccessFile)不屬於IO流,支持對文件的讀取和寫入隨機訪問。
RandomAccessFile容許自由定位文件記錄指針,RandomAccessFile能夠不從開始的地方開始輸出,由於RandomAccessFile能夠向已存在的文件後追加內容。
RandomAccessFile最大的侷限就是隻能讀寫文件,不能讀寫其餘IO節點。
RandomAccessFile對象包含了一個記錄指針,用以標識當前讀寫位置。RandomAccessFile能夠自由移動該記錄指針。
使用RandomAccessFile來訪問指定的中間部分數據:
public static void main(String[] args) { try( RandomAccessFile raf = new RandomAccessFile( "D:\\idea_project\\mybootbill\\src\\main\\java\\com\\jiangwenzhang\\mybootbill\\learn\\FileIO\\RandomAccessFileTest.java" , "r")) { // 獲取RandomAccessFile對象文件指針的位置,初始位置是0 System.out.println("RandomAccessFile的文件指針的初始位置:" + raf.getFilePointer()); // 移動raf的文件記錄指針的位置 raf.seek(300); byte[] bbuf = new byte[1024]; // 用於保存實際讀取的字節數 int hasRead = 0; // 使用循環來重複「取水」過程 while ((hasRead = raf.read(bbuf)) > 0 ) { // 取出「竹筒」中水滴(字節),將字節數組轉換成字符串輸入! System.out.print(new String(bbuf , 0 , hasRead )); } } catch (IOException ex) { ex.printStackTrace(); } }
該方法以只讀方式打開文件,從300字節處開始讀取。
像文件中追加內容,爲了追加內容,程序應該先將記錄指針移動到文件最後,而後項文件中輸出內容。
public static void main(String[] args) { try( //以讀、寫方式打開一個RandomAccessFile對象 RandomAccessFile raf = new RandomAccessFile("out.txt" , "rw")) { //將記錄指針移動到out.txt文件的最後 raf.seek(raf.length()); raf.write("追加的內容!\r\n".getBytes()); } catch (IOException ex) { ex.printStackTrace(); } }
指定文件,指定位置插入內容:
package com.jiangwenzhang.mybootbill.learn.FileIO; import java.io.*; public class InsertContent { public static void insert(String fileName , long pos , String insertContent) throws IOException { File tmp = File.createTempFile("tmp" , null); tmp.deleteOnExit(); try( RandomAccessFile raf = new RandomAccessFile(fileName , "rw"); // 使用臨時文件來保存插入點後的數據 FileOutputStream tmpOut = new FileOutputStream(tmp); FileInputStream tmpIn = new FileInputStream(tmp)) { raf.seek(pos); // ------下面代碼將插入點後的內容讀入臨時文件中保存------ byte[] bbuf = new byte[64]; // 用於保存實際讀取的字節數 int hasRead = 0; // 使用循環方式讀取插入點後的數據 while ((hasRead = raf.read(bbuf)) > 0 ) { // 將讀取的數據寫入臨時文件 tmpOut.write(bbuf , 0 , hasRead); } // ----------下面代碼插入內容---------- // 把文件記錄指針從新定位到pos位置 raf.seek(pos); // 追加須要插入的內容 raf.write(insertContent.getBytes()); // 追加臨時文件中的內容 while ((hasRead = tmpIn.read(bbuf)) > 0 ) { raf.write(bbuf , 0 , hasRead); } } } public static void main(String[] args) throws IOException { insert("InsertContent.java" , 45 , "插入的內容\r\n"); } }
對象序列化的目標是將對象保存到磁盤中,或容許在網絡中直接傳輸對象。全部分佈式應用經常須要跨平臺,跨網絡,所以要求全部傳的參數、返回值都必須實現序列化。
序列化:把Java對象轉換爲字節序列的過程。
反序列化:把字節序列恢復爲Java對象的過程。
對象的序列化是指將一個Java對象寫入IO流中,對象的反序列化則是是指從IO流中恢復該Java對象。
Java9加強了對象序列化機制,他容許對讀入的序列化數據進行過濾,這種過濾能夠在反序列化以前對數據執行校驗,從而提升安全性和健壯性。
讓某個類可序列化,必須實現以下兩個接口之一:
Serializable
Externailzable
Java的不少類已經實現了Serializable,這是一個標記接口,無需實現任何方法,只是代表該類的實例是能夠序列化的。
全部可能在網絡上傳輸的對象的類都應該是可序列化的。
public class Person implements java.io.Serializable { private String name; private int age; // 注意此處沒有提供無參數的構造器! public Person(String name , int age) { System.out.println("有參數的構造器"); this.name = name; this.age = age; } // 省略name與age的setter和getter方法 // name的setter和getter方法 public void setName(String name) { this.name = name; } public String getName() { return this.name; } // age的setter和getter方法 public void setAge(int age) { this.age = age; } public int getAge() { return this.age; } }
public class WriteObject { public static void main(String[] args) { try( // 建立一個ObjectOutputStream輸出流 ObjectOutputStream oos = new ObjectOutputStream( new FileOutputStream("object.txt"))) { Person per = new Person("孫悟空", 500); // 將per對象寫入輸出流 oos.writeObject(per); } catch (IOException ex) { ex.printStackTrace(); } } }
public class ReadObject { public static void main(String[] args) { try( // 建立一個ObjectInputStream輸入流 ObjectInputStream ois = new ObjectInputStream( new FileInputStream("object.txt"))) { // 從輸入流中讀取一個Java對象,並將其強制類型轉換爲Person類 Person p = (Person)ois.readObject(); System.out.println("名字爲:" + p.getName() + "\n年齡爲:" + p.getAge()); } catch (Exception ex) { ex.printStackTrace(); } } }
注意:反序列化讀取的僅僅是Java對象的數據,而不是Java類,所以採用反序列化恢復Java對象必須提供Java對象所屬類的class文件。
反序列化機制無需經過構造器來初始化Java對象。
若是使用序列化機制向文件中寫入了多個Java對象,使用反序列化機制恢復對象必須按實際寫入的順序讀取。
若是某個類的成員變量的類型不是基本類型或String而是引用類型,那麼這個引用類必須是可序列化的,不然擁有該類型成員變量的的類也是不可序列化的。
public class Teacher implements java.io.Serializable { private String name; private Person student; public Teacher(String name , Person student) { this.name = name; this.student = student; } // 此處省略了name和student的setter和getter方法 // name的setter和getter方法 public void setName(String name) { this.name = name; } public String getName() { return this.name; } // student的setter和getter方法 public void setStudent(Person student) { this.student = student; } public Person getStudent() { return this.student; } }
特殊狀況:
Person per = new Person("孫悟空", 500); Teacher t1 = new Teacher("唐僧" , per); Teacher t2 = new Teacher("菩提祖師" , per);
兩種對象互相引用,這樣若是先序列化t1,系統將t1對象引用的Person對象一塊兒序列化,在序列化t2,程序將同樣會序列化該t2對象,而且再次序列化Person對象,若是程序在顯示序列化per對象,系統又一次序列化person對象。這個過程向輸出流中輸出三個Person對象。
這樣程序從輸入流中反序列化這些對象,將會獲得三個person對象,從而引發t1和t2所引用的Person對象不是同一個對象。
Java序列化機制採用了一種特殊的序列化算法:
一、全部保存到磁盤中的對象都有一個序列化編號。
二、當程序試圖序列化一個對象時,會先檢查該對象是否已經被序列化過,只有該對象從未(在本次虛擬機中)被序列化,系統纔會將該對象轉換成字節序列並輸出。
三、若是對象已經被序列化,程序將直接輸出一個序列化編號,而不是從新序列化。
經過以上算法,當第二次第三次序列化,程序不會再次將Person對象轉換成字節序列並輸出,而是僅僅輸出一個序列化編號。
當屢次調用wirteObject()方法輸出同一個對象時,只有第一次調用wirteObject()方法纔會將該對象轉換成字節序列並輸出。
public static void main(String[] args) { try( // 建立一個ObjectOutputStream輸出流 ObjectOutputStream oos = new ObjectOutputStream( new FileOutputStream("teacher.txt"))) { Person per = new Person("孫悟空", 500); Teacher t1 = new Teacher("唐僧" , per); Teacher t2 = new Teacher("菩提祖師" , per); // 依次將四個對象寫入輸出流 oos.writeObject(t1); oos.writeObject(t2); oos.writeObject(per); oos.writeObject(t2); } catch (IOException ex) { ex.printStackTrace(); } }
public static void main(String[] args) { try( // 建立一個ObjectInputStream輸出流 ObjectInputStream ois = new ObjectInputStream( new FileInputStream("teacher.txt"))) { // 依次讀取ObjectInputStream輸入流中的四個對象 Teacher t1 = (Teacher)ois.readObject(); Teacher t2 = (Teacher)ois.readObject(); Person p = (Person)ois.readObject(); Teacher t3 = (Teacher)ois.readObject(); // 輸出true System.out.println("t1的student引用和p是否相同:" + (t1.getStudent() == p)); // 輸出true System.out.println("t2的student引用和p是否相同:" + (t2.getStudent() == p)); // 輸出true System.out.println("t2和t3是不是同一個對象:" + (t2 == t3)); } catch (Exception ex) { ex.printStackTrace(); } }
上面代碼依次讀取了序列化文件中的4個Java對象,經過比較能夠看出t2和t3是同一個對象。
注意:
因爲Java序列化機制使然,屢次序列化同一個Java對象時,只有第一次序列化該對象纔會把該Java對象轉換成字節序列並輸出,所以程序序列化一個可變對象以後,後面改變了對象的實例變量值,再次序列化也只是輸出前面的序列化編號,改變的實例變量值也不會輸出。
public static void main(String[] args) { try( // 建立一個ObjectOutputStream輸入流 ObjectOutputStream oos = new ObjectOutputStream( new FileOutputStream("mutable.txt")); // 建立一個ObjectInputStream輸入流 ObjectInputStream ois = new ObjectInputStream( new FileInputStream("mutable.txt"))) { Person per = new Person("孫悟空", 500); // 系統會per對象轉換字節序列並輸出 oos.writeObject(per); // 改變per對象的name實例變量 per.setName("豬八戒"); // 系統只是輸出序列化編號,因此改變後的name不會被序列化 oos.writeObject(per); Person p1 = (Person)ois.readObject(); //① Person p2 = (Person)ois.readObject(); //② // 下面輸出true,即反序列化後p1等於p2 System.out.println(p1 == p2); // 下面依然看到輸出"孫悟空",即改變後的實例變量沒有被序列化 System.out.println(p2.getName()); } catch (Exception ex) { ex.printStackTrace(); } }
Java9增長的過濾功能:
Java9爲ObjectInputStream增長了setObjectInputFilter()和getObjectInputFilter()兩個方法,其中第一個方法用於爲對象輸入流設置過濾器。當程序經過ObjectInputStream反序列化時,過濾器的checkInput()方法會被自動激發,用於檢查序列化數據是否有效。
public static void main(String[] args) { try( // 建立一個ObjectInputStream輸入流 ObjectInputStream ois = new ObjectInputStream( new FileInputStream("object.txt"))) { ois.setObjectInputFilter((info) -> { System.out.println("===執行數據過濾==="); ObjectInputFilter serialFilter = ObjectInputFilter.Config.getSerialFilter(); if (serialFilter != null) { // 首先使用ObjectInputFilter執行默認的檢查 ObjectInputFilter.Status status = serialFilter.checkInput(info); // 若是默認檢查的結果不是Status.UNDECIDED if (status != ObjectInputFilter.Status.UNDECIDED) { // 直接返回檢查結果 return status; } } // 若是要恢復的對象不是1個 if(info.references() != 1) { // 不容許恢復對象 return ObjectInputFilter.Status.REJECTED; } if (info.serialClass() != null && // 若是恢復的不是Person類 info.serialClass() != Person.class) { // 不容許恢復對象 return ObjectInputFilter.Status.REJECTED; } return ObjectInputFilter.Status.UNDECIDED; }); // 從輸入流中讀取一個Java對象,並將其強制類型轉換爲Person類 Person p = (Person)ois.readObject(); System.out.println("名字爲:" + p.getName() + "\n年齡爲:" + p.getAge()); } catch (Exception ex) { ex.printStackTrace(); } }
自定義序列化
經過在實例變量前使用transient關鍵字修飾,能夠指定Java序列化時無需理會該實例變量。
public class Person implements java.io.Serializable { private String name; private transient int age; // 注意此處沒有提供無參數的構造器! public Person(String name , int age) { System.out.println("有參數的構造器"); this.name = name; this.age = age; } // 省略name與age的setter和getter方法 // name的setter和getter方法 public void setName(String name) { this.name = name; } public String getName() { return this.name; } // age的setter和getter方法 public void setAge(int age) { this.age = age; } public int getAge() { return this.age; } }
public class TransientTest { public static void main(String[] args) { try( // 建立一個ObjectOutputStream輸出流 ObjectOutputStream oos = new ObjectOutputStream( new FileOutputStream("transient.txt")); // 建立一個ObjectInputStream輸入流 ObjectInputStream ois = new ObjectInputStream( new FileInputStream("transient.txt"))) { Person per = new Person("孫悟空", 500); // 系統會per對象轉換字節序列並輸出 oos.writeObject(per); Person p = (Person)ois.readObject(); System.out.println(p.getAge()); } catch (Exception ex) { ex.printStackTrace(); } } }
程序將輸出0
被transient修飾的的實例變量被徹底隔離在序列化機制以外,這致使在反序列化恢復Java對象時沒法取得該實例變量值。
Java提供了一種自定義序列化機制:
public class Person implements java.io.Serializable { private String name; private int age; // 注意此處沒有提供無參數的構造器! public Person(String name , int age) { System.out.println("有參數的構造器"); this.name = name; this.age = age; } // 省略name與age的setter和getter方法 // name的setter和getter方法 public void setName(String name) { this.name = name; } public String getName() { return this.name; } // age的setter和getter方法 public void setAge(int age) { this.age = age; } public int getAge() { return this.age; } private void writeObject(java.io.ObjectOutputStream out) throws IOException { // 將name實例變量的值反轉後寫入二進制流 out.writeObject(new StringBuffer(name).reverse()); out.writeInt(age); } private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException { // 將讀取的字符串反轉後賦給name實例變量 this.name = ((StringBuffer)in.readObject()).reverse() .toString(); this.age = in.readInt(); } }
還有一種更完全的自定義機制,他能夠在序列化對象時將該對象替換成其餘對象。
public class Person implements java.io.Serializable { private String name; private int age; // 注意此處沒有提供無參數的構造器! public Person(String name , int age) { System.out.println("有參數的構造器"); this.name = name; this.age = age; } // 省略name與age的setter和getter方法 // name的setter和getter方法 public void setName(String name) { this.name = name; } public String getName() { return this.name; } // age的setter和getter方法 public void setAge(int age) { this.age = age; } public int getAge() { return this.age; } // 重寫writeReplace方法,程序在序列化該對象以前,先調用該方法 private Object writeReplace()throws ObjectStreamException { ArrayList<Object> list = new ArrayList<>(); list.add(name); list.add(age); return list; } }
Java的序列化機制保證在序列化某個對象以前,先調用該對象的writeReplace()方法,若是方法返回的是另外一個對象,則系統轉爲序列化另外一個對象,
public class ReplaceTest { public static void main(String[] args) { try( // 建立一個ObjectOutputStream輸出流 ObjectOutputStream oos = new ObjectOutputStream( new FileOutputStream("replace.txt")); // 建立一個ObjectInputStream輸入流 ObjectInputStream ois = new ObjectInputStream( new FileInputStream("replace.txt"))) { Person per = new Person("孫悟空", 500); // 系統將per對象轉換字節序列並輸出 oos.writeObject(per); // 反序列化讀取獲得的是ArrayList ArrayList list = (ArrayList)ois.readObject(); System.out.println(list); } catch (Exception ex) { ex.printStackTrace(); } } }
序列化機制裏還有一個特殊的方法,他能夠實現保護整個對象,
public class Orientation implements java.io.Serializable { public static final Orientation HORIZONTAL = new Orientation(1); public static final Orientation VERTICAL = new Orientation(2); private int value; private Orientation(int value) { this.value = value; } // 爲枚舉類增長readResolve()方法 private Object readResolve()throws ObjectStreamException { if (value == 1) { return HORIZONTAL; } if (value == 2) { return VERTICAL; } return null; } }
Java的另外一種自定義序列化機制:
Java還提供了另外一種自定義序列化機制,這種序列化機制徹底由程序員存儲和恢復對象數據。須要實現Externalizable接口。
public class Person implements java.io.Externalizable { private String name; private int age; // 注意必須提供無參數的構造器,不然反序列化時會失敗。 public Person(){} public Person(String name , int age) { System.out.println("有參數的構造器"); this.name = name; this.age = age; } // 省略name與age的setter和getter方法 // name的setter和getter方法 public void setName(String name) { this.name = name; } public String getName() { return this.name; } // age的setter和getter方法 public void setAge(int age) { this.age = age; } public int getAge() { return this.age; } public void writeExternal(java.io.ObjectOutput out) throws IOException { // 將name實例變量的值反轉後寫入二進制流 out.writeObject(new StringBuffer(name).reverse()); out.writeInt(age); } public void readExternal(java.io.ObjectInput in) throws IOException, ClassNotFoundException { // 將讀取的字符串反轉後賦給name實例變量 this.name = ((StringBuffer)in.readObject()).reverse().toString(); this.age = in.readInt(); } }
NIO(新IO)和傳統的IO具備相同的目的,都用於進行輸入輸出,但新IO採用了不一樣的方式來處理,NIO採用內存映射文件的方式來處理,NIO將文件或文件的一段區域映射到內存中,從而像訪問內存同樣訪問文件。就像操做系統的虛擬內存概念。
Channel(通道)和Buffer(緩衝)是NIO的兩個核心對象,Channel是對傳統輸入輸出系統的模擬,在NIO中全部的數據都須要經過通道傳輸,Channel和傳統的輸入輸出最大的區別在於它提供了一個map()方法,該方法能夠直接將一塊數據映射到內存中。
IO是面向流的,NIO是面向快(緩衝區)的。
Buffer能夠被理解成一個容器,他的本質是數組,發送到Channel中的全部對象都必須首相放到Buffer中,從Channel中取出的數據也必須先放到Buffer中,Buffer能夠一次次去Channel中取數據,也容許用Channel將文件的某塊數據映射成Buffer。
NIO還提供了用於將Unicode字符串映射成字節序列以及逆映射操做的Charset累和用於支持非阻塞式輸入輸出的Selector類。
Buffer就像一個數組能夠保存多個類型相同的數據,Buffer是一個抽象類,最經常使用的子類是ByteBuffer,他能夠在底層字節數組上進行getset操做。其餘基本數據類型除了boolean都有相應的Buffer類。
在Buffer中有幾個重要概念
屬性 | 描述 |
---|---|
Capacity | 容量,便可以容納的最大數據量;在緩衝區建立時被設定而且不能改變 |
Limit | 上界,緩衝區中當前數據量 |
Position | 位置,下一個要被讀或寫的元素的索引 |
Mark | 標記,調用mark()來設置mark=position,再調用reset()可讓position恢復到標記的位置即position=mark |
並遵循:capacity>=limit>=position>=mark>=0
Buffer的主要做用是裝入數據而後輸出數據,開始時Buffer的postiton爲0,limit爲capatity,程序經過put()方法像Buffer裝入數據,或者從Channel中獲取數據,Buffer的postion相應的後移。
Buffer裝入數據後,調用Buffer的flip()方法,將limit位置設爲postiton所在位置,並將postiton設爲0,是的Buffer的讀寫指針移動到了開始的位置,爲輸出數據作好準備,當BUffer輸出數據結束後,Buffer調用clear方法,不是清空數據,僅僅將postiton設置爲0,兩limit設置爲capatity,爲再次向Buffer中裝入數據作好了準備。
Buffer常規操做:
ByteBuffer buffer=ByteBuffer.allocate(1024); //非直接 大小爲1024個字節 此時它的position=0 limit=1024 capacity=1024
ByteBuffer buf =ByteBuffer.allocateDirect(1024); //直接 大小爲1024個字節
buffer.put("abcde".getBytes()); //將一個字節數組寫入緩衝區 此時它的position=5 limit=1024 capacity=1024
buffer.put("abcde".getBytes()); //該數組會在以前position開始寫 寫完後 position=10 limit=1024 capacity=1024
buffer.flip(); //這一步的做用是 使position=0 limit爲能夠操做的最大字節數 這裏limit=10 capacity不變 仍是1024
System.out.println(new String(byteBuffer.array(),0,2)); //它的結果在這裏是:ab
這個方法的做用是什麼呢?回到屬性,執行下面語句:
System.out.println(byteBuffer.position());
System.out.println(byteBuffer.limit());
System.out.println(byteBuffer.capacity());
輸出的結果是(以空格代替換行): 0 1024 1024
也就是說,如今這個緩衝區如今能夠從索引0位置開始操做了。那就put試一試:
byteBuffer.put("hhh".getBytes()); //注意這裏只put3個字節
那麼若是put以後的position、limit、capacity又是多少呢?
此時position=3 limit=1024 capacity=1024
對於上面的結果?也許會有疑問?先保留吧,接下來咱們讀取該buffer的內容:
byteBuffer.flip(); //讀以前先翻轉 翻轉後position=3 limit=3 capacity=1024
System.out.println(new String(buffer.array(),0,buffer.limit()));
結果是:hhh
不知道小夥伴有沒有疑問,不是說以前的數據還在嗎?它去哪了呢?
被遺忘了,這裏仍是要把clear()方法再次提出來,以前說過,它並不會將緩衝區中的數據清空,也就是說緩衝區中以前的數據還在。執行clear後咱們能夠像操做一個空的緩衝區
同樣從索引0位置開始來操做這個緩衝區。可是以前的數據還存在,只是被遺忘了。若是上面咱們沒有執行byteBuffer.flip(); 那麼,結果就會是:hhhdeworld
因此啊,flip()必定不要忘了。
mark() :標記當前position
reset() :恢復position到mark標記的位置
hasRemaining :判斷緩衝區中是否含有元素
get() :從緩衝區中讀取單個字節
get(byte[] dst) :批量讀取多個字節到dst數組
get(int index) : 讀取指定位置的字節(position不會改變)
put(byte b) :將單個字節寫入緩衝區position位置
put(byte[] dst) :將多個字節從緩衝區position位置開始寫入
put(int index,byte b) : 將指定字節寫入緩衝區索引位置,不會移動position
public class BufferTest { public static void main(String[] args) { // 建立Buffer CharBuffer buff = CharBuffer.allocate(8); // ① System.out.println("capacity: " + buff.capacity()); System.out.println("limit: " + buff.limit()); System.out.println("position: " + buff.position()); // 放入元素 buff.put('a'); buff.put('b'); buff.put('c'); // ② System.out.println("加入三個元素後,position = " + buff.position()); // 調用flip()方法 buff.flip(); // ③ System.out.println("執行flip()後,limit = " + buff.limit()); System.out.println("position = " + buff.position()); // 取出第一個元素 System.out.println("第一個元素(position=0):" + buff.get()); // ④ System.out.println("取出一個元素後,position = " + buff.position()); // 調用clear方法 buff.clear(); // ⑤ System.out.println("執行clear()後,limit = " + buff.limit()); System.out.println("執行clear()後,position = " + buff.position()); System.out.println("執行clear()後,緩衝區內容並無被清除:" + "第三個元素爲:" + buff.get(2)); // ⑥ System.out.println("執行絕對讀取後,position = " + buff.position()); } }
Buffer的建立成本很高,因此直接Buffer適用於長期生存的Buffer。
只有ByteBuffer提供allocateDirect()方法,因此只能在ByteBuffer級別上建立直接ByteBuffer,若是但願使用其餘類型,將該Buffer轉換成其餘類型Buffer。
channel相似於傳統流對象,區別:
channel能夠直接將指定文件的部分或者所有映射成Buffer。
程序不能直接訪問channel中的數據,channel只能和Buffer進行交互。
全部的channel都不該該經過構造器直接建立,而是經過傳統節點InputStream、outPutstream的getChannel()方法返回對應的channel,不一樣的節點流得到的channel也不同。
channel中最經常使用的三類方法map() , read() , write() 。map()用於將數據映射成ByteBuffer,另外兩個有一系列重載,用於對Buffer讀寫數據。
將FileChannel的所有數據映射成ByteBuffer:
public class FileChannelTest { public static void main(String[] args) { File f = new File("FileChannelTest.java"); try( // 建立FileInputStream,以該文件輸入流建立FileChannel FileChannel inChannel = new FileInputStream(f).getChannel(); // 以文件輸出流建立FileBuffer,用以控制輸出 FileChannel outChannel = new FileOutputStream("a.txt") .getChannel()) { // 將FileChannel裏的所有數據映射成ByteBuffer MappedByteBuffer buffer = inChannel.map(FileChannel .MapMode.READ_ONLY , 0 , f.length()); // ① // 使用GBK的字符集來建立解碼器 Charset charset = Charset.forName("GBK"); // 直接將buffer裏的數據所有輸出 outChannel.write(buffer); // ② // 再次調用buffer的clear()方法,復原limit、position的位置 buffer.clear(); // 建立解碼器(CharsetDecoder)對象 CharsetDecoder decoder = charset.newDecoder(); // 使用解碼器將ByteBuffer轉換成CharBuffer CharBuffer charBuffer = decoder.decode(buffer); // CharBuffer的toString方法能夠獲取對應的字符串 System.out.println(charBuffer); } catch (IOException ex) { ex.printStackTrace(); } } }
FileInputStream獲取的 channel只能讀,FileOutputStream獲取的 channel只能寫。RandomAccessFile中也包含getChannel方法,他是隻讀的仍是讀寫的,取決於RandomAccessFile打開文件的模式。
public class RandomFileChannelTest { public static void main(String[] args) throws IOException { File f = new File("a.txt"); try( // 建立一個RandomAccessFile對象 RandomAccessFile raf = new RandomAccessFile(f, "rw"); // 獲取RandomAccessFile對應的Channel FileChannel randomChannel = raf.getChannel()) { // 將Channel中全部數據映射成ByteBuffer ByteBuffer buffer = randomChannel.map(FileChannel .MapMode.READ_ONLY, 0 , f.length()); // 把Channel的記錄指針移動到最後 randomChannel.position(f.length()); // 將buffer中全部數據輸出 randomChannel.write(buffer); } } }
使用Channel和Buffer使用傳統的IO的屢次獲取數據的方式:
public class ReadFile { public static void main(String[] args) throws IOException { try( // 建立文件輸入流 FileInputStream fis = new FileInputStream("ReadFile.java"); // 建立一個FileChannel FileChannel fcin = fis.getChannel()) { // 定義一個ByteBuffer對象,用於重複取水 ByteBuffer bbuff = ByteBuffer.allocate(256); // 將FileChannel中數據放入ByteBuffer中 while( fcin.read(bbuff) != -1 ) { // 鎖定Buffer的空白區 bbuff.flip(); // 建立Charset對象 Charset charset = Charset.forName("GBK"); // 建立解碼器(CharsetDecoder)對象 CharsetDecoder decoder = charset.newDecoder(); // 將ByteBuffer的內容轉碼 CharBuffer cbuff = decoder.decode(bbuff); System.out.print(cbuff); // 將Buffer初始化,爲下一次讀取數據作準備 bbuff.clear(); } } } }
Java默認採用Unicode字符集。JDK1.4提供了Charset來處理字節序列和字符序列之間的轉換關係。該類提供了建立解碼器和編碼器的方法。
獲取JDK支持的所有字符集:
public class CharsetTest { public static void main(String[] args) { // 獲取Java支持的所有字符集 SortedMap<String,Charset> map = Charset.availableCharsets(); for (String alias : map.keySet()) { // 輸出字符集的別名和對應的Charset對象 System.out.println(alias + "----->" + map.get(alias)); } } }
知道了字符集別名,能夠調用Charset的forName方法建立對應的Charset對象,forName方法的參數是字符集別名。
得到了Charset對象以後,能夠得到Charset的編碼器和解碼器,而後能夠實現字節序列和字符序列的轉換。
public class CharsetTransform { public static void main(String[] args) throws Exception { // 建立簡體中文對應的Charset Charset cn = Charset.forName("GBK"); // 獲取cn對象對應的編碼器和解碼器 CharsetEncoder cnEncoder = cn.newEncoder(); CharsetDecoder cnDecoder = cn.newDecoder(); // 建立一個CharBuffer對象 CharBuffer cbuff = CharBuffer.allocate(8); cbuff.put('孫'); cbuff.put('悟'); cbuff.put('空'); cbuff.flip(); // 將CharBuffer中的字符序列轉換成字節序列 ByteBuffer bbuff = cnEncoder.encode(cbuff); // 循環訪問ByteBuffer中的每一個字節 for (int i = 0; i < bbuff.capacity() ; i++) { System.out.print(bbuff.get(i) + " "); } // 將ByteBuffer的數據解碼成字符序列 System.out.println("\n" + cnDecoder.decode(bbuff)); } }
Charset自己也提供了編碼解碼方法,若是僅需編碼解碼操做,能夠直接使用,沒必要建立編碼器和解碼器對象。
String的getBytes方法也是使用指定字符集將字符串轉換成字節序列。
若是多個程序須要併發修改同一個文件,程序須要某種機制來進行通訊,使用文件鎖能夠有效地阻止多個進程併發修改同一個文件。
文件鎖控制文件的所有或部分字節的訪問。在NIO中Java提供FileLock來支持文件鎖定功能。
lock():對文件從position開始,長度爲size的內容加鎖,阻塞。
tryLock():非阻塞。
當穿的shared參數是true時,表示這是共享鎖,容許多個進程讀取文件,但阻止其餘進程得到對該文件的排它鎖。
直接使用lock() tryLock()獲取文件鎖就是排它鎖。
public class FileLockTest { public static void main(String[] args) throws Exception { try( // 使用FileOutputStream獲取FileChannel FileChannel channel = new FileOutputStream("a.txt") .getChannel()) { // 使用非阻塞式方式對指定文件加鎖 FileLock lock = channel.tryLock(); // 程序暫停10s Thread.sleep(10000); // 釋放鎖 lock.release(); } } }
Java7 NIO.2對NIO進行了重大改進,主要包括:
提供了全文見IO和文件系統訪問支持。
基於異步的Channel的IO。
Path接口表明和平臺無關的平臺路徑。
public class PathTest { public static void main(String[] args) throws Exception { // 以當前路徑來建立Path對象 Path path = Paths.get("."); System.out.println("path裏包含的路徑數量:" + path.getNameCount()); System.out.println("path的根路徑:" + path.getRoot()); // 獲取path對應的絕對路徑。 Path absolutePath = path.toAbsolutePath(); System.out.println(absolutePath); // 獲取絕對路徑的根路徑 System.out.println("absolutePath的根路徑:" + absolutePath.getRoot()); // 獲取絕對路徑所包含的路徑數量 System.out.println("absolutePath裏包含的路徑數量:" + absolutePath.getNameCount()); System.out.println(absolutePath.getName(3)); // 以多個String來構建Path對象 Path path2 = Paths.get("g:" , "publish" , "codes"); System.out.println(path2); } }
Files是一個操做文件的工具類:
public class FilesTest { public static void main(String[] args) throws Exception { // 複製文件 Files.copy(Paths.get("FilesTest.java") , new FileOutputStream("a.txt")); // 判斷FilesTest.java文件是否爲隱藏文件 System.out.println("FilesTest.java是否爲隱藏文件:" + Files.isHidden(Paths.get("FilesTest.java"))); // 一次性讀取FilesTest.java文件的全部行 List<String> lines = Files.readAllLines(Paths .get("FilesTest.java"), Charset.forName("gbk")); System.out.println(lines); // 判斷指定文件的大小 System.out.println("FilesTest.java的大小爲:" + Files.size(Paths.get("FilesTest.java"))); List<String> poem = new ArrayList<>(); poem.add("水晶潭底銀魚躍"); poem.add("清徐風中碧竿橫"); // 直接將多個字符串內容寫入指定文件中 Files.write(Paths.get("pome.txt") , poem , Charset.forName("gbk")); // 使用Java 8新增的Stream API列出當前目錄下全部文件和子目錄 Files.list(Paths.get(".")).forEach(path -> System.out.println(path)); // 使用Java 8新增的Stream API讀取文件內容 Files.lines(Paths.get("FilesTest.java") , Charset.forName("gbk")) .forEach(line -> System.out.println(line)); FileStore cStore = Files.getFileStore(Paths.get("C:")); // 判斷C盤的總空間,可用空間 System.out.println("C:共有空間:" + cStore.getTotalSpace()); System.out.println("C:可用空間:" + cStore.getUsableSpace()); } }
FileVisitor表明一個文件訪問器,
public class FileVisitorTest { public static void main(String[] args) throws Exception { // 遍歷g:\publish\codes\15目錄下的全部文件和子目錄 Files.walkFileTree(Paths.get("D:", "idea_project", "mybootbill" /*, "15"*/) , new SimpleFileVisitor<Path>() { // 訪問文件時候觸發該方法 @Override public FileVisitResult visitFile(Path file , BasicFileAttributes attrs) throws IOException { System.out.println("正在訪問" + file + "文件"); // 找到了FileInputStreamTest.java文件 if (file.endsWith("D:\\idea_project\\mybootbill\\src\\main\\java\\com\\jiangwenzhang\\mybootbill\\learn\\FileIO\\NIO\\FileVisitorTest.java")) { System.out.println("--已經找到目標文件--"); return FileVisitResult.TERMINATE; } return FileVisitResult.CONTINUE; } // 開始訪問目錄時觸發該方法 @Override public FileVisitResult preVisitDirectory(Path dir , BasicFileAttributes attrs) throws IOException { System.out.println("正在訪問:" + dir + " 路徑"); return FileVisitResult.CONTINUE; } }); } }
之前的Java中,須要監控文件變化,能夠考慮啓動一個後臺線程,每隔一段時間遍歷一次指定目錄,若是發現遍歷結果和上次不一樣則認爲文件發生了變化。
WatchService
public class WatchServiceTest { public static void main(String[] args) throws Exception { // 獲取文件系統的WatchService對象 WatchService watchService = FileSystems.getDefault() .newWatchService(); // 爲C:盤根路徑註冊監聽 Paths.get("C:/").register(watchService , StandardWatchEventKinds.ENTRY_CREATE , StandardWatchEventKinds.ENTRY_MODIFY , StandardWatchEventKinds.ENTRY_DELETE); while(true) { // 獲取下一個文件改動事件 WatchKey key = watchService.take(); //① for (WatchEvent<?> event : key.pollEvents()) { System.out.println(event.context() +" 文件發生了 " + event.kind()+ "事件!"); } // 重設WatchKey boolean valid = key.reset(); // 若是重設失敗,退出監聽 if (!valid) { break; } } } }
啓動項目後,在C盤根目錄新建文件夾,而後刪除文件夾
NIO.2中提供了大量工具類,能夠簡單的讀取修改文件屬性
public class AttributeViewTest { public static void main(String[] args) throws Exception { // 獲取將要操做的文件 Path testPath = Paths.get("AttributeViewTest.java"); // 獲取訪問基本屬性的BasicFileAttributeView BasicFileAttributeView basicView = Files.getFileAttributeView( testPath , BasicFileAttributeView.class); // 獲取訪問基本屬性的BasicFileAttributes BasicFileAttributes basicAttribs = basicView.readAttributes(); // 訪問文件的基本屬性 System.out.println("建立時間:" + new Date(basicAttribs .creationTime().toMillis())); System.out.println("最後訪問時間:" + new Date(basicAttribs .lastAccessTime().toMillis())); System.out.println("最後修改時間:" + new Date(basicAttribs .lastModifiedTime().toMillis())); System.out.println("文件大小:" + basicAttribs.size()); // 獲取訪問文件屬主信息的FileOwnerAttributeView FileOwnerAttributeView ownerView = Files.getFileAttributeView( testPath, FileOwnerAttributeView.class); // 獲取該文件所屬的用戶 System.out.println(ownerView.getOwner()); // 獲取系統中guest對應的用戶 UserPrincipal user = FileSystems.getDefault() .getUserPrincipalLookupService() .lookupPrincipalByName("guest"); // 修改用戶 ownerView.setOwner(user); // 獲取訪問自定義屬性的FileOwnerAttributeView UserDefinedFileAttributeView userView = Files.getFileAttributeView( testPath, UserDefinedFileAttributeView.class); List<String> attrNames = userView.list(); // 遍歷全部的自定義屬性 for (String name : attrNames) { ByteBuffer buf = ByteBuffer.allocate(userView.size(name)); userView.read(name, buf); buf.flip(); String value = Charset.defaultCharset().decode(buf).toString(); System.out.println(name + "--->" + value) ; } // 添加一個自定義屬性 userView.write("發行者", Charset.defaultCharset() .encode("瘋狂Java聯盟")); // 獲取訪問DOS屬性的DosFileAttributeView DosFileAttributeView dosView = Files.getFileAttributeView(testPath , DosFileAttributeView.class); // 將文件設置隱藏、只讀 dosView.setHidden(true); dosView.setReadOnly(true); } }