1. 程式人生 > >Java NIO學習系列六:Java中的IO模型

Java NIO學習系列六:Java中的IO模型

  前文中我們總結了linux系統中的5中IO模型,並且著重介紹了其中的4種IO模型:

  • 阻塞I/O(blocking IO)
  • 非阻塞I/O(nonblocking IO)
  • I/O多路複用(IO multiplexing)
  • 非同步I/O(asynchronous IO)

  但是前面總結的IO模型只是限定在linux下,更偏向於作業系統底層的概念,並沒有涉及到Java應用層面,其實Java中也提供了和前面作業系統層面的IO模型相對應的概念,這是本文接下來要講的重點。

  同樣本文會圍繞如下幾點進行展開:

  I/O模型在Java中的對應

  適用場景

  Java中各種IO模型的使用方式

  總結

 

1. I/O模型在Java中的對應

1.1 阻塞I/O

  傳統Java IO提供的面向流的IO操作方式就屬於阻塞式的,呼叫其read()或write()方法的執行緒會阻塞,直到完成了資料的讀寫,在讀寫的過程中執行緒是什麼都做不了的。

1.2 非阻塞I/O

  Java NIO類庫提供了多種支援非阻塞模式的類,比如Channel、Buffer,可以將其設定為非阻塞模式,執行緒向channel請求讀資料時,只會獲取已經就緒的資料,並不會阻塞以等待所有資料都準備好,這樣在資料準備的階段執行緒就能夠去處理別的事情,這就是非阻塞式讀,對於寫資料是一樣的。

  這裡和上面阻塞的區別就是,呼叫read()或write()方法並不阻塞,而是會立即返回,但是這時候IO操作往往是還沒有結束的。

1.3 多路複用

  Java NIO中的Selector允許單個執行緒監控多個channel,可以將多個channel註冊到一個Selector中,然後可以"select"出已經準備好資料的channel,或者準備好寫入的channel,然後對其進行讀或者寫資料,這就是多路複用。

1.4 非同步IO

   非同步IO模型是比較理想的IO模型,在非同步IO模型中,當用戶執行緒發起read操作之後,立刻就可以開始去做其它的事。另一方面,核心會等待資料準備完成,然後將資料複製到使用者執行緒,當這一切都完成之後,核心會給使用者執行緒傳送一個訊號,告訴它read操作完成了。也就是說使用者執行緒完全不需要關心實際的整個IO操作了,只需要發起請求就行了,當收到核心的成功訊號時就可以直接去使用資料了。這就是和非阻塞式的區別,如果說阻塞式IO是完全手動,非阻塞式IO就是半自動,而非同步IO就是全自動,多路複用呢?我覺得可以是半自動衝鋒槍^_^

  在Java 7中,提供了Asynchronous IO,Java NIO中的AsynchronousFileChannel支援非同步模型實現的。

 

2. 適用場景

  BIO方式適用於連線數目比較小且每個連線佔用大量寬頻,這種方式對伺服器資源要求比較高,JDK1.4以前的唯一選擇,但程式直觀簡單易理解。

  NIO方式適用於連線數目多且連線比較短(輕操作)的架構,比如聊天伺服器,程式設計比較複雜,JDK1.4開始支援。

  AIO方式適用於連線數目多且連線比較長(重操作)的架構,比如相簿伺服器,充分呼叫OS參與併發操作,程式設計比較複雜,JDK7開始支援。

 

3. Java中各種IO模型的使用方式

  前面講了這麼多,即講了linux下的IO模型,又講了Java中對這些IO模型的支援,到這裡我覺得是時候找一些Java中實際的例子看看,下面就分別用三種IO模型來讀寫檔案。

3.1 通過BIO方式讀寫檔案

  public void rwByBIO() {
    BufferedReader br = null;
    FileInputStream in = null;
    FileOutputStream out = null;
    try {
      in = new FileInputStream("test.txt");
      out = new FileOutputStream("testBIO.txt");
      List<Integer> list = new ArrayList();
      int temp;
      while((temp = in.read()) != -1) {
        out.write(temp);
      }
      br = new BufferedReader(new InputStreamReader(new FileInputStream("testBIO.txt")));
      System.out.println(br.readLine());
    }catch(Exception e) {
      e.printStackTrace();
    }finally {
      if(br != null) {
        try {
          br.close();
        }catch(IOException e) {
          e.printStackTrace();
        }
      }
      if(out != null) {
        try {
          out.close();
        }catch(IOException e) {
          e.printStackTrace();
        }
      }
    }
  }

  在根目錄下準備好檔案test.txt,裡面寫上準備好的內容,比如"黃沙百戰穿金甲,不破樓蘭終不還",然後跑起來,之後應該會多出一個檔案testBIO.txt,裡面內容是一樣的。我們通過BIO的方式讀取test.txt中的內容,同樣以BIO的方式寫入到testBIO.txt中。

3.2 通過NIO讀寫檔案

  public void rwByNIO() {
    FileChannel readChannel = null;
    FileChannel writeChannel = null;
    try {
      readChannel = new RandomAccessFile(new File("test.txt"),"r").getChannel();
      writeChannel = new RandomAccessFile(new File("testNIO.txt"),"rw").getChannel();
      ByteBuffer buffer = ByteBuffer.allocate(10);
      int bytesRead = readChannel.read(buffer);
      while(bytesRead != -1) {
        buffer.flip();
        while(buffer.hasRemaining()) {
          // 寫入檔案
          writeChannel.write(buffer);
        }
        // 一次寫完之後
        buffer.clear();
        bytesRead = readChannel.read(buffer);
      }
    }catch(Exception e) {
      e.printStackTrace();
    }finally {
      if(readChannel != null) {
        try {
          readChannel.close();
        } catch (IOException e) {
          e.printStackTrace();
        }
      }
      if(writeChannel != null) {
        try {
          writeChannel.close();
        } catch (IOException e) {
          e.printStackTrace();
        }
      }
    }
  }

  這裡是通過NIO中的FileChannel來讀寫檔案,但是要注意,雖然這一節的標題是說用NIO的方式來讀寫檔案,但是FileChannel並不支援非阻塞模式,所以其實際上還是屬於阻塞的,即BIO的方式,只是因為這裡為了統一演示讀寫檔案的例子,所以仍然使用NIO中的FileChannel類來完成。

3.3 通過AIO方式讀寫檔案

    public void rwByAIO() {
        Path path = Paths.get("test.txt");
        AsynchronousFileChannel fileChannel = null;
        try {
            fileChannel = AsynchronousFileChannel.open(path, StandardOpenOption.READ);
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            long position = 0;
            Future<Integer> operation = fileChannel.read(buffer, position);
            while(!operation.isDone());
            buffer.flip();
            Path writePath = Paths.get("testAIO.txt");
            if(!Files.exists(writePath)){
                Files.createFile(writePath);
            }
            AsynchronousFileChannel writeFileChannel = AsynchronousFileChannel.open(writePath, StandardOpenOption.WRITE);

            writeFileChannel.write(buffer, position, buffer, new CompletionHandler<Integer, ByteBuffer>() {

                @Override
                public void completed(Integer result, ByteBuffer attachment) {
                    System.out.println("bytes written: " + result);
                }

                @Override
                public void failed(Throwable exc, ByteBuffer attachment) {
                    System.out.println("Write failed");
                    exc.printStackTrace();
                }
            });
          
        }catch(Exception e) {
          e.printStackTrace();
        }
      }

  這個例子中是通過非同步地方式來讀寫檔案。當呼叫了Java NIO中的AsynchronousFileChannel對這種操作提供了支援,當呼叫其read()方法時會立即返回一個Future物件,通過呼叫其isDone方法來得知資料是否讀取完畢。

 

4. 總結

  本文結合前文講到的IO模型,分別對應到Java中的具體類庫實現,並通過例子演示了BIO、NIO、AIO三種方式讀寫檔案。

  • 標準Java IO提供的面向流的方式屬於BIO模型的實現,在讀取的過程中是會阻塞的;
  • Java NIO提供的Channel和Buffer是支援NIO模式的,呼叫了Channel的讀寫方法之後可以立即返回,在往Buffer中準備資料的過程中是不阻塞的,執行緒可以做別的事情,但是從Buffer讀寫資料是阻塞的;
  • Java NIO中提供的AsynchronousFileChannel支援非同步讀寫檔案,當呼叫了其讀寫方法之後可以立即返回,只需要等待系統把資料複製到指定位置即可,整個過程都不會阻塞;

 

參考文獻

bio-vs-nio-vs-aio

linu