Nio與IO的區別 原有的 IO 是面向流的、阻塞的,NIO 則是面向塊的、非阻塞的。 1.IO流每次從流中讀一個或多個位元組,直至讀完所有位元組,他們沒有被緩存在其他地方,並且,IO流不能移動流中的數據,如果需要前後移動從流中讀取的教據,需要先將它緩存到一個緩衝區。Java NIO的緩衝導向方法略有不 ...
Nio與IO的區別
原有的 IO 是面向流的、阻塞的,NIO 則是面向塊的、非阻塞的。
1.IO流每次從流中讀一個或多個位元組,直至讀完所有位元組,他們沒有被緩存在其他地方,並且,IO流不能移動流中的數據,如果需要前後移動從流中讀取的教據,需要先將它緩存到一個緩衝區。Java NIO的緩衝導向方法略有不同。數據讀取到一個它稍後處理的緩衝區,霱要時可在緩衝區中前後移動。這就增加了處理過程中的靈活性。但是,還需要檢查是否該緩衝區中包含所有您需要處理的數裾。而且,需確保當更多的數據讀入緩衝區時,不要覆蓋緩衝區里尚未處理的數據。
2.Java IO的各種流是阻塞的。這意味著,當一個線程調用read() 或 write()時,該線程被阻塞,直到有一些數據被讀取,或數據完全寫入。該線程在此期間不能再乾任何事情了。 Java NIO的非阻塞模式,使一個線程從某通道發送請求讀取數據,但是它僅能得到目前可用的數據,如果目前沒有數據可用時,就什麼都不會獲取。而不是保持線程阻塞,所以直至數據變的可以讀取之前,該線程可以繼續做其他的事情。 非阻塞寫也是如此。一個線程請求寫入一些數據到某通道,但不需要等待它完全寫入,這個線程同時可以去做別的事情。 線程通常將非阻塞IO的空閑時間用於在其它通道上執行IO操作,所以一個單獨的線程現在可以管理多個輸入和輸出通道(channel)。、
通道Channel
NIO的通道類似於流,但有些區別如下:
1. 通道可以同時進行讀寫,而流只能讀或者只能寫
2. 通道可以實現非同步讀寫數據
3. 通道可以從緩衝讀數據,也可以寫數據到緩衝:
緩衝區Buffer類
緩衝區是一個對象,有四個基本屬性,Nio的讀寫都是利用Buffer來實現的,簡而言之,讀取數據先從緩衝區讀,寫數據也是先寫入緩衝區的。我們最常用的是ByteBuffer這個實現類,對於Java中的基本類型都有一個對應的Buffer實現類與之對應,如CharBuffer,DoubleBuffer等等
1丶其中的四個屬性的含義分別如下:
容量(Capacity):緩衝區能夠容納的數據元素的最大數量。這一個容量在緩衝區創建時被設定,並且永遠不能改變。
上界(Limit):緩衝區的第一個不能被讀或寫的元素。或者說,緩衝區中現存元素的計數。
位置(Position):下一個要被讀或寫的元素的索引。位置會自動由相應的 get( )和 put( )函數更新。
標記(Mark):下一個要被讀或寫的元素的索引。位置會自動由相應的 get( )和 put( )函數更新。
2丶Buffer的常見方法如下所示:
flip(): 寫模式轉換成讀模式
rewind():將 position 重置為 0 ,一般用於重覆讀。
clear() :
compact(): 將未讀取的數據拷貝到 buffer 的頭部位。
mark(): reset():mark 可以標記一個位置, reset 可以重置到該位置
3丶讀取操作
1 FileInputStream inputStream = new FileInputStream("E:\\A.txt"); 2 /** 3 * 拿到通道 4 */ 5 FileChannel channel = inputStream.getChannel(); 6 7 /** 8 * 創建緩存區 9 */ 10 ByteBuffer buffer = ByteBuffer.allocate(1024); 11 12 /** 13 * 讀取數據到緩衝區 14 */ 15 channel.read(buffer); 16 17 buffer.flip(); 18 19 while (buffer.remaining() > 0){ 20 byte b = buffer.get(); 21 System.out.println(((char)b)); 22 } 23 /** 24 * 關閉流 25 */ 26 inputStream.close();View Code
4丶寫入操作
1 static private final byte message[] = { 83,83,83,83,83,83 }; 2 3 static public void main( String args[] ) throws Exception { 4 FileOutputStream fout = new FileOutputStream( "e:\\A.txt" ); 5 6 FileChannel fc = fout.getChannel(); 7 8 ByteBuffer buffer = ByteBuffer.allocate( 1024 ); 9 10 for (int i=0; i<message.length; ++i) { 11 buffer.put( message[i] ); 12 } 13 14 buffer.flip(); 15 16 fc.write( buffer ); 17 18 fout.close(); 19 }View Code
選擇器Selector
可以檢測多個NIO channel,看看讀或者寫事件是否就緒。多個Channel以事件的方式可以註冊到同一個Selector,從而達到用一個線程處理多個請求成為可能。
使用NIO中非阻塞IO編寫伺服器處理程式,有三個步驟
1.向Selector對象註冊感興趣的事件
2.從Selector中獲取感興趣的事件
3.根據不同事件進行相應的處理
簡單API介紹
open:創建selector
selectKeys:獲取可用channel的集合
select:選擇就緒的通道
簡單聊天室實現思路代碼
伺服器代碼
1 public class NioServer { 2 3 4 public void start() throws Exception { 5 /** 6 * 1.創建selector 7 */ 8 Selector selector = Selector.open(); 9 /** 10 * 2.通過ServerSocketChannel創建channel 11 */ 12 ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); 13 14 /** 15 * 3.為channel通道綁定監聽埠 16 */ 17 serverSocketChannel.bind(new InetSocketAddress(8000)); 18 /** 19 * 4.設置channel 為非阻塞模式 20 */ 21 serverSocketChannel.configureBlocking(false); 22 /** 23 * 5.將channel 註冊到selector,監聽連接 24 */ 25 serverSocketChannel.register(selector,SelectionKey.OP_ACCEPT); 26 System.out.println("伺服器啟動成功"); 27 /** 28 * 6.迴圈等待新接入的連接 29 */ 30 for(;;){ 31 int select = selector.select(); 32 if (select == 0){ 33 continue; 34 } 35 36 /** 37 * 7.獲取可用channel的集合 38 */ 39 Set<SelectionKey> keys = selector.selectedKeys(); 40 Iterator<SelectionKey> iterator = keys.iterator(); 41 while (iterator.hasNext()){ 42 SelectionKey selectionKey = (SelectionKey) iterator.next(); 43 /** 44 * 8.移除selctionKey 45 */ 46 iterator.remove(); 47 /** 48 * 處理具體業務邏輯 49 */ 50 /** 51 * 接入事件 52 */ 53 if (selectionKey.isAcceptable()){ 54 acceptHandler(serverSocketChannel,selector); 55 } 56 /** 57 * 可讀事件 58 */ 59 if(selectionKey.isReadable()){ 60 readHandler(selectionKey, selector); 61 } 62 } 63 64 65 } 66 67 68 /** 69 * 根據就緒狀態,調用對應方法處理業務邏輯 70 */ 71 72 } 73 74 /** 75 * 接入事件處理器 76 */ 77 private void acceptHandler(ServerSocketChannel serverSocketChannel, Selector selector) throws Exception { 78 /** 79 * 如果是接入事件 創建serverSocket 80 */ 81 SocketChannel socketChannel = serverSocketChannel.accept(); 82 /** 83 * 設置非阻塞 84 */ 85 socketChannel.configureBlocking(false); 86 /** 87 * 註冊進selector中 88 */ 89 socketChannel.register(selector, SelectionKey.OP_READ); 90 /** 91 * 回覆服務端信息 92 */ 93 socketChannel.write(Charset.forName("UTF-8").encode("你與聊天室里其他人都不是朋友關係,請註意隱私安全")); 94 95 } 96 97 private void readHandler(SelectionKey selectionKey, Selector selector) throws Exception{ 98 /** 99 * 要用selectionKey中獲取已經就緒的channel 100 */ 101 SocketChannel channel = (SocketChannel)selectionKey.channel(); 102 /** 103 * 創建buffer 104 */ 105 ByteBuffer buffer = ByteBuffer.allocate(1024); 106 /** 107 * 迴圈讀取客戶端數據 108 */ 109 String request = ""; 110 while (channel.read(buffer) > 0){ 111 /** 112 * 切換讀模式 113 */ 114 buffer.flip(); 115 /** 116 * 讀取buffer中的內容 117 */ 118 request += Charset.forName("UTF-8").decode(buffer); 119 120 } 121 /** 122 * 講channel註冊到selector上 123 */ 124 channel.register(selector, SelectionKey.OP_READ); 125 /** 126 * 講客戶端發送的請求信息,廣播給其他客戶端 127 */ 128 if (request.length() > 0){ 129 broadCast(selector, channel, request); 130 } 131 } 132 133 private void broadCast(Selector selector, SocketChannel socketChannel, String request){ 134 /** 135 * 獲取到已接入的客戶端hannel 136 */ 137 Set<SelectionKey> selectionKeys = selector.keys(); 138 selectionKeys.forEach(selectionKey -> { 139 Channel channel = selectionKey.channel(); 140 if (channel instanceof SocketChannel && 141 channel != socketChannel){ 142 try { 143 //將信息發送到channel客戶端 144 ((SocketChannel) channel).write(Charset.forName("UTF-8").encode(request)); 145 } catch (IOException e) { 146 e.printStackTrace(); 147 } 148 } 149 }); 150 /** 151 * 迴圈向所有channel廣播信息 152 */ 153 } 154 /** 155 * 156 * @param args 157 */ 158 public static void main(String[] args) throws Exception { 159 NioServer server = new NioServer(); 160 server.start(); 161 } 162 }View Code
客戶端代碼
1 public class NioClient { 2 3 4 public void start() throws Exception { 5 /** 6 * 連接伺服器 7 */ 8 SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 8000)); 9 /** 10 * 接收伺服器地址 11 */ 12 Selector selector = Selector.open(); 13 socketChannel.configureBlocking(false); 14 socketChannel.register(selector, SelectionKey.OP_READ); 15 new Thread(new NioClientHandler(selector)).start(); 16 /** 17 * 向伺服器發送數據 18 */ 19 Scanner scanner = new Scanner(System.in); 20 while (scanner.hasNextLine()){ 21 String next = scanner.nextLine(); 22 if (StringUtils.isNotBlank(next)){ 23 socketChannel.write(Charset.forName("UTF-8").encode(next)); 24 } 25 } 26 } 27 28 public static void main(String[] args) throws Exception { 29 new NioClient().start(); 30 } 31 }View Code
客戶端線程類
1 public class NioClientHandler implements Runnable { 2 3 private Selector selector; 4 5 public NioClientHandler(Selector selector) { 6 this.selector = selector; 7 } 8 9 @Override 10 public void run() { 11 /** 12 * 迴圈等待新接入的連接 13 */ 14 try { 15 for(;;){ 16 int select = 0; 17 select = selector.select(); 18 19 if (select == 0){ 20 continue; 21 } 22 23 /** 24 * 獲取可用channel的集合 25 */ 26 Set<SelectionKey> keys = selector.selectedKeys(); 27 Iterator<SelectionKey> iterator = keys.iterator(); 28 while (iterator.hasNext()){ 29 SelectionKey selectionKey = (SelectionKey) iterator.next(); 30 /** 31 * 移除selctionKey 32 */ 33 iterator.remove(); 34 /** 35 * 可讀事件 36 */ 37 if(selectionKey.isReadable()){ 38 readHandler(selectionKey, selector); 39 } 40 } 41 } 42 } catch (Exception e) { 43 e.printStackTrace(); 44 } 45 46 47 } 48 49 private void readHandler(SelectionKey selectionKey, Selector selector) throws Exception{ 50 /** 51 * 要用selectionKey中獲取已經就緒的channel 52 */ 53 SocketChannel channel = (SocketChannel)selectionKey.channel(); 54 /** 55 * 創建buffer 56 */ 57 ByteBuffer buffer = ByteBuffer.allocate(1024); 58 /** 59 * 迴圈讀取客戶端數據 60 */ 61 String request = ""; 62 while (channel.read(buffer) > 0){ 63 /** 64 * 切換讀模式 65 */ 66 buffer.flip(); 67 /** 68 * 讀取buffer中的內容 69 */ 70 request += Charset.forName("UTF-8").decode(buffer); 71 72 } 73 /** 74 * 講channel註冊到selector上 75 */ 76 channel.register(selector, SelectionKey.OP_READ); 77 /** 78 * 講客戶端發送的請求信息,廣播給其他客戶端 79 */ 80 if (request.length() > 0){ 81 System.out.println(request); 82 } 83 } 84 }View Code