第15 章 輸入/輸出
使用輸入機制 允許程序記錄運行時讀取外部數據,(磁盤,關盤等存儲介質),用戶輸入
使用輸出允許程序記錄運行狀態,將程序數據輸出到磁盤、關盤等介質
java io流使用了一種裝飾設計模式,它將IO流分成底層字節流和上層處理流,其中節點流和底層物理存儲節點直接關聯,程序在將處理流包裝成處理流,從而包裝程序使用輸入輸出流來訪問不同節點
15.1 File 類
15.1.1 訪問文件和目錄
File類可以使用文件路徑字符串來創建File實例,該路徑可是相對路徑也可以是絕對路徑
UNIX/Linux/BSD等系統上,如果路徑是( / ) 則表明是絕對路徑
15.2 理解java的IO流
15.2.1 流的分類
1. 輸入流和輸出流(以內存來劃分, 以服務器來劃分)
輸入流 :只能從中讀取數據不能寫入數據
輸出流 :只能想其中寫入數據,而不能讀取數據
java的輸入流主要由InputStream和Reader作爲基類
輸出流主要是用OutputStream和 Writer作爲基類
2. 字節流和字符流
字符流主要有InputStream和OutputStream作爲基類
字符流主要是Reader 和 Writer 作爲基類
3. 節點流和處理流
按照流的角色來分,可以分爲節點流和處理流
可以從一個特定的IO設備(磁盤、網絡)讀/寫數據的流,稱爲節點流(低級流)
處理流則用用對一個已經存在的流進行連接或者封裝。通過封裝後的流來實現數據的讀寫功能(高級流)
15.2.2 流的概念模型
java把所有的有序數據抽象成流模型,簡化了輸入輸出處理,理解了流的概念模型也就瞭解兩類javaIO
JAVA 的處理流模型則體現java輸入/輸出流的靈活,處理流的功能主要體現在以下兩個方面
1.功能的提高:主要增加緩衝的方式來提高輸入/輸出的效率
2.操作的便捷:處理流可能提供了一系列的便捷方法來一次輸入/輸出大量流
通過處理流java無需理會輸入輸出的是磁盤還是網絡
15.3 字節流和字符流
15.3.1 InputStream 和Reader
InputStream 和Reader是所有輸入流的抽象基類,本身不能創建實例來執行輸入,但他們分別有一個讀取文件的輸入流 FileInputStream 和FileReader
15.3.2 OutputStream和Write
使用java的IO流執行輸出是,不要忘記關閉輸出流
1.關閉輸出流可以保證流的物理資源被關閉
2.可能還可以將輸出流緩存區中的數據flush到物理節點裏
15.4 輸入/輸出流體系
15.4.1 處理流的用法
處理流可以隱藏底層設備撒上節點流的差距,並對外提供更加方便的輸入/輸出方法,讓程序員只需關心高級流的操作
使用處理流是的典型思路是 使用處理流來包裝節點流,程序通過處理流來執行輸入輸出的功能,讓節點流與底層的I/0設備,文件交換
只要流的構造器參數不是一個物理節點而是一個已將存在的流,那麼這種流就一定是處理流
PrintStream類的輸出功能非常強大,通常如果需要輸出文本內容,都應該講輸出流包裝成PrintStream
使用處理流包裝底層的節點流後,只要關閉最上層的處理流即可,系統會自動關閉節點流
15.4.2 輸入/輸出流體系
管道流主要是用來實現線程之間的通信問題
緩存流 可以提高輸入輸出效率,增加緩衝流功能後需要使用flush()纔可以將緩衝區的內容寫入實際的物理節點
15.4.3 轉換流
輸入輸出流體系中還提供了兩個轉換流,這兩個轉換流用於實現將字節流轉換成字符流
InputStreamReader將字節輸入流轉換爲字符輸入流,
OutputStreamWriter 將字節輸出流轉換成字符輸出流
java 使用 System.in 代表標準輸入,這個標準輸入流式 InputStream類的實例,鍵盤輸入的內容都是文本內容可以使用 InputStreamReader將其轉換成字符流,普通的Reader讀取輸入內容時依然不太方便,可以將Reader再次包裝成BufferedReader 利用BufferedReader可以一次讀取一行內容
經常把讀取文本內容的輸入流包裝成BufferedReader,用來方便的讀取輸入流的文本內容
15.4.3 推回輸入流
PushbackInputStream 和PushbackReader 流 這兩個推回輸入流都帶有一個推回緩衝區,當程序調用者兩個流的 unread()方法時,系統會把指定數組的內容推回到該緩衝區 ,而推回輸入流每次調用read()方法總是先從緩衝區讀取,只有完全讀取,但read()還沒裝滿時,纔會從原輸入流讀取
默認緩衝區的長度爲1
15.5 重定向標準輸入輸出流
不在使用鍵盤作爲輸入,不在使用屏幕作爲輸入
System類裏提供了三個重定向方法
15.6 JAVA 虛擬機讀寫其他進程的數據
使用Runtime對象的exec()方法可以運行平臺上的其他程序,該方法產生一個Process對象, Process對象代表由該java程序啓動的java的子進程
子進程讀取程序中的數據 使用的是輸出流
15.7 RandomAccessFile
RandomAccessFile是java輸入輸出體系中最豐富的文件內容訪問類,它提供了衆多方法來訪問文件內容,也可以向文件輸出數據,與普通的輸入輸出流不同的是,RandomAccessFile支持隨機訪問,程序可以直接跳到文件任意地方來讀
如果要想文件後追加內容可以使用RandomAccessFile
RandomAccessFile只能讀取文件,不能讀取其他的io節點
RandomAccessFile對象包含了一個記錄指針,用於標記當前讀寫的位置。
RandomAccessFile不能像文件指定位置插入內容,如果需要插入則可以通過把插入點後的內容輸入緩衝區,插入完畢後再將緩衝區內的內容寫到文件後面
多線程斷點的網絡下載工具就可以通過RandomAccessFile類來實現
15.8 對象系列化
對象系列化的目標是將對象保存在磁盤中,或允許在網絡中直接傳輸對象。對象系列化機制允許吧內存中的java對象轉換成平臺無關的二進制流 通過網絡將這種二進制流傳輸到另一個網絡節點。其他程序一旦獲得了這種二進制流,都可以將這種二進制流恢復成原來的java對象
15.8.1 系列化的含義和意義
系列化機制允許將實現系列化的java對象轉換爲字節系列,這些字節系列可以保存在磁盤上,或通過網絡傳輸,以備以後重新恢復成原來的對象,系列化機制使得對象可以脫離程序的運行而獨立存在,
所有可能在網絡上傳播的對象的類都應該是可系列化的
15.8.2 使用對象流實現系列化
通過實現Serializable接口來實現對象系列化,程序可以通過如下兩個步驟來實現對象系列化
1.創建一個ObjectOutputStream 這個輸出流是一個處理流
2.調用 ObjectOutputStream對象的writeObject()方法輸出可系列化的對象
反系列化步驟
1.創建一個ObjectInputStream輸入流,這個輸入流是一個處理流
2.調用ObjectInputStream對象的readObject() 方法讀取流中的對象
反序列化讀取的僅僅是java對象的數據,不是java類,因此採用反序列化恢復java對象時,必須提供對象所屬於的class對象,
反系列化機制無需通過構造器來初始化java對象
如果使用系列化機制想文件中寫入多個java對象,使用反系列機制恢復對象時,必須按實際寫的順序讀取
當一個可系列化對象有多個父類時,這些父類要麼有無參數的構造器要麼也是可系列化的
15.8.3 對象引用的系列化
如果系列化類引用的對象沒有系列化,那麼該類也無法系列化
程序系列化算法 如下
1.所有保存在磁盤中的對象都有一個系列化編號
2.當程序試圖系列化一個對象時,程序將先檢查對象是否已經被系列化過,如果該對象從未被系列化過,系統纔會將對象轉化爲字節系列並輸出
3.如果對象已經系列化過,程序將只是直接輸出一個系列化編號
15.9 NIO
15.9.1 java 新IO
新IO採用內存映射文件的方式來處理輸入輸出,新IO將文件或文件的一段區域映射到內存中,這樣就可以像訪問內存一樣訪問文件了,模擬了操作系統上的虛擬內存的概念,
Channel(通道)和Buffer(緩存) 是NIO的兩個核心對象,Channel是對傳統的輸入輸出系統的模擬,在NIO系統中國所有數據都需要通過通道傳輸,與傳統的輸入輸出系統模擬,Channel與傳統的InputStream 和OutputStream(面向流輸的處理)最大的區別是提供了一個map()方法,通過該方法可以直接將一塊數據映射到內存中(面向塊的處理)
Buffer可以理解爲一個容器,本質是一個數組,發送到Channel中的所有對象都必須首先放到Buffer中,從Channel讀取數據也必須先放到Buffer中
NIO還提供了一個用於將Unicode字符串映射成字節系列以及逆映射操作的Charset類,也提供了用於支持非阻塞式輸入輸出的Selector 類
Buffer中有三個重要的概念 容量 界限 和位置
Buffer中有兩個重要方法 flip() 將limit設置爲position所在位置,並將position設爲0 爲從Buffer中取出數據做好準備
clear() 將position置爲0,將limit置爲capacity
put 和get來訪問Buffer中的數據時,分爲相對和絕對兩種
相對 從buffer中的當前position出開始讀寫,然後將position的值按處理元素的個數增加
絕對 直接根據索引執行Buffer中讀取或寫入數據 不影響position的值
通過allocate() 方法創建的Buffer對象是普通的Buffer
ByteBuffer還提供了一個allocateDirect()方法來創建直接Buffer
直接創建Buffer的成本很高,所以只適用於長生存期的buffer
15.9.3 使用Channel
Channel類似於傳統的流對象,但與傳統的流對象有兩個主要區別
1. Channel 可以直接將指定文件的部分或者全部直接映射成Buffer
2. 程序不能直接訪問Channel中的數據,只能通過Buffer進行交互
所有的Channel都不應該直接通過構造器直接創建,而是通過傳統的節點InputStream...的getChannel()方法來獲取,
Channel中最常用的三類方法是map() read() write() 其中map()方法用於將Channel對應
的部分或者全部數據映射成ByteBuffer read() write() 用來讀取數據
15.9.4 字符集和Charset
newDecoder() 代表該Charset 解碼器
newEncoder() 代表該Charset 編碼器
15.9.5 文件鎖
文件鎖可以有效的阻止多個進程併發修改同一個文件
在NIO中java提供了FileLock來支持文件鎖定功能,在FileChannel中提供的lock()/tryLock()方法可以獲得文件鎖FileLock對象
lock() 當試圖鎖定某一個文件時,如果無法得到文件鎖,程序將一直阻塞,
而 try lock() 是嘗試鎖定文件,它將直接返回而不是阻塞,如果獲得文件鎖,則返回文件鎖,否則返回null
文件鎖雖然可以用於控制併發訪問,但是對於高併發訪問的情景,還是推薦使用數據庫來保存程序信息
文件鎖還需要指出的幾點
1. 在某一些平臺上,文件鎖僅是建議性的(即使一個文件不能獲得文件鎖,它也可以讀寫)
2. 在某一些平臺上,不能同步的鎖定一個文件並把它映射到內存中
3. 文件鎖是由java虛擬機所持有的,如果兩個java程序使用同一個java虛擬機允許,則他們不能對同時同一個文件進行加鎖
4. 在某一些平臺上關閉FileChannel時,會釋放jvm 在該文件中的所有鎖
15.10 java 7 的NIO.2
java 7對原有的NIO進行了重大的改進,主要有
1. 提供了全面的文件IO和文件系統訪問支持
2. 基於異步的Channel 的IO
15.10.1 Path Paths Files核心API
15.10.2 使用FileVisitor遍歷文件和目錄
在以前的java版本中,如果要遍歷指定目錄下的所有文件和子類目錄,只能使用遞歸進行遍歷,但這種方式不僅複雜,而且性能也不高
Files 工具類提供了更方便的方法遍歷文件和子目錄
15.10.3 使用WatchService 監控文件的變化
在java之前的版本如果程序需要監視文件的變化,則可以考慮啓動一條後臺線程,這條後臺線程每隔一段時間去遍歷一次指定目錄的文件,如果和上次遍歷結果不一樣就認爲發生了變化,但這種方式不僅十分繁瑣,而且性能也不高