1. 程式人生 > >高併發Java:NIO和AIO

高併發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

KKNftaoEMWYIdp7q.png

所有的從通道中的讀寫操作,都要經過Buffer,而通道就是io的抽象,通道的另一端就是操縱的檔案。

2. Buffer

P8idokeokUYegA5G.png

Java中Buffer的實現。基本的資料型別都有它對應的Buffer

Buffer的簡單使用例子:

  1. package test;
  2. import java.io.File;
  3. import java.io.FileInputStream
    ;
  4. import java.nio.ByteBuffer;
  5. import java.nio.channels.FileChannel;
  6. publicclassTest{
  7. publicstaticvoid main(String[] args)throwsException{
  8. FileInputStream fin =newFileInputStream(newFile(
  9. "d:\\temp_buffer.tmp"));
  10. FileChannel fc = fin.getChannel();
  11. ByteBuffer byteBuffer =ByteBuffer.allocate(1024);
  12. fc.read
    (byteBuffer);
  13. fc.close();
  14. byteBuffer.flip();//讀寫轉換
  15. }
  16. }

總結下使用的步驟是:

  1. 得到Channel

  2. 申請Buffer

  3. 建立Channel和Buffer的讀/寫關係

  4. 關閉

下面的例子是使用NIO來複制檔案:

  1. publicstaticvoid nioCopyFile(String resource,String destination)
  2. throwsIOException{
  3. FileInputStream fis =newFileInputStream(resource);
  4. FileOutputStream fos =newFileOutputStream(destination);
  5. FileChannel readChannel = fis.getChannel();// 讀檔案通道
  6. FileChannel writeChannel = fos.getChannel();// 寫檔案通道
  7. ByteBuffer buffer =ByteBuffer.allocate(1024);// 讀入資料快取
  8. while(true){
  9. buffer.clear();
  10. int len = readChannel.read(buffer);// 讀入資料
  11. if(len ==-1){
  12. break;// 讀取完畢
  13. }
  14. buffer.flip();
  15. writeChannel.write(buffer);// 寫入檔案
  16. }
  17. readChannel.close();
  18. writeChannel.close();
  19. }

Buffer中有3個重要的引數:位置(position)、容量(capactiy)和上限(limit)

7RtqFUlReL36fD6q.png

這裡要區別下容量和上限,比如一個Buffer有10KB,那麼10KB就是容量,我將5KB的檔案讀到Buffer中,那麼上限就是5KB。

下面舉個例子來理解下這3個重要的引數:

  1. publicstaticvoid main(String[] args)throwsException{
  2. ByteBuffer b =ByteBuffer.allocate(15);// 15個位元組大小的緩衝區
  3. System.out.println("limit="+ b.limit()+" capacity="+ b.capacity()
  4. +" position="+ b.position());
  5. for(int i =0; i <10; i++){
  6. // 存入10個位元組資料
  7. b.put((byte) i);
  8. }
  9. System.out.println("limit="+ b.limit()+" capacity="+ b.capacity()
  10. +" position="+ b.position());
  11. b.flip();// 重置position
  12. System.out.println("limit="+ b.limit()+" capacity="+ b.capacity()
  13. +" position="+ b.position());
  14. for(int i =0; i <5; i++){
  15. System.out.print(b.get());
  16. }
  17. System.out.println();
  18. System.out.println("limit="+ b.limit()+" capacity="+ b.capacity()
  19. +" position="+ b.position());
  20. b.flip();
  21. System.out.println("limit="+ b.limit()+" capacity="+ b.capacity()
  22. +" position="+ b.position());
  23. }

整個過程如圖:

BO41n1oZsBXr0Mc1.png

此時position從0到10,capactiy和limit不變。

k1ZMgOkg25SBjDf2.png

該操作會重置position,通常,將buffer從寫模式轉換為讀 模式時需要執行此方法 flip()操作不僅重置了當前的position為0,還將limit設定到當前position的位置 。

limit的意義在於,來確定哪些資料是有意義的,換句話說,從position到limit之間的資料才是有意義的資料,因為是上次操作的資料。所以flip操作往往是讀寫轉換的意思。

Pn8I7kg06velfNef.png

意義同上。

而Buffer中大多數的方法都是去改變這3個引數來達到某些功能的:

  1. publicfinalBuffer rewind()

將position置零,並清除標誌位(mark)

  1. publicfinalBuffer clear()

將position置零,同時將limit設定為capacity的大小,並清除了標誌mark

  1. publicfinalBuffer flip()

先將limit設定到position所在位置,然後將position置零,並清除標誌位mark,通常在讀寫轉換時使用

檔案對映到記憶體

  1. publicstaticvoid main(String[] args)throwsException{
  2. RandomAccessFile raf =newRandomAccessFile("C:\\mapfile.txt","rw");
  3. FileChannel fc = raf.getChannel();
  4. // 將檔案對映到記憶體中
  5. MappedByteBuffer mbb = fc.map(FileChannel.MapMode.READ_WRITE,0,
  6. raf.length());
  7. while(mbb.hasRemaining()){
  8. System.out.print((char) mbb.get());
  9. }
  10. mbb.put(0,(byte)98);// 修改檔案
  11. raf.close();