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

  前文中咱們總結了linux系統中的5中IO模型,而且着重介紹了其中的4種IO模型:html

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

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

  一樣本文會圍繞以下幾點進行展開:linux

  I/O模型在Java中的對應git

  適用場景github

  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操做每每是尚未結束的。dom

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

相關文章
相關標籤/搜索