這節重點探讨兩個概念性的問題:什麼是NIO?為什麼要用NIO?
在引入NIO之前,有必要聊下BIO,因為NIO是相對于BIO所提出的新的Java IO api,但這裡不會深入,每本java書籍都會介紹javaIO的。
BIO:blocking IO,即阻塞IO,是java的傳統IO api,以流的方式處理資料,一般可分為檔案IO(處理檔案)和網絡IO(Socket網絡程式設計),這裡重點探讨網絡IO,通過一個小的例子,讓大家了解阻塞IO的具體含義。
服務端代碼:(代碼中用到java8中的try-with-resources寫法,可以自動關閉流和socket連接配接,jdk1.7就有了try-with-resources)
public class BIOServer {
public static void main(String[] args) {
while (true){
try(ServerSocket serverSocket = new ServerSocket(9000); //建立ServerSocket對象
//監聽用戶端的連接配接請求
final Socket socket= serverSocket.accept(); //當沒有用戶端連接配接時,就會阻塞到這裡,一直等待用戶端的連接配接請求,下面的代碼不會執行
//從連接配接中取出輸入流來接收用戶端發來的消息
InputStream is = socket.getInputStream(); //阻塞
//從連接配接中取出輸出流回應用戶端
OutputStream os = socket.getOutputStream()) {
System.out.println("我是風清揚"); //在用戶端啟動之前,不會列印輸出,因為前面的accept()方法已經使線程阻塞,等待用戶端連接配接,用戶端啟動後,該語句執行
//讀取消息
byte[] b = new byte[1024];
int len;
String clientIP = socket.getInetAddress().getHostAddress();
while ((len = is.read(b)) != -1){ //當沒有讀取到用戶端發送的消息時,就會阻塞到這裡,InputStream.read()方法下面的代碼不會執行
System.out.println(clientIP + "說:" + new String(b,0,len));
}
System.out.println("我是令狐沖"); //該語句在用戶端沒有發送消息前不會執行,隻有上面的read()方法接收到服務端發送的消息時才會執行該語句
//發送消息
Scanner scanner = new Scanner(System.in);
String msg = scanner.nextLine();
os.write(msg.getBytes());
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
用戶端代碼:
public class BIOClient {
public static void main(String[] args) {
while (true){
try(Socket socket = new Socket(InetAddress.getByName("127.0.0.1"),9000); // 建立Socket對象
//從連接配接中取出輸入流讀消息
final InputStream is = socket.getInputStream(); //阻塞
//從連接配接中取出輸出流發消息
final OutputStream os = socket.getOutputStream()) {
System.out.println("請輸入:");
Scanner scanner = new Scanner(System.in);
String msg = scanner.nextLine();
os.write(msg.getBytes());
socket.shutdownOutput();
byte[] b = new byte[1024];
int len;
while ((len = is.read(b)) != -1){ //當沒有讀取到服務端發送的消息時,就會阻塞到這裡,InputStream.read()方法下面的代碼不會執行
System.out.println("服務端說:" + new String(b));
}
System.out.println("我是逍遙子"); //該語句在服務端沒有發送消息前不會執行,隻有上面的read()方法接收到服務端發送的消息時才會執行該語句
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
執行結果分析:
BIOServer控制台輸出:
BIOClient控制台輸出:
一、什麼是NIO:
百度百科對NIO的解釋如下:
NIO:non-blocking IO,即非阻塞IO,以塊的方式處理資料,相比于BIO用流的方式處理資料,塊IO的效率要比流IO高很多,有的書籍也叫做新IO(New IO),這裡我們與BIO進行比較,non-blocking IO更加語義化,能夠突出對比性。JDK1.4中,新增了許多新的用于處理輸入輸出的類,這些類放在java.nio包下(java11是放在java.base子產品下的java.nio包中):
NIO提出了三大核心的概念:Channel(通道)、Buffer(緩沖區)、Selector(選擇器),後面的内容也是主要圍繞這三個核心概念展開,再适當加一些charset字元集的東西。相對于BIO基于 位元組流和字元流 對資料進行操作,NIO是基于Channel和Buffer對資料進行操作。讀資料的時候先将資料從通道讀取到緩沖區,寫資料的時候要将資料從緩沖區寫如到通道,Selector用于監聽多個通道的事件,進而使單個線程就可以監聽多個用戶端。下面通過安倍和特朗普通電話的事例代碼(本人屬于段子手類型,平時掃地,偶爾開車,如果例子舉的有不當的地方,還請大家包涵),讓大家了解NIO相對于BIO的非阻塞的特性,涉及到的api不了解也沒關系,後面會詳細介紹,可以把代碼運作一遍,體會下NIO的非阻塞特性。
用戶端代碼:
public class NioClient {
public static void main(String[] args) throws IOException {
//1.得到一個通道
final SocketChannel channel = SocketChannel.open();
//2.設定非阻塞方式,預設是阻塞方式,是以需要手動設定為非阻塞方式
channel.configureBlocking(false);
//3.設定服務端的ip和端口号
InetSocketAddress address = new InetSocketAddress("127.0.0.1",9000);
//4.連接配接伺服器端
if(!channel.connect(address)){ //當connect連接配接沒有成功時,嘗試再次連接配接服務端,但不能再用connect方法連接配接,此方法隻能連接配接一次,再次連接配接需要用finishConnect()方法
while (!channel.finishConnect()){ //當連接配接服務端時,用戶端不會處于阻塞狀态,還可以繼續執行下面的代碼
System.out.println("安倍:在特朗普老兄的電話還未接通之前,小弟我可以幹些其他的事情:哎呀,我大日本的網絡有點令人擔憂啊,半天了," +
"特朗普老兄的電話還是沒有接通,這老家夥,不會在做些羞羞的事情吧,不行我還是先看個蒼老師或是波多老師的劇情片吧,真有意思,先看再說!");
}
}
//5.提供一個緩沖區并存入資料
String msg = "您好,特朗普兄長,我是安倍,可否賞臉陪小弟喝個便茶,小弟最近有點迷茫啊,突然感覺沒有了高潮!";
final ByteBuffer buffer = ByteBuffer.wrap(msg.getBytes());
//6..發送資料
channel.write(buffer);
//7.當程式執行到這裡,已經執行完,channel就會關閉,伺服器端就會抛出異常,是以讓程式阻塞到這裡
System.in.read();
}
}
服務端代碼:
public class NioServer {
public static void main(String[] args) throws IOException {
//1.得到一個通道
final ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//2.得到一個Selector對象
final Selector selector = Selector.open();
//3.綁定端口号
serverSocketChannel.bind(new InetSocketAddress(9000));
//4.設定非阻塞方式
serverSocketChannel.configureBlocking(false);
//5.把ServerSocketChannel對象注冊給Selector對象
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
//6.實作業務
while (true){
//6,1 一直監控用戶端,直到有用戶端連接配接上來
if(selector.select(2000) == 0){
System.out.println("特朗普:安倍這小子好幾天就越好今天和我通話,現在也沒來,不會幹火星上去了吧,不管他了,我還是幹别的事情吧:82年拉菲," +
"加拿大總理特魯多小老弟送我的,我去找我的merry女秘書喝兩杯,cheers,大爺我要嫖了!");
continue;
}
//6.2 得到SelectionKey
final Iterator<SelectionKey> selectionKeyIterator = selector.selectedKeys().iterator();
while (selectionKeyIterator.hasNext()){
final SelectionKey selectionKey = selectionKeyIterator.next();
if(selectionKey.isAcceptable()){ //用戶端連接配接事件
System.out.println("連接配接...............................................");
final SocketChannel socketChannel = serverSocketChannel.accept();
socketChannel.configureBlocking(false);
socketChannel.register(selector,SelectionKey.OP_READ, ByteBuffer.allocate(1024));
}
if(selectionKey.isReadable()){ //讀取用戶端資料事件
final SocketChannel channel = (SocketChannel) selectionKey.channel();
final ByteBuffer buffer = (ByteBuffer) selectionKey.attachment();
channel.read(buffer);
System.out.println("用戶端發來資料:" +new String(buffer.array()));
}
//6.3 手動從目前集合中移除key,防止重複處理
selectionKeyIterator.remove();
}
}
}
}
運作結果分析:
1.隻啟動服務端,不啟動用戶端
2.隻啟動用戶端,不啟動服務端
3.啟動服務端和用戶端
通過以上的代碼舉例,想必大家對NIO的非阻塞特性應該有所了解。下面聊下NIO的今生,AIO。
AIO: asynchronous IO,即異步IO,并且是非阻塞的,也被稱作NIO2,算是對NIO的增強和補充吧,在JDK1.7中新加入的,AIO最大的特性就是異步能力,jdk1.7新增三個異步通道,用于對異步IO的支援:
1.AsynchronousFileChannel: 用于檔案異步讀寫;
2.AsynchronousSocketChannel: 用戶端異步socket;
3.AsynchronousServerSocketChannel: 伺服器異步socket。
二、為什麼要用NIO:
NIO比普通的BIO提供了功能更加強大,處理資料更快的解決方案,大大提升IO的吞吐量,常用在高性能伺服器上,在大多數涉及java高性能應用軟體中,NIO是必不可少的技術之一,例如Netty就是封裝了NIO,而網際網路微服務架構中常用的Dubbo,Elasticsearch等中間件底層網絡通信都用Netty實作,可見NIO對于高性能網絡通信的作用。
三、總結:
這個小節由BIO引出了NIO,通過将NIO與BIO相比較,突出NIO非阻塞IO的優勢,簡單介紹了什麼是NIO,回答了為什麼要用NIO的疑問。