思維導圖
一.NIO的目的
NIO存在的主要目的在於提高速度,並且,IO類也被NIO重新實現了,速度也有一定的提高。
二.NIO的原理
2.1運行模型
在下圖中可以看到,數據源相較於IO類沒有多大的變化,通道類Channel則是通過IO類的FlieInputStream,FileOutputStream,RandomAccessFile類取得,而內存會利用一個ByteBuffer和通道類進行交互。打個比方來說,數據源相當於是一座礦山,Channel類相當於是通過上述三個IO類所打的隧道,ByteBuffer相當於車輛,往來於工地(內存)和隧道之間,進行貨物(數據)的輸送。
在這幅圖中, 數據源和Channel類由FlieInputStream,FileOutputStream,RandomAccessFile類的getChannel()方法取得。
貨車ByteBuffer的核心是一個數組,用來存儲數據,還有一些字段,用來對貨車的數據進行一些操作。
ByteBuffer主要有以下幾個字段:
- position:當前數組指針所在的地方
- capacity:容量,即數組的大小限制
- limit:限制,他是在容量這一限制下的又一個限制,即position最後可以達到的位置
- mark:一個標記,標記了當時position的值,在重置時有用。
那麼貨車一般會有什麼操作呢:
- put:放入數據
- flip:當數據已經輸入完成後,需要讀取數據時,使用flip(),他會將limit設置在數據區最後的位置,標誌數據已被讀取完了。而position會被設置在數組的開頭,表示開始讀取數據。
- get:從buffer裏讀一個字節,並把postion移動一位。上限是limit,即寫入數據的最後位置
- clear:將position置爲0,並不清除buffer內容。
- mark: 將mark值賦爲position的值。
- reset:將此緩衝區的位置重置爲以前標記的位置,調用此方法不更改也不丟棄標記的值。
2.2簡單使用
//定義輸入的數據源
File file = new File("e:/in.txt");
try {
//通過輸入流獲得隧道
FileChannel fc = new FileInputStream(file).getChannel();
//定義一個ByteBuffer貨車出來,必須給貨車分配容量,才能更快。
ByteBuffer bb = ByteBuffer.allocate(1024);
//隧道指定貨車讀取數據
fc.read(bb);
//準備讀取貨車中的數據
bb.flip();
//輸出到控制檯
while(bb.hasRemaining()){
System.out.println((char)bb.get());
}
//寫入到另一份文件中
File outFile = new File("e:/out.txt");
FileChannel fc2 = new FileOutputStream(outFile).getChannel();
//由於剛纔讀取過貨車中的信息,所以現在positon位於limit上,即數據已經讀取完了
// 所以重置以下,方便讀取
bb.flip();
fc2.write(bb);
//也可以使用其他的方法寫入數據wrap方法會指定新的ByteBuffer貨車,進行運輸
//方法中的Byte數組會直接打包寫入
fc2.write(ByteBuffer.wrap("new way".getBytes()));
fc.close();
fc2.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
}catch(IOException e){
e.printStackTrace();
}
三.常用功能
3.1字符輸出
在上面的程序中,如果輸入數據源中有中文,那麼輸出到控制檯就會出現亂碼,所以,我們可以在輸入數據時進行編碼,或者在輸出數據時解碼。
//定義輸入的數據源
File file = new File("e:/in.txt");
try {
//通過輸入流獲得隧道
FileChannel fc = new FileInputStream(file).getChannel();
//定義一個ByteBuffer貨車出來,必須給貨車分配容量,才能更快。
ByteBuffer bb = ByteBuffer.allocate(1024);
//隧道指定貨車讀取數據
fc.read(bb);
bb.flip();
//準備讀取貨車中的數據
Charset charset = Charset.forName("GBK");
//進行解碼
System.out.println(charset.decode(bb));
//寫入到另一份文件中
File outFile = new File("e:/out.txt");
FileChannel fc2 = new FileOutputStream(outFile).getChannel();
//由於剛纔讀取過貨車中的信息,所以現在positon位於limit上,即數據已經讀取完了所以重置以下,方便讀取
bb.flip();
//輸入時不編碼也可以將中文輸入,當然輸入編碼,輸出就可以不解碼了
fc2.write(bb);
//輸入字符串則可以採用這種編碼方式
fc2.write(ByteBuffer.wrap("小狗".getBytes("GBK")));
fc.close();
fc2.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
}catch(IOException e){
e.printStackTrace();
}
3.2格式化:視圖
在NIO包中,除了ByteBuffer外,還有IntBuffer,FloatBuffer等一些列的基本類型的Buffer,利用他們是ByteBuffer的視圖,依舊是說,實際上的處理還是ByteBuffer,我們可以利用這些Buffer對基本類型的數據進行處理。
//定義輸入的數據源
File inFile = new File("e:/in.txt");
File outFile = new File("e:/out.txt");
try {
//獲得隧道
FileChannel inFc = new FileInputStream(inFile).getChannel();
FileChannel outFc = new FileOutputStream(outFile).getChannel();
//定義一個ByteBuffer貨車出來,必須給貨車分配容量,才能更快。
ByteBuffer bb = ByteBuffer.allocate(1024*20);
bb.asIntBuffer().put(new int[]{1,2,3,4});
//此處寫出後,在文本中看見的是亂碼,因爲輸出的是int(4字節)的而二進制文件,文本不
//能顯示二進制文件,所以亂碼
outFc.write(bb);
//讀取數據,可以一個一個或者成批次的讀取
bb.flip();
int i = 0;
IntBuffer ib = bb.asIntBuffer();
while((i = ib.get()) != 0){
System.out.println(i);
}
//成批次獲取
ib.flip();
//數組的大小必須符合ByteBuffer中數據的數量,不然會報錯
int[] temp = new int[ib.limit()];
ib.get(temp);
for(int j : temp){
System.out.print(j);
}
inFc.close();
outFc.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
}catch(IOException e){
e.printStackTrace();
}
3.3內存映射文件
由於虛擬機內存太小,或者某些數據太大,導致不能將某些數據放入內存,所以需要內存映射文件,他能直接在內存中映射大文件,可以把它當作非常大的數組訪問。
在寫入時,可以使用FileOutputStream,但映射文件的輸出則必須使用RandomAccessFile。所以說,最好使用RandomAccessFile
try {
//先獲取隧道
FileChannel fc = new RandomAccessFile("e:/out.txt","rw").getChannel();
//通過隧道獲取MappedByteBuffer,此類繼承自ByteBuffer,是一個抽象類,表示內存映射文件
//三個參數分別爲:使用權限,起始映射位置,映射大小
MappedByteBuffer mbb = fc.map(FileChannel.MapMode.READ_WRITE,0,1024);
IntBuffer ib = mbb.asIntBuffer();
ib.put(1);
fc.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
}catch (IOException e){
e.printStackTrace();
}
3.4文件加鎖
NIO中由文件加鎖機制,可以保證文件的同步或者異步訪問。鎖能直接映射到系統的文件鎖,所以無論是不是Java線程,都不能訪問加鎖後的文件。
try {
//先獲取隧道
FileChannel fc = new RandomAccessFile("e:/out.txt","rw").getChannel();
//tryLock是非阻塞式的,當不可獲得時會返回。
//position - 鎖定區域開始的位置;必須爲非負數
//size - 鎖定區域的大小;必須爲非負數,並且 position + size 的和必須爲非負數
//shared - 要請求共享鎖定,則爲 true,在這種情況下此通道必須允許進行讀取(可能是寫入)操作;
// 要請求獨佔鎖定,則爲 false,在這種情況下此通道必須允許進行寫入(可能是讀取)操作
FileLock fl1 = fc.tryLock(0,512,true);
//lock是阻塞式的,他將阻塞進程,直到可以獲得鎖,或者調用lock的通道被關閉
FileLock fl2 = fc.lock();
//釋放鎖
fl1.release();
fl2.release();
fc.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
}catch (IOException e){
e.printStackTrace();
}