4.通道(Channel)
4.1Channel的只要實現類
通道(Channel):由 java.nio.channels 包定義的。Channel 表示 IO 源與目標打開的連接。Channel 類似於傳統的“流”。只不過 Channel本身不能直接訪問數據,Channel 只能與Buffer 進行交互。
- FileChannel:用於讀取、寫入、映射和操作文件的通道。
- DatagramChannel:通過 UDP 讀寫網絡中的數據通道。
- SocketChannel:通過 TCP 讀寫網絡中的數據。
- ServerSocketChannel:可以監聽新進來的 TCP 連接,對每一個新進來的連接都會創建一個SocketChannel。
4.2 獲取通道
- 獲取通道的一種方式是對支持通道的對象調用getChannel() 方法。支持通道的類如下:
- FileInputStream
- FileOutputStream
- RandomAccessFile
- DatagramSocket
- Socket
- ServerSocket
- 獲取通道的其他方式是java 1.7中的NIO2使用 Files 類的靜態方法 newByteChannel() 獲取字節通道。
- 或者通過java 1.7中的NIO2提供的各個通道的靜態方法 open() 打開並返回指定通道。
4.3通道的數據傳輸
-
將 Buffer 中數據寫入 Channel, int bytesWritten = inChannel.write(buf);
-
從 Channel 讀取數據到 Buffer, int bytesRead = inChannel.read(buf);
-
利用通道複製文件demo:
@Test public void testChannel() { //輸入 輸出流 FileInputStream fis = null; FileOutputStream fos = null; //通道 FileChannel inChannel = null; FileChannel outChannel = null; try { //1,創建channel fis = new FileInputStream("下載.jpg"); inChannel = fis.getChannel(); fos = new FileOutputStream("2.jpg"); outChannel = fos.getChannel(); //2,創建buffer ByteBuffer byteBuffer = ByteBuffer.allocate(1024); //3,讀取 while (inChannel.read(byteBuffer) != -1){ //4 調換buffer byteBuffer.flip(); //5 將緩存區數據寫入通道 outChannel.write(byteBuffer); //6 清空buffer byteBuffer.clear(); } } catch (Exception e) { e.printStackTrace(); }finally { //關閉各種通道 try { if(fis != null){ fis.close(); } if(fos != null){ fos.close(); } if(inChannel != null){ inChannel.close(); } if(outChannel != null){ outChannel.close(); } } catch (IOException e) { System.out.println("關閉通道異常!"); e.printStackTrace(); } } }
-
利用直接緩存區複製文件demo:
/**
*使用直接緩存區進行復制文件demo
* demo中沒有處理異常,正常代碼中應該處理異常
*/
@Test
public void testChannel2() throws IOException {
//1 創建channel
FileChannel inChannel = FileChannel.open(Paths.get("下載.jpg"), StandardOpenOption.READ);
FileChannel outChannel = FileChannel.open(Paths.get("3.jpg"), StandardOpenOption.READ, StandardOpenOption.WRITE, StandardOpenOption.CREATE);
//2 創建物理映射buffer
MappedByteBuffer inMap = inChannel.map(FileChannel.MapMode.READ_ONLY, 0, inChannel.size());
MappedByteBuffer outMap = outChannel.map(FileChannel.MapMode.READ_WRITE, 0, inChannel.size());
//3 直接對緩衝區進行操作
byte[] bytes = new byte[inMap.limit()];
inMap.get(bytes);
outMap.put(bytes);
//4 關閉通道
inChannel.close();
outChannel.c lose();
}
-
通道直接的數據傳輸
-
transferTo( ):將A傳輸到B A.transferTo()
-
transferFrom( ):從A傳輸到B B.transferForm()
/**
* 直接使用通道進行傳輸
*/
@Test
public void transferTest() throws IOException {
FileChannel inChannel = FileChannel.open(Paths.get("2.jpg"), StandardOpenOption.READ);
//此處先創建要被傳輸到的文件,要不然報 沒有此文件的異常
Files.createFile(Paths.get("5.jpg"));
FileChannel outChannel = FileChannel.open(Paths.get("5.jpg"), StandardOpenOption.WRITE, StandardOpenOption.READ);
//直接傳輸
//inChannel.transferTo(0, inChannel.size(), outChannel);
outChannel.transferFrom(inChannel, 0, inChannel.size());
//關閉流
inChannel.close();
outChannel.close();
}
4.4 分散讀取和聚合寫入
-
分散讀取(Scattering Reads)是指從 Channel 中讀取的數據“分散”到多個 Buffer 中。注意:按照緩衝區的順序,從 Channel 中讀取的數據依次將 Buffer 填滿。
-
聚集寫入(Gathering Writes)是指將多個 Buffer 中的數據“聚集”到 Channel。注意:按照緩衝區的順序,寫入 position 和 limit 之間的數據到 Channel 。
demo:
/** * * 分算讀取,聚合寫入的測試 * @author * @version 1.00 * @time 2020/3/17 0017 下午 10:49 */ public class ScattergatherTest { /** * 採用隨機讀取文件流進行測試 */ @Test public void test() throws Exception { //1 獲取通道 RandomAccessFile file = new RandomAccessFile("1.txt", "rw"); FileChannel inChannel = file.getChannel(); //2 分配多個緩衝區 ByteBuffer buffer1 = ByteBuffer.allocate(10); ByteBuffer buffer2 = ByteBuffer.allocate(10); ByteBuffer buffer3 = ByteBuffer.allocate(10); //3 分散讀取 ByteBuffer[] buffers = {buffer1, buffer2, buffer3}; inChannel.read(buffers); for (ByteBuffer buffer : buffers) { //翻轉 buffer.flip(); } System.out.println(new String(buffers[0].array(), 0, buffer1.limit())); System.out.println("-----------------------------"); System.out.println(new String(buffers[1].array(), 0, buffer2.limit())); System.out.println(new String(buffers[2].array(), 0, buffer3.limit())); //4 聚合寫入 RandomAccessFile file1 = new RandomAccessFile("2.txt", "rw"); FileChannel outChannel = file1.getChannel(); outChannel.write(buffers); inChannel.close(); outChannel.close(); } }
5.NIO 的非阻塞式網絡通信
- 傳統的 IO 流都是阻塞式的。也就是說,當一個線程調用 read() 或 write()時,該線程被阻塞,直到有一些數據被讀取或寫入,該線程在此期間不能執行其他任務。因此,在完成網絡通信進行 IO 操作時,由於線程會阻塞,所以服務器端必須爲每個客戶端都提供一個獨立的線程進行處理,當服務器端需要處理大量客戶端時,性能急劇下降。
- Java NIO 是非阻塞模式的。當線程從某通道進行讀寫數據時,若沒有數據可用時,該線程可以進行其他任務。線程通常將非阻塞 IO 的空閒時間用於在其他通道上執行 IO 操作,所以單獨的線程可以管理多個輸入
和輸出通道。因此,NIO 可以讓服務器端使用一個或有限幾個線程來同時處理連接到服務器端的所有客戶端。
5.1 選擇器(Selector)
- 選擇器(Selector) 是 SelectableChannle 對象的多路複用器,Selector 可以同時監控多SelectableChannel 的 IO 狀況,也就是說,利用 Selector可使一個單獨的線程管理多個 Channel。Selector 是非阻塞 IO 的核心。
- SelectableChannle 的結構:
- SelectableChannel
- AbstractSelectableChannel
- SocketChannel
- ServerSocketChannel
- DatagramChannel
- AbstractSelectableChannel
- SelectableChannel
5.1.1 Selector的應用
-
創建 Selector :通過調用 Selector.open() 方法創建一個 Selector。
Selector selector = Selector.open();
-
向選擇器註冊通道:SelectableChannel.register(Selector sel, int ops)
-
當調用 register(Selector sel, int ops) 將通道註冊選擇器時,選擇器對通道的監聽事件,需要通過第二個參數 ops 指定。
-
可以監聽的事件類型(用 可使用 SelectionKey 的四個常量 表示):
-
讀: SelectionKey.OP_READ (1)
-
寫:SelectionKey.OP_WRITE (4)
-
連接: SelectionKey.OP_CONNECT (8)
-
接收: SelectionKey.OP_ACCEPT(16)
-
若註冊時不止監聽一個事件,則可以使用“位或”操作符連接。
SelectionKey.OP_READ|SelectionKey.OP_WRITE;
5.1.2 SelectionKey
- SelectionKey:表示 SelectableChannel 和 Selector 之間的註冊關係。每次向選擇器註冊通道時就會選擇一個事件(選擇鍵)。選擇鍵包含兩個表示爲整數值的操作集。操作集的每一位都表示該鍵的通道所支持的一類可選擇操作。
方 法 | 描 述 |
---|---|
int interestOps() | 獲取感興趣事件集合 |
int readyOps() | 獲取通道已經準備就緒的操作的集合 |
SelectableChannel channel() | 獲取註冊通道 |
Selector selector() | 返回選擇器 |
boolean isReadable() | 檢測 Channal 中讀事件是否就緒 |
boolean isWritable() | 檢測 Channal 中寫事件是否就緒 |
boolean isConnectable() | 檢測 Channel 中連接是否就緒 |
boolean isAcceptable() | 檢測 Channel 中接收是否就緒 |
5.1.3 Selector 的常用方法
方 法 | 描 述 |
---|---|
Set keys() | 所有的 SelectionKey 集合。代表註冊在該Selector上的Channel |
selectedKeys() | 被選擇的 SelectionKey 集合。返回此Selector的已選擇鍵集 |
int select() | 監控所有註冊的Channel,當它們中間有需要處理的 IO 操作時,該方法返回,並將對應得的 SelectionKey 加入被選擇的SelectionKey 集合中,該方法返回這些Channel 的數量。 |
int select(long timeout) | 可以設置超時時長的 select() 操作 |
int selectNow() | 執行一個立即返回的 select() 操作,該方法不會阻塞線程 |
Selector wakeup() | 使一個還未返回的 select() 方法立即返回 |
void close() | 關閉該選擇器 |
5.2 SocketChannel、ServerSocketChannel、DatagramChannel
5.2.1 SocketChannel
- Java NIO中的SocketChannel是一個連接到TCP網絡套接字的通道。
- 操作步驟:
- 打開 SocketChannel
- 讀寫數據
- 關閉 SocketChannel
5.2.2 ServerSocketChannel
- Java NIO中的 ServerSocketChannel 是一個可以監聽新進來的TCP連接的通道,就像標準IO中的ServerSocket一樣。
/**
* 使用非阻塞的方式數據傳輸
*
* @author
* @version 1.00
* @time 2020/3/18 0018 下午 9:20
*/
public class NioTest {
public static void main(String[] args) {
try {
SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9527));
//切換爲非阻塞模式
socketChannel.configureBlocking(false);
//分配緩衝區
ByteBuffer buffer = ByteBuffer.allocate(1024);
//數據填充
buffer.put((LocalDateTime.now().toString()).getBytes());
//翻轉
buffer.flip();
//發送
socketChannel.write(buffer);
buffer.clear();
//關閉
socketChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
@Test
public void server() throws IOException {
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//2 切換爲非阻塞模式
serverSocketChannel.configureBlocking(false);
//3 綁定
serverSocketChannel.bind(new InetSocketAddress(9527));
//4 創建選擇器
Selector selector = Selector.open();
//5 將通道註冊到選擇器上 設置監聽事件爲 接收
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
//6 輪詢的獲取選擇器上已經準備就緒的事件
while (selector.select() > 0) {
//7 被選擇的 SelectionKey 集合。返回此Selector的已選擇鍵集
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()) {
//8 獲取準備就緒的時間
SelectionKey key = iterator.next();
//9 判斷具體是什麼事件準備完畢
if (key.isAcceptable()) {
//10 若接收就緒 獲取客戶端連接
SocketChannel acceptChannel = serverSocketChannel.accept();
//11 切換非阻塞模式
acceptChannel.configureBlocking(false);
//12 將該選擇器註冊到選擇器上 讀操作
acceptChannel.register(selector, SelectionKey.OP_READ);
} else if (key.isReadable()) {//讀就緒
//13 獲取當前選擇器上的讀就緒的通道
SocketChannel inChannel = (SocketChannel) key.channel();
//讀取數據
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
Integer len = 0;
while( (len = inChannel.read(byteBuffer)) != -1){
byteBuffer.flip();
//打印
System.out.println(new String(byteBuffer.array(), 0, len));
byteBuffer.clear();
}
}
//取消已選擇的key 必須取消 此步驟很重要
iterator.remove();
}
}
}
}
5.2.3 DatagramChannel
- Java NIO中的DatagramChannel是一個能收發UDP包的通道。
- 操作步驟:
- 打開 DatagramChannel
- 接收/發送數據
6.管道(Pipe)
-
Java NIO 管道是2個線程之間的單向數據連接。Pipe有一個source通道和一個sink通道。數據會被寫到sink通道,從source通道讀取。
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-uTtvZWX4-1584702199139)(C:\Users\Administrator\Desktop\Md文檔\nio\pipe.jpg)]
-
向通道寫入數據;
-
從通道讀取數據;
System.out.println(new String(byteBuffer.array(), 0, len)); byteBuffer.clear(); } } //取消已選擇的key 必須取消 此步驟很重要 iterator.remove(); } }
}
}
### 5.2.3 DatagramChannel
* Java NIO中的DatagramChannel是一個能收發UDP包的通道。
* 操作步驟:
* 打開 DatagramChannel
* 接收/發送數據
# 6.管道(Pipe)
* Java NIO 管道是2個線程之間的單向數據連接。Pipe有一個source通道和一個sink通道。數據會被寫到sink通道,從source通道讀取。
[外鏈圖片轉存中...(img-uTtvZWX4-1584702199139)]
* 向通道寫入數據;
* 從通道讀取數據;