在java API中,可以從其中讀入一個字節序列的對象叫做輸入流,而可以向其中寫入一個字節序列的對象叫做輸出流,這些字節序列的來源和目的地可以是文件,而且通常是文件,但是也可以是網絡連接,甚至還可以是內存塊,而抽象類InputStream和OutputStream構成了輸入/輸出(I/O)類層次結構的基礎。
因爲面向字節的流不便於處理以Unicode形式存儲的信息(Unicode中每個字符都使用了多個字節來表示),所以從抽象類Reader和Writer中繼承出來了一個專門用於處理Unicode字符的單獨的類層次結構,這些類擁有的讀寫操作都是基於兩字節的Unicode碼元的,而不是基於單字節的字符。
順便提一下,UTF-8編碼方式是一種8位的Unicode字符集,長度是可變的,一個字符可能是1個字節,2個字節,3個字節或者4個字節,中文一般是3個字節。
UCS-2編碼是固定長度爲16位的Unicode字符集,每個字符都佔2個字節。
UTF-16編碼也是一種16位的編碼字符集,在UTF-16中,字符要麼是2字節,要麼是4字節。
一、流
圖1-1 輸入流和輸出流的層次結構
圖1-2 Reader和Writer的層次結構
上圖1-1和1-2就是java的流家族,包含各種流類型
我們把流家族中的成員按照他們的使用方法來進行劃分,這樣就形成了處理字節和字符的兩個單獨的層次結構:
(1)對於單個的字節或字節數組,我們可以使用InputStream和OutputStream,他們可以可以讀寫單個的字節或字節數組
(2)對於字符串和數字:我們就需要功能更爲強大的子類,例如DataInputStream和DataOutputStream可以以二進制的格式讀取所有的基本java類型
(3)其他流:ZipInputStream和ZipOutputStream可以以常見的ZIP壓縮格式讀取文件
java.io.InputStream:
abstract int read() :從數據中讀入一個字節,並返回該字節,這個read方法在碰到流的結尾時返回-1。
int read(byte[] b) :讀入一個字節數組,並返回實際讀入的字節數,或者在碰到流的結尾時返回-1;這個read方法最多讀入b.length個字節
int read(byte[] b, int off, int len) :讀入一個字節數組,這個read方法返回實際讀入的字節數,或者在碰到流的結尾時返回-1;
參數:b: 數據讀入的數組
off: 第一個讀入字節應該被放置的位置在b中的偏移量
len: 讀入字節的最大數量
long skip(long n):在輸入流中跳過n個字節,返回實際跳過的字節數(如果碰到流的結尾,則可能小於n)
int available( ):返回在不阻塞的情況下可獲取的字節數
void close( ):關閉這個輸入流
void mark( int readlimit):在輸入流的當前位置打一個標記(並非所有的流都支持這個特性),如果從輸入流中已經讀入的字節多餘readlimit個,則這個流允許忽略這個標記
void reset( ):返回到最後一個標記,隨後對read的調用將重新讀入這些字節,如果當前沒有任何標記,則這個流不被重置。
boolean markSupported( ):如果這個流支持打標記,則返回true
java.io.OutputStream:
abstract void write(int n):寫出一個字節數據
void write(byte[] b)
void write(byte[] b,int off, int len):寫出所有字節或者某個範圍的字節到數組b
參數:b: 數據寫出的數組
off: 第一個寫出字節在b中的偏移量
len: 寫出字節的最大數量
void close( ):沖刷並關閉輸出流
void flush( ):沖刷輸出流,也就是將所有緩衝的數據發送到目的地
首先,read和write方法在執行時都將阻塞,直至字節確實被讀入或者寫出,當完成對流的讀寫時,應該調用close方法關閉它,這個調用會釋放掉十分有限的操作系統資源,如果一個應用程序打開了過多的流而沒有關閉,系統的資源將被耗盡,關閉一個輸出流的同時還會沖刷用於該輸出流的緩衝區:所有被臨時置於緩衝區中,以便使用更大的包的形式傳遞的字符在關閉輸出流時都將被送出,但是如果不關閉文件,那麼寫出字節的最後一個包可能將永遠也得不到傳遞,所以就需要我們的flush方法來認爲的沖刷這些輸出
我們再來看一張圖
這裏有四個附加接口:Closeable、Flushable、Readable和Appendable。
Closeable和Flushable非常簡單,分別擁有下面的兩個方法:
void close( ) throws IOException
void flush( )
InputStream、OutPutStream、Reader和Writer都實現了Closeable接口
而OutputStream和Writer還實現了Flushable接口
Readable接口只有一個方法:int read(CharBuffer cb)
CharBuffer 類擁有按順序和隨機的進行讀寫的方法,它表示內存中的一個緩衝區或者一個內存映像的文件
Appendable接口有兩個用於添加單個字符和字符序列的方法:
Appendable append(char c)
Appendable append(CharSequence s)
CharSequence 接口描述了一個char值序列的基本屬性,String,CharBuffer,StringBuilder和StringBuffer都實現了它
在流類家族中,只有Writer類實現了Appendable 接口
java.io.Closeable:
void close( ):關閉這個Closeable,可能胡拋出IOException
java.io.Flushable:
void flush( ):沖刷這個Flushable
java.lang.Readable:
int read(CharBuffer cb ):嘗試着向cb讀入其可持有數量的char值,返回讀入的char值得數量,或者當從這個Readable中無法在獲取更多的值時返回-1。
java.lang.Appendable
Appendable append(char c)
Appendable append(CharSequence s)
向這個Appendable中追加給定的碼元或者給定的字符序列中的所有碼元,返回this
java.lang.CharSequence
char charAt(int index):返回給定索引處的碼元
int length( ):返回在這個序列中的碼元的數量
CharSequence subSequence(int startIndex, int endIndex ):返回由存儲在startIndex到endIndex -1處的所有碼元構成的CharSequence
String toString( ):返回這個序列中所有碼元構成的字符串
二、組合流過濾器:
FileInputStream和FileOutputStream可以提供一個附着在磁盤上的輸入流和輸出流,而我們只需要向它的構造器提供文件名或者文件的完整路徑。例如
FileInputStream fin = new FileInputStream("employee.dat");
這句代碼可以用戶目錄下的employee.dat文件
與抽象類InputStream 和OutputStream一樣,這些類只能在字節級別上進行讀寫,也就是說,我們只能從fin對象讀取字節或字節數組。
byte b = (byte)fin.read();
如果我們只有DataInputStream,我們就只能讀入數值類型:
DataInputStream din = ...;
double s = din.readDouble();
FileInputStream 中沒有任何讀入數值類型的方法,DataInputStream 中沒有任何從文件獲取數據的方法。
這是由於java使用了一種靈巧的機制來分離這兩種職責,某些流可以從文件和其他外部的位置上獲取字節(例如FileInputStream 和openStream),而某些流可以將字節組裝到更有用的數據類型中(例如DataInputStream 和PrintWriter),我們必須對二者進行結合。
例如,我們想從文件中讀入數字,需要創建一個FileInputStream ,然後將其傳遞給DataInputStream 構造器:
FileInputStream fin = new FileInputStream("employee.dat");
DataInputStream din = new DataInputStream(fin);
double s = din.readDouble();
又例如:流在默認的情況下是不能被緩衝區緩存的,也就是說每個對read的調用都會請求操作系統在分發一個字節。相比之下,我們可以請求一個數據塊存並將其放到緩衝區中,這樣每次讀取字節都是在緩存中讀取,會顯得更加高效,下面就是使用緩衝機制的構造器序列:
DataInputStream din = new DataInputStream (
new BufferedInputStream(
new FileInputStream("employee.dat")));
再例如:當多個流鏈接在一起的時候,我們需要跟蹤各個中介流,例如,當讀入輸入時,我們經常會要瀏覽下一個字節,以便於判斷是不是我們想要的值:
PushbackInputStream pbin = new PushbackInputStream(
new BufferedInputStream(
new FileInputStream("employee.dat")));
現在我們可以預讀下一個字節
int b = pbin.read();
如果下一個字節並不是我們所期望的,我們可以將其推回到流中
if(b != '<') pbin.unread(b);
但是對於讀入和推回是應用於可回推輸入流(PushbackInputStream)
如果你想能夠預先瀏覽並且還可以讀入數字,那就需要再結合DataInputStream 。
java的流類庫相比其他的編程語言有一點麻煩,其他語言諸如緩衝機制和預覽等細節都是自動處理的,但是java必須將多個流過濾器組合起來,不過呢,這種混合並匹配過濾器類以構建真正有用的流序列能力,將帶來極大的靈活性。
java.io.FileInputStream
FileInputStream(String name)
FileInputStream(File file)
使用由name字符串或者file對象指定路徑名的文件,創建一個新的文件輸入流,非絕對路徑名將按照相對於VM啓動時所設置的相對目錄解析
java.io.FileOutputStream
FileOutputStream(String name)
FileOutputStream(String name,boolean append)
FileOutputStream(File file)
FileOutputStream(File file,boolean append)
使用由name字符串或者file對象指定路徑名的文件創建一個新的輸出流,如果append爲true,那麼數據將被添加到文件尾部,而具有相同名字的已有文件不會刪除;否則,這個方法刪除所有相同名字的已有文件
java.io.BufferedInputStream
BufferedInputStream(InputStream in)
創建一個帶緩衝區的流,帶緩衝區的輸入流在從流中讀入字符時,不會每次都對設備訪問,而是從緩存中讀取。當緩衝區爲空時,會向緩衝區中讀入一個新的數據塊
java.io.OutputStream
BufferedInputStream(OutputStream out)
創建一個帶緩衝區的流,帶緩衝區的輸出流在收集要寫出的字符時,不會每次都對設備訪問。當緩衝區填滿或當流被沖刷時,數據就被寫出。
java.io.PushbackInputStream
PushbackInputStream(InputStream in)
PushbackInputStream(InputStream in,int size)
構建一個可以預覽一個字節或者具有指定大小的回推緩衝區的流
void unread(int b)
回推一個字節,它可以在下次調用read方法是被再次獲取
參數 :b 要再次讀入的字節