File類
- File類能去新建刪除重命名目錄和文件,不能訪問文件內容本身
- 默認情況以用戶的工作路徑來解釋相對路徑,就是運行jvm時所在的路徑
- 使用文件路徑字符串來創建File實例
- File類提供了操作文件和目錄的方法,用File對象來調用,訪問文件名相關方法,文件檢測,獲取常規文件信息,文件和目錄操作的相關方法
- 使用相對路徑的FIle對象獲取父路徑時可能引起錯誤,因為該方法返回將File對象對應的目錄名文件名里最后一個子目錄名子文件名刪除后的結果
- windows路徑字符串分隔符可以用\也可以用/
- File類的list()方法接受一個FilenameFilter參數,這個函數式接口包含一個accept(File dir, String name)方法用于對指定File的所有文件或目錄進行迭代
理解IO流
- Java把不同的輸入輸出源抽象表述為流,stream是從起源到接收的有序數據
- 程序運行所在內存的角度分輸入流和輸出流,操作的數據單元分字節流字符流,流的角色分節點流處理流
- 可以從一個特定的IO設備讀寫數據的流叫節點流,又叫低級流,程序直接連接到實際的數據源,和實際的節點連接
- 處理流是對一個已存在的流進行連接或封裝,通過封裝后的流來實現數據讀寫,又叫高級流
- 處理流的好處只要使用相同的處理流,程序就可以用相同的代碼訪問不同的數據源,隨著處理流所包裝的節點流的變化,程序實際所訪問的數據源也發生變化
- 處理流包裝節點流是一種典型的裝飾器模式,可以消除不同節點流實現差異,也可以方便方法輸入輸出數據,程序不用理會輸入輸出節點的種類,只需要包裝成處理流就可以使用相同的代碼來讀寫不同輸入輸出設備的數據
- 處理流性能提高,增加了緩沖的方式來提高輸入輸出效率,提供了一系列便捷方式一次輸入輸出批量數據,而不是一個字節或字符
- 四個抽象基類InputStream、Reader、OutputStream、Writer
- 輸入流使用隱式指針表示開始讀取的地方,輸出流隱式記錄指針表示即將放入的位置,都會隨著讀寫移動
字節流和字符流
- InputStrean Reader是所有輸入流的抽象基類,本身不能創建實例,作為所有輸入流的模板,提供了多種read()方法
- 用于讀取文件的節點流FileInputStream FileReader
- 程序打開的IO資源不屬于內存里的資源需要顯式關閉,可以放在try()里面自動關閉
- InputStrean Reader還提供了操作記錄指針的方法 mark markSupported reset skip
- OutputStream Writer和上面的相似
- 關閉輸出流除了資源回收還會將緩沖區的數據flush到物理節點中
- Windows平臺換行符是\r\n Unix是\n
輸入輸出流體系
- 處理流隱藏底層設備上節點流的差異,提供更方便的輸入輸出方法
- 處理流的構造參數不是一個物理節點而是一個已經存在的流,而所有節點流都是直接以物理節點作為構造器參數的
- 通常需要輸出文本內容都應該將輸出流包裝成PrintStream后進行輸出
- 關閉最上層的處理流時系統會自動關閉被其包裝的節點流
-
輸入輸出流體系
- 通常字節流功能比字符流功能強,但是處理文本內容會不方便
- 輸入輸出為文本內容時應該考慮字符流,二進制內容應該考慮字節流
- 字符流可以使用字符串作為物理節點,實現從字符串讀取內容,或將內容寫入字符串,StringWriter使用StringBuffer作為輸出節點,因為String是不可變的字符串對象
- 轉換流實現了將字節流轉換成字符流
- 輸入文本時,可將InputStrean類的實例,可以轉換成字符輸入流,再包裝成處理流,方便使用
- 推回輸入流PushbackInoutStream PushbackReader提供了unread方法實現講讀取的內容推回到推回緩沖區,read是先讀取推回緩沖區中的內容
- 創建PushbackInoutStream PushbackReader時需要指定推回緩沖區的大小,默認為1,如果推回內容大于指定大小,會出現異常
重定向標準輸入輸出
- System類中提供了三個重定向標準輸入輸出的方法 setErr setIn setOut
System.setOut(ps);
System.out.println("...");//會輸出到ps指定的地方
Java虛擬機讀寫其他進程的數據
- Runtime對象的exec()方法獲取Process對象,此對象代表由該Java程序啟動的子進程
- Process類提供了三個方法 getErrorStream getInputStream getOutoutStrean 獲取子進程的節點流,這里的輸入輸出是對于程序而言的
RandomAccessFile
- 支持隨機訪問,可以直接跳轉到任何地方讀寫數據
- 如果程序需要在已存在的文件后追加內容,應該使用RandomAccessFile,但是仍然不能向指定位置插入內容,同樣會覆蓋原有內容
- 但是只能讀寫文件,不能讀寫其他IO節點
- RandonAccessFile包含兩個方法操作記錄指針,getFilePoint seek ,也包含同InputStream和OutputStrean的read和write方法,另外提供readXxx WriteXxx方法來輸入輸出
- 兩個構造器,一個使用文件名String參數,一個使用File參數,還需要指定訪問模式mode參數,r rw rws rwd
對象序列化
- 對象序列化的目標是將對象保存到磁盤中,或允許在網絡中直接傳輸對象
- 對象序列化機制允許將內存中的對象轉換成與平臺無關的二進制流,使得對象可以脫離程序的運行而獨立存在
- 序列化是將一個Java對象寫入IO流中,反序列化是從IO流中恢復對象
- 序列化對象必須要讓它的類是可序列化的,必須實現兩個接口之一,Serializable Externalizable
- Serializable是一個標記接口,不需要實現任何方法,很多類都實現了此接口,建議創建的每個JavaBean類都實現了Serializable
- 序列化步驟
- 創建ObjectOutputStream,是個處理流
- 調用writeObject方法輸出可序列化對象
- 反序列化步驟
- 創建ObjectInputStream輸入流
- 調用readObject方法讀取流中對象,返回Object類型的對象,如果知道類型,可以強制轉換成真實類型
- 反序列化讀取的是對象,所以還需要提供對象所屬的class文件,否則會出現異常
- 反序列化機制無須通過構造器來初始化Java對象
- 當在一個文件序列化了多個對象,反序列化讀取時應該按照實際寫入的順序讀取
- 可序列化類的父類必須要么有無參數的構造器要么也是可序列化的,否則反序列化時會拋出異常
- 如果父類不可序列化但有無參數構造器,則父類中定義的成員變量值不會序列化到二進制流中
- 可序列化的類中引用類型變量的引用類也必須是可序列化的,遞歸序列化
- 特殊的序列化算法
- 所有保存到磁盤中的對象都有一個序列化編號
- 程序企圖序列化一個對象時會檢查該對象是否已經被序列化過,只有在本次虛擬機中沒有被序列化過,才會序列化輸出
- 已經序列化過的,程序只是輸出一個序列化編號,而不是重新序列化該對象
- 當程序序列化一個可變對象時,序列化過一次之后即使改變了實例變量的值,也不會輸出序列化
- transient關鍵字只能修飾實例變量,使序列化時不理會該實例變量,反序列化恢復時不能獲取到該實例變量的值
-
實現自定義序列化
自定義重寫readObject()方法哪些實例變量需要序列化,怎樣序列化,重寫writeObject()需要反序列化哪些實例變量和怎樣反序列化
- readObject()和writeObject()兩個方法對應,重寫時應該注意相反處理,操作實例變量的順序也應該一致,以便正確恢復
- 默認情況readObject()調用out.defaultWriteObject來各保存實例變量,writeObject()調用in.defaultReadObject來恢復對象的非瞬態實例變量
- 當序列化流不完整時,readObjectNoData()可以用來正確的初始化反序列化的對象
-
重寫writeReplace()可以在序列該對象時將該對象替換成其他對象,由序列化機制調用,可以擁有訪問權限,可以被子類繼承
- 序列化機制保證在序列化某對象之前先調用writeReplace(),如果返回另一個對象,則轉為序列化另一個對象
- 會先調用writeReplace(),如果返回另一個對象后會調用那個對象的writeReplace()直到不再返回另一個對象,最后調用writeObject()
- readResolve()接著readObject()之后調用,該返回值會代替原反序列化的對象,原來的對象被立即丟棄,在單例類和早期的枚舉類中有效,都應該提供readResolve()
- 反序列化可以用來克隆對象,不需要構造器的新建對象
- readResolve()同樣可以使用任意訪問權限控制符,對于不是final類的重寫readResolve()最好用private修飾該方法
- 實現Externalizable接口的自定義序列化機制完全由程序員決定存儲和恢復對象數據
- Externalizable接口提供了readExternal writeExternal,強制自定義序列化,和readObject() writeObject類似
- 實現Externalizable接口的類的對象序列化反序列化操作同Serializable,一樣調用ObjectOutputStream的writeObject()和ObjectInputStream的readObject()
- 實現Externalizable序列化類必須提供public的無參數構造器
- Externalizable性能略好,但是編程復雜度增加
- 對象序列化注意點
- 對象的類名和實例變量會被序列化,方法、類變量、transient修飾的實例變量不會被序列化
- 在實例變量前加static可以實現和transient一樣的效果,但是不能這樣用
- 必須保證可序列化的類的實例變量的類型也是可序列化的
- 反序列化對象時必須有序列化對象的class文件
- 反序列化按實際寫入順序讀取
- 序列化機制允許為序列化類提供一個private static final 的serialVersionUID值,標識類的序列化版本
- 最好在每個要序列化的類中加入serialVersionUID,數值自己定義
- 不顯示定義的話,JVM會根據類的相關信息計算,修改后會導致與之前計算結果不一致,從而造成反序列化因類版本不兼容而失敗。
- 不同JVM計算方法也可能不同,,不利于程序在不同JVM上移植,導致反序列化失敗
- 對象流中的對象比新類中包含更多的實例變量,多出的實例變量值會被忽略,序列化版本兼容
- 如果新類包含更多的實例變量,序列化版本也兼容,但反序列化得到的新對象的值都是null或0
NIO
NIO概述
- 面相流的輸入輸出系統一次只能處理一個字節,效率不高
- NIO采用內存映射文件的方式處理輸入輸出,將文件或文件的一段區域映射到內存中,像訪問內存一樣來訪問文件,提高了效率
- Channel Buffer是NIO中兩個核心對象
- Channel提供map()方法,直接將一塊數據映射到內存中
- 發送到Channel的數據必須首先放到Buffer中,Buffer可以去讀取Channel的數據,也可以使用Channel的map()將數據映射成Buffer
- NIO還有Unicode字符串映射成字節序列,Charset逆映射,支持非阻塞式輸入輸出的Selector
Buffer
- Buffer是一個抽象類,Buffer常用子類ByteBuffer和CharBuffer,XxxBuffer.allocate(capacity)返回對象
- ByteBuffer有一個子類MappedByteBuffer由Channel的map()方法返回
- Buffer三個概念
- capacity,緩沖區容量,創建后不能改變
- limit,界限,第一個不該被讀寫的索引
- position,位置,下一個被讀寫的位置索引
- Buffer同樣支持mark,同IO流中的mark
- 0≤mark≤position≤limit≤capacity
- 默認開始時,position為0,limit為capacity
- flip()方法將limit設置為position的位置,并將position設置為0
- clear()方法設置position為0,limit為capacity,對Buffer執行clear()后,對象的數據依然存在
- Buffer常用方法capacity hasRemaining limit mark position remaining reset rewind
- 訪問數據的常用方法 put() get(),分相對和絕對,相對是從position處讀寫數據,絕對是根據索引讀寫數據
- ByteBuffer提供allocateDirect()來創建直接Buffer,創建成本較高但讀取效率好
Channel
- Channel可以將文件的部分或全部映射成Buffer
- 程序不能直接訪問Channel,只能通過Buffer來交互
- Channel應該用傳統節點的getChannel()來返回對應的Channel來創建
- Channel常用方法,read() write() map()
- MappedByteBuffer map(FileChannel.MapMode mode, long position, long size);第一個參數有只讀、讀寫等模式
- RandomAccessFile的getChannel()返回的是只讀還是讀寫取決于RandomAccessFile打開文件的模式
- 也可以不一次性map,使用Buffer分批讀取數據,結合flip和clear也可以做到
字符集和Charset
- Charset類提供了字節序列和字符序列之間的轉換關系,是不可變類
- forname()創建字符集對象,調用newDecoder()或newEncoder()返回解碼器編碼器對象,再調用decode()或encode()進行字節序列和字符序列的轉換
- 便捷轉換方法,使用Charset類的decode encode方法來轉換
- String類的getBytes()也將字符串轉換為字節序列
文件鎖
- 文件鎖避免多個進程并發修改同一個文件
- FileChannel提供lock() tryLock()獲取文件鎖FileLock對象
- lock()獲取不到文件鎖時會使程序一直阻塞,tryLock()將直接返回null而不會阻塞,直接使用lock() tryLock()獲取排他鎖
- Lock() tryLock()可以鎖定文件的部分內容,參數shared表示共享鎖,允許多個進程讀取文件,但阻止其他進程獲取該文件的排他鎖
- release()釋放文件鎖
- 對于高并發訪問情形,應該用數據庫保存程序信息,而不是使用文件
- 在某些平臺,文件鎖不是強制性的,一個程序不能獲得文件鎖,也能對文件進行讀寫
- 某些平臺,不能同步鎖定一個文件并把它映射到內存中
- 文件鎖是JVM所持有的,同一JVM的兩個運行程序不能對同一個文件進行加鎖
- 某些平臺上關閉FileChannel時會釋放次JVM的所有鎖
NIO.2
- Java7對原有的NIO提供了全面的文件IO和文件系統的訪問支持,提供了基于異步Channel的IO
- NIO.2引入了一個Path接口,提供了Files和Paths工具類
- Files提供大量便捷的靜態工具方法操作文件,是一個高度封裝的工具類,完成文件復制讀取文件內容寫入文件內容,Java8允許使用StreamAPI操作文件和目錄內容
- Paths提供兩個返回Path的靜態工廠方法
- Files提供walkFileTree()遍歷文件和子目錄,需要FileVisitor文件訪問器
-
遍歷時會觸發FileVisitor的方法,并會返回一個枚舉類值表示后續行為
- 可以繼承SimpleFileVisitor(FileVisitor的實現類)來實現自己的文件訪問器,再根據需要重寫指定方法
- Path類提供register()方法用WatchService對象監聽指定目錄下的指定類型事件的文件變化
- WatchService提供獲取WatchKey的方法,poll() take(),如果程序需要一直監控,應該選擇take方法,如果指定監控時間,應該選poll方法
- Java7在java.nio.file.attribute包下提供讀取修改文件屬性的方法
- 這些工具類主要分為XxxAttributeView文件屬性視圖,XxxAttributes文件屬性集合,一般通過視圖對象獲取,方法詳見API