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

linux-io


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM