高併發Java:NIO和AIO
IO感覺上和多執行緒並沒有多大關係,但是NIO改變了執行緒在應用層面使用的方式,也解決了一些實際的困難。而AIO是非同步IO和前面的系列也有點關係。在此,為了學習和記錄,也寫一篇文章來介紹NIO和AIO。
1. 什麼是NIO
NIO是New I/O的簡稱,與舊式的基於流的I/O方法相對,從名字看,它表示新的一套Java I/O標 準。它是在Java 1.4中被納入到JDK中的,並具有以下特性:
- NIO是基於塊(Block)的,它以塊為基本單位處理資料 (硬碟上儲存的單位也是按Block來儲存,這樣效能上比基於流的方式要好一些)
- 為所有的原始型別提供(Buffer)快取支援
- 增加通道(Channel)物件,作為新的原始 I/O 抽象
- 支援鎖(我們在平時使用時經常能看到會出現一些.lock的檔案,這說明有執行緒正在使用這把鎖,當執行緒釋放鎖時,會把這個檔案刪除掉,這樣其他執行緒才能繼續拿到這把鎖)和記憶體對映檔案的檔案訪問介面
- 提供了基於Selector的非同步網路I/O
所有的從通道中的讀寫操作,都要經過Buffer,而通道就是io的抽象,通道的另一端就是操縱的檔案。
2. Buffer
Java中Buffer的實現。基本的資料型別都有它對應的Buffer
Buffer的簡單使用例子:
package test;
import java.io.File;
import java.io.FileInputStream
;import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
publicclassTest{
publicstaticvoid main(String[] args)throwsException{
FileInputStream fin =newFileInputStream(newFile(
"d:\\temp_buffer.tmp"));
FileChannel fc = fin.getChannel();
ByteBuffer byteBuffer =ByteBuffer.allocate(1024);
fc.read
(byteBuffer);fc.close();
byteBuffer.flip();//讀寫轉換
}
}
總結下使用的步驟是:
-
得到Channel
-
申請Buffer
-
建立Channel和Buffer的讀/寫關係
-
關閉
下面的例子是使用NIO來複制檔案:
publicstaticvoid nioCopyFile(String resource,String destination)
throwsIOException{
FileInputStream fis =newFileInputStream(resource);
FileOutputStream fos =newFileOutputStream(destination);
FileChannel readChannel = fis.getChannel();// 讀檔案通道
FileChannel writeChannel = fos.getChannel();// 寫檔案通道
ByteBuffer buffer =ByteBuffer.allocate(1024);// 讀入資料快取
while(true){
buffer.clear();
int len = readChannel.read(buffer);// 讀入資料
if(len ==-1){
break;// 讀取完畢
}
buffer.flip();
writeChannel.write(buffer);// 寫入檔案
}
readChannel.close();
writeChannel.close();
}
Buffer中有3個重要的引數:位置(position)、容量(capactiy)和上限(limit)
這裡要區別下容量和上限,比如一個Buffer有10KB,那麼10KB就是容量,我將5KB的檔案讀到Buffer中,那麼上限就是5KB。
下面舉個例子來理解下這3個重要的引數:
publicstaticvoid main(String[] args)throwsException{
ByteBuffer b =ByteBuffer.allocate(15);// 15個位元組大小的緩衝區
System.out.println("limit="+ b.limit()+" capacity="+ b.capacity()
+" position="+ b.position());
for(int i =0; i <10; i++){
// 存入10個位元組資料
b.put((byte) i);
}
System.out.println("limit="+ b.limit()+" capacity="+ b.capacity()
+" position="+ b.position());
b.flip();// 重置position
System.out.println("limit="+ b.limit()+" capacity="+ b.capacity()
+" position="+ b.position());
for(int i =0; i <5; i++){
System.out.print(b.get());
}
System.out.println();
System.out.println("limit="+ b.limit()+" capacity="+ b.capacity()
+" position="+ b.position());
b.flip();
System.out.println("limit="+ b.limit()+" capacity="+ b.capacity()
+" position="+ b.position());
}
整個過程如圖:
此時position從0到10,capactiy和limit不變。
該操作會重置position,通常,將buffer從寫模式轉換為讀 模式時需要執行此方法 flip()操作不僅重置了當前的position為0,還將limit設定到當前position的位置 。
limit的意義在於,來確定哪些資料是有意義的,換句話說,從position到limit之間的資料才是有意義的資料,因為是上次操作的資料。所以flip操作往往是讀寫轉換的意思。
意義同上。
而Buffer中大多數的方法都是去改變這3個引數來達到某些功能的:
publicfinalBuffer rewind()
將position置零,並清除標誌位(mark)
publicfinalBuffer clear()
將position置零,同時將limit設定為capacity的大小,並清除了標誌mark
publicfinalBuffer flip()
先將limit設定到position所在位置,然後將position置零,並清除標誌位mark,通常在讀寫轉換時使用
檔案對映到記憶體
publicstaticvoid main(String[] args)throwsException{
RandomAccessFile raf =newRandomAccessFile("C:\\mapfile.txt","rw");
FileChannel fc = raf.getChannel();
// 將檔案對映到記憶體中
MappedByteBuffer mbb = fc.map(FileChannel.MapMode.READ_WRITE,0,
raf.length());
while(mbb.hasRemaining()){
System.out.print((char) mbb.get());
}
mbb.put(0,(byte)98);// 修改檔案
raf.close();