1. 程式人生 > >java I/O和NI/O 詳解

java I/O和NI/O 詳解

目錄

I/O概述

字元流

位元組流

理解I/O超類結構

FileInputStream詳解 

FileOutputStream詳解

File類

新I/O

Java NIO: Channels and Buffers

Channels

 NIO FileChannel

SocketChannel

ServerSocketChannel

Buffers

Java NIO: Selectors

使用Selector的好處是什麼?

Java NIO: Non-blocking IO

Java NIO vs. IO

Stream Oriented vs. Buffer Oriented

Blocking vs. Non-blocking IO

Selectors

NIO和IO總結

BIO,NIO和AIO


I/O概述

在jdk原始碼中,I/O是一個單獨的包。I代表的是input輸入,O代表的是output輸出。java I/O就是指的java在輸入輸出上的技術。

一個程式針對一個資訊源(如一個檔案file,記憶體memory,一個埠socket)開啟一個流,並且按照順序的讀取資訊.如下圖所示:

與讀相似,一個程式也可以通過開啟一個流並且按照順序的寫資訊,來發送資訊到一個外部的目的地去,如下所示:

一個流被定義為一個數據序列。輸入流用於從源讀取資料,輸出流用於向目標寫資料。無論資料來自何處或去往何處,也不論其型別如何,用於順序讀取和寫入資料的演算法基本相同:

Reading Writing
open a stream
while more information
    read information
close the stream
open a stream
while more information
    write information
close the stream

java.io包包含一組流類,這些流類支援這些用於讀取和寫入的演算法。要使用這些類,程式需要匯入Java.io。流類根據其操作的資料型別(字元或位元組)分為兩個類層次結構:Character Streams字元流,Byte Streams位元組流。

字元流

Reader和Writer是java.io中字元流的抽象超類。Reader為讀取讀取16位字元的流提供了API和部分實現,而Writer為寫入器(寫入16位字元的流)提供API和部分實現。Reader和Writer的子類實現專用流,並被分成兩類:從資料接收器讀取或寫入資料接收器(在下面的圖中以灰色顯示)和執行某種處理(以白色顯示)的那些類:

大多數程式應該使用readers和writers來讀和寫文字資訊。原因是它們可以處理Unicode字符集中的任何字元,而位元組流僅限於ISO-Latin-1 8位位元組。

位元組流

為了讀寫8位位元組,程式應該使用位元組流、InputStream和OutputStream的後代。InputStream和OutputStream為輸入流(讀取8位位元組的流)和輸出流(寫入8位位元組的流)提供API和部分實現。這些流通常用於讀取和寫入二進位制資料,例如影象和聲音。物件流序列化中使用了兩個位元組流類ObjectInputStream 和ObjectOutputStream流。這些類在物件序列化中被覆蓋。

與Reader和Writer一樣,InputStream和OutputStream的子類提供了專門的I/O功能,可以分為兩類,如下面的類層次圖所示:資料匯流(灰色)和資料處理流(白色):

下圖是一個描述輸入流和輸出流的類層次圖:

理解I/O超類結構

Reader和InputStream定義了類似的API,但對於不同的資料型別。例如,Reader包含讀取字元和字元陣列的方法:

int read()
int read(char cbuf[])
int read(char cbuf[], int offset, int length)

InputStream 定義了相同的方法,但用於讀取位元組和位元組陣列:

int read()
int read(byte cbuf[])
int read(byte cbuf[], int offset, int length)

此外,Reader和InputStream都提供了用於標記流中的位置、跳過輸入和重置當前位置的方法。

Writer和OutputStream 同樣是類似的的。Writer定義了用於書寫字元和字元陣列的方法

int write(int c)
int write(char cbuf[])
int write(char cbuf[], int offset, int length)

OutputStream流定義了相同的方法,但對於位元組:

int write(int c)
int write(byte cbuf[])
int write(byte cbuf[], int offset, int length)

所有流,讀取器readers、寫入器writers、輸入流input streams和輸出流output streams——都是在建立時自動開啟的。可以通過呼叫它的關閉方法顯式關閉任何流。或者垃圾收集器可以隱式關閉它,這是在不再引用物件時發生的(由垃圾收集器呼叫處理)。

FileInputStream詳解 

該流用於從檔案讀取資料,它的物件可以用關鍵字 new 來建立。

有多種構造方法可用來建立物件

可以使用字串型別的檔名來建立一個輸入流物件來讀取檔案

InputStream f = new FileInputStream("C:/java/hello");

也可以使用一個檔案物件來建立一個輸入流物件來讀取檔案。我們首先得使用 File() 方法來建立一個檔案物件:

File f = new File("C:/java/hello");
InputStream out = new FileInputStream(f);

建立了InputStream物件,就可以使用下面的方法來讀取流或者進行其他的流操作:

public void close() throws IOException{} 關閉此檔案輸入流並釋放與此流有關的所有系統資源。丟擲IOException異常。
protected void finalize()throws IOException {} 這個方法清除與該檔案的連線。確保在不再引用檔案輸入流時呼叫其 close 方法。丟擲IOException異常。
public int read(int r)throws IOException{} 這個方法從 InputStream 物件讀取指定位元組的資料。返回為整數值。返回下一位元組資料,如果已經到結尾則返回-1。
public int read(byte[] r) throws IOException{} 這個方法從輸入流讀取r.length長度的位元組。返回讀取的位元組數。如果是檔案結尾則返回-1。
public int available() throws IOException{}
返回下一次對此輸入流呼叫的方法可以不受阻塞地從此輸入流讀取的位元組數。返回一個整數值。

FileOutputStream詳解

該類用來建立一個檔案並向檔案中寫資料。

如果該流在開啟檔案進行輸出前,目標檔案不存在,那麼該流會建立該檔案。

有兩個構造方法可以用來建立 FileOutputStream 物件。

使用字串型別的檔名來建立一個輸出流物件:

OutputStream f = new FileOutputStream("C:/java/hello")

也可以使用一個檔案物件來建立一個輸出流來寫檔案。我們首先得使用File()方法來建立一個檔案物件:

File f = new File("C:/java/hello");
OutputStream f = new FileOutputStream(f);

建立OutputStream 物件完成後,就可以使用下面的方法來寫入流或者進行其他的流操作。

public void close() throws IOException{} 關閉此檔案輸入流並釋放與此流有關的所有系統資源。丟擲IOException異常。
protected void finalize()throws IOException {} 這個方法清除與該檔案的連線。確保在不再引用檔案輸入流時呼叫其 close 方法。丟擲IOException異常。
public void write(int w)throws IOException{} 這個方法把指定的位元組寫到輸出流中。
public void write(byte[] w) 把指定陣列中w.length長度的位元組寫到OutputStream中。

File類

建立目錄

File類中有兩個方法可以用來建立資料夾:

  • mkdir( )方法建立一個資料夾,成功則返回true,失敗則返回false。失敗表明File物件指定的路徑已經存在,或者由於整個路徑還不存在,該資料夾不能被建立。
  • mkdirs()方法建立一個資料夾和它的所有父資料夾。 

實現邏輯分析, mkdir方法的實現是通過native方法實現的,而mkdirs方法的實現邏輯則是藉助於mkdir方法實現。

讀取目錄

一個目錄其實就是一個 File 物件,它包含其他檔案和資料夾。

如果建立一個 File 物件並且它是一個目錄,那麼呼叫 isDirectory() 方法會返回 true。

可以通過呼叫該物件上的 list() 方法,來提取它包含的檔案和資料夾的列表。

下面展示的例子說明如何使用 list() 方法來檢查一個資料夾中包含的內容:

刪除目錄或檔案

刪除檔案可以使用 java.io.File.delete()方法。

新I/O

在java1.4當中引入了新的java I/O類庫,java.nio,java.nio, 全稱java non-blocking IO,其目的在於提高速度, 新的io為所有的原始型別(boolean型別除外)提供快取支援的資料容器,使用它可以提供非阻塞式的高伸縮性網路。實際上,舊的I/O包已經使用nio重新實現過了,以便充分利用這種速度提高。因此我們即便不顯式的使用nio編寫程式碼,也能從中受益。NI/O是用來替代標準IO的一個方案。

Java NIO提供了一種和標準IO API不同的方式來處理IO. JAVA NIO由以下核心元件組成:

  • Channels
  • Buffers
  • Selectors

Java NIO: Channels and Buffers

Channels

在標準的IO API當中我們處理的是位元組流和字元流,在NIO當中我們處理的是channels和buffers. channel和流有一點相似,資料總是從一個channel讀到buffer裡面,或者從一個buffer寫到某個channel裡面,如下圖所示

JAVA NIO的channel和流類似但是有以下幾個差異:

  • 你可以從一個channel讀和寫。但流通常是單向的(只能是讀或寫)。
  • Channels 可以進行非同步的讀和寫
  • Channels 的讀和寫永遠都是針對的一個Buffer

在java的nio包裡面,主要的channel實現類如下:

  • FileChannel
  • DatagramChannel
  • SocketChannel
  • ServerSocketChannel

我們可以分辨出來,這些channels的實現包含了UDP,TCP網路IO和 file IO。

FileChannel從檔案files可以讀寫資料

DatagramChannel可以通過UDP協議從網路讀寫資料

DatagramChannel可以通過TCP協議從網路讀寫資料

ServerSocketChannel可以讓你像web server一樣監聽發來的TCP連線。對於每一個發來的連線都會有一個SocketChannel被建立。

 NIO FileChannel

Java NIO的FileChannel是連線到檔案的一個channel。使用FileChannel,可以讀取檔案中的資料,並將資料寫入檔案。JavaNIO檔案通道類FileChannel是標準Java IO API讀取檔案功能的替代方案。

開啟一個FileChannel

在使用FileChannel之前,必須開啟它。不能直接開啟FileChannel, 我們需要通過InputStream、OutputStream或RandomAccessFile獲取FileChannel。下面是如何通過RandomAccessFile開啟FileChannel的例子程式碼:

RandomAccessFile aFile     = new RandomAccessFile("data/nio-data.txt", "rw");
FileChannel      inChannel = aFile.getChannel();

從FileChannel讀取資料

若要從FileChannel讀取資料,需要呼叫其中一個read()方法。下面是一個例子:

ByteBuffer buf = ByteBuffer.allocate(48);

int bytesRead = inChannel.read(buf);

首先我們分配了一個Buffer,然後從FileC含泥量讀到的資料會讀入到Buffer裡面。

其次需要呼叫FileChannel的read方法,這個方法會把FileChannel中的data讀取到Buffer當中去。返回的int型別的值表明了有多少bytes的資料被寫入了Buffer.如果返回了-1,那麼說明讀到了檔案結束的地方(?)

把data寫入FileChannel

寫入資料到FileChannel是通過呼叫FileChannel的write方法完成的,這個方法會接收Buffer作為它的引數.下面是一個例子:

String newData = "New String to write to file..." + System.currentTimeMillis();

ByteBuffer buf = ByteBuffer.allocate(48);
buf.clear();
buf.put(newData.getBytes());

buf.flip();

while(buf.hasRemaining()) {
    channel.write(buf);
}

注意,FileChannel的write方法是在一個while迴圈內部被呼叫的。沒有一個保證有多少bytes的資料通過write方法被寫入到了FileChannel當中。因此我們不停的反覆呼叫write方法直到沒有更多的資料需要寫入。

關閉一個FileChannel

在使用完FileChannel之後我們必須關閉它,關閉的程式碼如下:

channel.close();    

FileChannel的大小

FileChannel的size方法返回這個FileChannel連線到的file檔案的大小:

long fileSize = channel.size();  

檔案截斷

您可以通過呼叫 FileChannel.truncate()方法截斷檔案。截斷檔案時,按給定長度切斷檔案。

channel.truncate(1024);

這個示例以長度為1024個位元組截斷檔案。

FileChannel Force

FileChannel的force方法將所有未寫入資料從通道重新整理到磁碟。由於效能原因,作業系統可能會在記憶體中快取資料,所以在呼叫.force()方法之前,不能保證寫入通道的資料實際上是寫入磁碟的。

channel.force(true);

下面是一個使用FileChannel把資料讀到Buffer的例子:

    RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw");
    FileChannel inChannel = aFile.getChannel();

    ByteBuffer buf = ByteBuffer.allocate(48);

    int bytesRead = inChannel.read(buf);
    while (bytesRead != -1) {

      System.out.println("Read " + bytesRead);
      buf.flip();

      while(buf.hasRemaining()){
          System.out.print((char) buf.get());
      }

      buf.clear();
      bytesRead = inChannel.read(buf);
    }
    aFile.close();

SocketChannel

Java NIO SocketChannel 是連線到TCP網路套接字的channel實現。它是和Java網路的套接字一樣的Java NIO。建立SocketChannel 有兩種方法:

  1. 開啟一個SocketChannel ,並連線到Internet上的某個伺服器。
  2. 當connection到達一個ServerSocketChannel時,可以建立一個SocketChannel 。

SocketChanne的一些基本用法:

//開啟一個SocketChannel
SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress("http://jenkov.com", 80));

socketChannel.close();  //關閉一個SocketChannel

//從一個SocketChannel讀資料
ByteBuffer buf = ByteBuffer.allocate(48);
int bytesRead = socketChannel.read(buf);

寫資料到一個SocketChannel

String newData = "New String to write to file..." + System.currentTimeMillis();

ByteBuffer buf = ByteBuffer.allocate(48);
buf.clear();
buf.put(newData.getBytes());

buf.flip();

while(buf.hasRemaining()) {
    channel.write(buf);
}

注意,SocketChannel的write方法是在一個while迴圈內部被呼叫的。沒有一個保證有多少bytes的資料通過write方法被寫入到了SocketChannel當中。因此我們不停的反覆呼叫write方法直到沒有更多的資料需要寫入。這一點與FileChannel是一樣的。

Non-blocking Mode非阻塞模式

可以將SocketChannel設定為非阻塞模式。這樣設定之後,可以在非同步模式下呼叫connect()、Read()和write()方法。

Non-blocking Mode 和Selectors

SocketChannel的非阻塞模式和Selector一起工作得更好。通過向Selector註冊一個或多個SocketChannel,您可以向Selector請求已經準備好 reading, writing的通道。

ServerSocketChannel

Java NIO ServerSocketChannel是一個可以偵聽傳入的TCP連線的通道,就像標準Java網路中的ServerSocket 一樣。ServerSocketChannel 類位於JavaNio.channels 包中。下面是一個例子:

ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

serverSocketChannel.socket().bind(new InetSocketAddress(9999));

while(true){
    SocketChannel socketChannel =
            serverSocketChannel.accept();

    //do something with socketChannel...
}

一些基本用法

//開啟一個ServerSocketChannel
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//關閉一個ServerSocketChannel
serverSocketChannel.close();
//監聽連線
while(true){
    SocketChannel socketChannel =
            serverSocketChannel.accept();

    //do something with socketChannel...
}

Non-blocking Mode非阻塞模式

可以將ServerSocketChannel設定成非阻塞模式。在非阻塞模式下,accept()方法立即返回,如果沒有傳入連線則返回空值。因此,您必須檢查返回的SOCKET通道是否為NULL。下面是一個例子:

ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

serverSocketChannel.socket().bind(new InetSocketAddress(9999));
serverSocketChannel.configureBlocking(false);

while(true){
    SocketChannel socketChannel =
            serverSocketChannel.accept();

    if(socketChannel != null){
        //do something with socketChannel...
        }
}

Buffers

下面是Java NIO中核心的Buffer實現的列表:

  • ByteBuffer
  • CharBuffer
  • DoubleBuffer
  • FloatBuffer
  • IntBuffer
  • LongBuffer
  • ShortBuffer

這些Buffer的實現類覆蓋了可以通過IO傳送的基本資料型別,包括:byte, short, int, long, float, double 和 characters。

當我們和NIO的channel互動式需要用到Buffers。資料是從channel讀入緩衝區buffer,並從緩衝區buffer寫入通道。

Buffer基本用法

使用緩衝區Buffer讀取和寫入資料通常遵循這4步:

  1. 寫入資料到Buffer當中
  2. 呼叫buffer.flip()方法
  3. 從Buffer當中讀取資料
  4. 呼叫buffer.clear() 或者 buffer.compact()方法

當你將資料寫入緩衝區時,緩衝區會記錄你已經寫入了多少資料。一旦需要讀取資料的時候,就需要呼叫flip()方法將緩衝區從寫入模式(writing model)切換到讀取模式(reading model)。在讀取模式下,緩衝區允許你讀取已經寫入緩衝區的所有資料。

一旦讀取了所有資料,就需要清除緩衝區,以便再次準備寫入。您可以通過兩種方式來執行這一操作:呼叫clear()方法或呼叫compact()方法。clear方法將會清除整個緩衝區。compact方法只清除您已經讀取的資料。所有的未讀資料都會被移動到緩衝器的開頭,並且新寫入的資料將被寫入在緩衝區的未讀資料之後。

下面是一個簡單的Buffer使用示例,包含了write, flip, read and clear 方法:

RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw");
FileChannel inChannel = aFile.getChannel();

//create buffer with capacity of 48 bytes
ByteBuffer buf = ByteBuffer.allocate(48);

int bytesRead = inChannel.read(buf); //read into buffer.
while (bytesRead != -1) {

  buf.flip();  //make buffer ready for read

  while(buf.hasRemaining()){
      System.out.print((char) buf.get()); // read 1 byte at a time
  }

  buf.clear(); //make buffer ready for writing
  bytesRead = inChannel.read(buf);
}
aFile.close();

Buffer 容量、位置和極限

緩衝區本質上是一個記憶體塊,您可以在其中寫入資料,然後再讀取。這個記憶體塊包裝在一個NIO緩衝物件中(NIO Buffer object),該物件提供了一系列的方法來讓我們更加輕易的使用記憶體塊。

緩衝區有三個需要熟悉的屬性,可以幫助我們理解緩衝區是如何工作的:

  • capacity
  • position
  • limit

position和limit的含義取決於Buffer是處於讀還是寫模式。不管buffer模式如何,capacity容量總是相同的。

下面是寫和讀模式下的容量、位置和限制的說明:

Capacity

作為一個記憶體塊,緩衝器具有一定的固定大小,也稱其“容量”。只能將容量位元組、長度、字元等寫入緩衝區。一旦緩衝區滿了,您就需要清空它(讀取資料,或清除資料),然後再寫入更多的資料。

position

當您將資料寫入緩衝區時,您在某個位置上這樣做。最初的位置是0。當位元組、長等被寫入緩衝區時,位置被提前指向緩衝區中的下一個單元格,以便將資料插入其中。位置可以最大限度地變為容量1。

當你從緩衝區讀取資料時,你也可以從給定的位置讀取資料。當將緩衝區從寫入模式翻轉到讀取模式時,該位置被重置為0。當您從緩衝區讀取資料時,您從位置讀取資料,然後將位置提前到下一個位置進行讀取。

limit

在寫入模式中,緩衝區的限制是可以寫入緩衝區的資料量的限制。在寫入模式中,限制等於緩衝器的容量。

當將緩衝區翻轉為讀取模式時,限制意味著可以從資料中讀取多少資料的限制。因此,當將緩衝器翻轉為讀取模式時,限制設定為寫入模式的寫入位置。換句話說,您可以讀取與寫入的位元組數一樣多的位元組(限制設定為寫入的位元組數,以位置標記)。

分配一個Buffer

下面的程式碼是一個分配一個固定容量的buffer的例子

//Here is an example showing the allocation of a ByteBuffer, with a capacity of 48 bytes:
ByteBuffer buf = ByteBuffer.allocate(48);

//Here is an example allocating a CharBuffer with space for 1024 characters:
CharBuffer buf = CharBuffer.allocate(1024);

Java NIO: Selectors

Java NIO包含“selectors”的概念。一個selector是一個可以監視多個通道channels的事件(例如:連線開啟、資料到達等)的物件。因此,單個執行緒可以監視多個通道channels以獲取資料,例如,在聊天軟體的伺服器中。

使用Selector的好處是什麼?

使用單個執行緒來處理多個通道channels的優點是隻需要更少的執行緒來處理通道。實際上,我們只能使用一個執行緒來處理所有的通道。執行緒之間的切換對於作業系統來說是昂貴的,並且每個執行緒也佔用了作業系統中的一些資源(記憶體)。因此,使用的執行緒越少越好。

我們需要記住的是,現代作業系統和CPU在多工中變得越來越好,因此多執行緒的開銷隨著時間的推移變得越來越小。事實上,如果CPU有多個核心,你不使用多執行緒處理的話可能會浪費CPU功率。不管怎樣,設計討論屬於不同的文字。這裡我們只考慮使用單個執行緒通過一個Selector來處理多個channels,。

下面是一個執行緒通過使用一個Selector來處理3個Channel的說明:

要使用Selector就要用它去註冊一個Channel。然後你可以call這個Selector的select()方法。此方法將阻塞,直到接收到一個註冊的channel準備好的event。一旦方法返回,執行緒就可以處理這些事件。Event的例子有傳入連線、資料接收等。

建立一個Selector

我們可以通過呼叫 Selector.open() 方法來建立一個Selector的例項

Selector selector = Selector.open();

註冊channel到一個Selector

為了使用Selector和Channel,必須用Selector註冊Channel。這是使用SelectableChannel.register()方法完成的,如下所示:

channel.configureBlocking(false);

SelectionKey key = channel.register(selector, SelectionKey.OP_READ);

通道必須處於非阻塞模式non-blocking mode,以便與Selector一起使用。這意味著不能使用Selector 和FileChannel ,因為檔案FileChannel不能切換到非阻塞模式。Socket channels比較適用於這種場景。

注意register()方法的第二個引數(即上面的SelectionKey.OP_READ)。這是一個interest set,意思是通過Selector針對一個channel你想監聽的events事件是什麼。有四個不同型別的事件可以監聽

  1. Connect
  2. Accept
  3. Read
  4. Write

 一個成功連線上另一個server的channel的狀態就是"connect" ready, 一個server的socket channel接收了一個連線之後的狀態就是'Accept' ready狀態. 一個channel如果有準備好被讀取的data那麼它的狀態就是'read' ready,一個channel的狀態是準備好可以寫入data,那麼它的狀態就是‘write’ ready.

這四個事件有四個對應的SelectionKey常數表示,分別是:

  1. SelectionKey.OP_CONNECT
  2. SelectionKey.OP_ACCEPT
  3. SelectionKey.OP_READ
  4. SelectionKey.OP_WRITE

如果你想監聽不止一個事件,則可以按照下面這種方式來定義

int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;  

下面的程式碼是一個完整的Selector的用法的例子:

Selector selector = Selector.open();

channel.configureBlocking(false);

SelectionKey key = channel.register(selector, SelectionKey.OP_READ);


while(true) {

  int readyChannels = selector.select();

  if(readyChannels == 0) continue;


  Set<SelectionKey> selectedKeys = selector.selectedKeys();

  Iterator<SelectionKey> keyIterator = selectedKeys.iterator();

  while(keyIterator.hasNext()) {

    SelectionKey key = keyIterator.next();

    if(key.isAcceptable()) {
        // a connection was accepted by a ServerSocketChannel.

    } else if (key.isConnectable()) {
        // a connection was established with a remote server.

    } else if (key.isReadable()) {
        // a channel is ready for reading

    } else if (key.isWritable()) {
        // a channel is ready for writing
    }

    keyIterator.remove();
  }
}

Java NIO: Non-blocking IO

Java NIO使你能夠使用非阻塞IO。例如,執行緒可以讓一個channel將資料讀入某個緩衝區buffer當中。當通道channel將資料讀入緩衝區時,執行緒可以做其他事情。一旦資料讀入緩衝區buffer,執行緒就可以繼續處理它。將資料寫入通道也是一樣的。

Java NIO vs. IO

下表總結了Java NIO和IO之間的主要區別

IO NIO
Stream oriented 面向流 Buffer oriented面向緩衝
Blocking IO阻塞式IO Non blocking IO非阻塞式IO
  Selectors

Stream Oriented vs. Buffer Oriented

Java NIO和IO之間的第一個最大區別是IO是面向流的,其中NIO是面向緩衝區的。那麼,這意味著什麼呢?

以流為導向的Java IO意味著每次從一個流讀取一個或多個位元組。你用讀位元組做什麼取決於你。它們不在任何地方快取。此外,不能在流中的資料中來回移動。如果需要在資料流中來回移動,則需要先在緩衝區中快取資料。

Java NIO的面向緩衝的方法略有不同。將資料讀入緩衝區,從中緩衝區稍後進行處理。您可以在緩衝區中來回移動,如您需要的那樣。這使您在處理過程中有更多的靈活性。但是,您還需要檢查緩衝區是否包含您需要的所有資料以便完全處理它。而且,您需要確保在向緩衝區讀取更多資料時,不會覆蓋尚未處理的緩衝區中的資料。

Blocking vs. Non-blocking IO

Java IO的各種流正在阻塞。這意味著,當執行緒呼叫read()或write()時,該執行緒將被阻塞,直到有要讀取的資料或資料被完全寫入為止。同時執行緒也不能做任何其他事情。

JAVA NIO的非阻塞模式允許執行緒請求從通道讀取資料,並且只獲取當前可用的內容,或者根本沒有任何資料,如果當前沒有可用的資料。執行緒可以繼續進行其他操作,而不是在資料變得可讀取之前保持阻塞。

對於非阻塞寫作也是如此。一個執行緒可以請求一些資料寫入一個通道,而不是等待它被完全寫入。然後執行緒可以繼續執行,同時做一些其他的事情。

當執行緒在IO呼叫中未被阻塞時,它們的空閒時間通常在其他通道上執行IO。也就是說,單個執行緒現在可以管理多個輸入和輸出通道。

Selectors

JAVA NIO的Selectors允許單個執行緒監視多個輸入通道。您可以用Selectors註冊多個通道,然後使用單個執行緒來“選擇”具有可供處理的輸入的通道,或者選擇準備寫入的通道。這個Selectors機制使單個執行緒易於管理多個通道。

NIO和IO總結

NIO允許您僅使用一個(或幾個)執行緒來管理多個通道(網路連線或檔案),但代價是解析資料可能比從阻塞流讀取資料要複雜一些。

如果需要同時管理數千個開啟的連線(每個連線只發送少量資料,例如聊天伺服器),那麼在NIO中實現伺服器可能是一個優勢。類似地,如果您需要保持到其他計算機的大量開放連線,例如,在P2P網路中,使用單個執行緒來管理所有出站連線可能是一個優勢。這一個執行緒,多個連線設計在這個圖中說明:

如果具有較高頻寬的連線較少,一次傳送大量資料,那麼經典的IO伺服器實現可能是最合適的。這個圖說明了一個經典的IO伺服器設計:

BIO,NIO和AIO

BIO : Blocking IO即阻塞IO,即傳統的java IO技術。

NIO:new IO。同步非阻塞IO。在jdk1.4之後引入。詳細資訊在上面已經介紹。

國內有很多技術部落格將英文翻譯成No-Blocking I/O,非阻塞I/O模型 ,當然這樣就與BIO形成了鮮明的特性對比。NIO本身是基於事件驅動的思想來實現的,其目的就是解決BIO的大併發問題,在BIO模型中,如果需要併發處理多個I/O請求,那就需要多執行緒來支援,NIO使用了多路複用器機制,以socket使用來說,多路複用器通過不斷輪詢各個連線的狀態,只有在socket有流可讀或者可寫時,應用程式才需要去處理它,線上程的使用上,就不需要一個連線就必須使用一個處理執行緒了,而是隻是有效請求時(確實需要進行I/O處理時),才會使用一個執行緒去處理,這樣就避免了BIO模型下大量執行緒處於阻塞等待狀態的情景。

AIO:Asynchronous I/O,非同步非阻塞I/O。在jdk1.7中引入。

Java AIO就是Java作為對非同步IO提供支援的NIO.2 ,Java NIO2 (JSR 203)定義了更多的 New I/O APIs, 提案2003提出,直到2011年才釋出, 最終在JDK 7中才實現

socket

本質為實現兩個程序之間的通訊

參考: https://www.math.uni-hamburg.de/doc/java/tutorial/essential/io/overview.html

http://www.jfox.info/java-nio-jieshao.html