Java NIO——channel
文章目錄
1 概述
channel,意為通道,表示IO流和目標開啟的連線。channel本身不能直接訪問資料,只能與Buffer進行互動。所有的資料都是通過Buffer進行互動的。
- 讀:將資料從channel通道讀入緩衝區,再從緩衝區獲取這個位元組。
- 寫:將資料寫入Buffer,將buffer中資料寫入channel。
NIO包中有channel的幾種實現
- FileChannel:用於讀取、寫入、對映和操作檔案的通道。
- DatagramChannel:能通過UDP讀寫網路中的資料。
- SocketChannel:能通過TCP讀寫網路中的資料。
- ServerSocketChannel:可以監聽新進來的TCP連線,像Web伺服器那樣。對每一個新進來的連線都會建立一個SocketChannel。
2 FileChannel
I/O(asynchronous I/O),允許一個程序可以從作業系統請求一個或多個I/O操作而不必等待這些操作的完成。發起請求的程序之後會收到它請求的I/O操作已完成的通知。
2.1 getChannel
需要通過使用inputStream,outputStream或者RandomAccessFile來獲取例項
FileChannel fileChannel = new RandomAccessFile(file, "rw").getChannel();
2.2 Scatter和Gatter
分散讀取(Scattering Reads)是指從 Channel 中讀取的資料“分散” 到多個 Buffer 中。按照緩衝區的順序,從 Channel 中讀取的資料依次將 Buffer 填滿。
聚集寫入(Gathering Writes)是指將多個 Buffer 中的資料“聚集”
到 Channel。
scatter / gather經常用於需要將傳輸的資料分開處理的場合
Scattering Reads在移動下一個buffer前,必須填滿當前的buffer,這也意味著它不適用於動態訊息(譯者注:訊息大小不固定)。換句話說,如果存在訊息頭和訊息體,訊息頭必須完成填充(例如 128byte),Scattering Reads才能正常工。
Gatter writer會按照buffer在陣列中的順序,將資料寫入到channel,注意只有position和limit之間的資料才會被寫入。因此,如果一個buffer的容量為128byte,但是僅僅包含58byte的資料,那麼這58byte的資料將被寫入到channel中。因此與Scattering Reads相反,Gathering Writes能較好的處理動態訊息。
//Gatter
ByteBuffer header = ByteBuffer.allocate(128);
ByteBuffer body = ByteBuffer.allocate(1024);
//write data into buffers
ByteBuffer[] bufferArray = { header, body };
channel.write(bufferArray);
2.3 常用方法
int read(ByteBuffer dst)//從channel中讀取資料到ByteBuffer
long read(ByterBuffer dsts)//從channel中“分散”資料到Bytebuffer[]中
int write(ByteBuffer src)將ByteBuffer中資料寫入Channel
long write(ByteBuffer[] srcs)將ByteBuffer中資料“聚集”到Channel
transferFrom(Channel from,long count,long pos)
transferTo(Channel to,long count,long pos)
2.4 demo
@Test
public void testCostTime() throws IOException {
String src = "D:\\mygit\\downloads\\2016年初評成果附件.zip";
String des = "D:\\mygit\\downloads\\copy.zip";
Path from = Paths.get(src);
Path to = Paths.get(des);
long start1 = System.currentTimeMillis();
Files.copy(from,to,NOFOLLOW_LINKS);
long cost1 = System.currentTimeMillis();
System.out.println(cost1-start1);
long start2 = System.currentTimeMillis();
copyByChannel(src,des);
long cost2 = System.currentTimeMillis();
System.out.println(cost2-start2);
long start3 = System.currentTimeMillis();
copyByNioBuffer2(src,des);
long cost3 = System.currentTimeMillis();
System.out.println(cost3-start3);
long start4 = System.currentTimeMillis();
copyByNioDirectBuffer(src,des);
long cost4 = System.currentTimeMillis();
System.out.println(cost4-start4);
}
public void copyByChannel(String src,String des) throws IOException {
RandomAccessFile sr = new RandomAccessFile(src,"r");
RandomAccessFile dr = new RandomAccessFile(des,"rw");
FileChannel schannel = sr.getChannel();
FileChannel dchannel = dr.getChannel();
schannel.transferTo(0L,schannel.size(), dchannel);
}
public void copyByNioBuffer(String src,String des) throws IOException {
RandomAccessFile sr = new RandomAccessFile(src,"r");
RandomAccessFile dr = new RandomAccessFile(des,"rw");
FileChannel schannel = sr.getChannel();
FileChannel dchannel = dr.getChannel();
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
while ((schannel.read(byteBuffer)) !=-1){
byteBuffer.flip();
dchannel.write(byteBuffer);
byteBuffer.clear();
}
schannel.close();
dchannel.close();
}
public void copyByNioDirectBuffer(String src,String des) throws IOException {
RandomAccessFile sr = new RandomAccessFile(src,"r");
RandomAccessFile dr = new RandomAccessFile(des,"rw");
FileChannel schannel = sr.getChannel();
FileChannel dchannel = dr.getChannel();
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(1024);
while ((schannel.read(byteBuffer)) !=-1){
byteBuffer.flip();
dchannel.write(byteBuffer);
byteBuffer.clear();
}
schannel.close();
dchannel.close();
}
// 檔案大小為1.4G
// 當增大快取區大小時,DirectBuffer可能比Buffer快
5127 //Files.copy
10261 // transferTo
7330 // copyByNioBuffer
15681 //copyByNioDirectBuffer
3 DatagramChannel
// 開啟dataGramChannel
DatagramChannel channel = DatagramChannel.open();
channel.socket().bind(new InetSocketAddress(9999));
ByteBuffer buf = ByteBuffer.allocate(48);
buf.clear();
// 接收資料
channel.receive(buf);
buf.clear();
buf.put(newData.getBytes());
buf.flip();
// 傳送資料
channel.send(buf);
channel.close();
channel.connect(new InetSocketAddress("jenkov.com", 80));
4 SocketChannel
SocketChannel是一個連線到TCP網路套接字的通道。
操作步驟:
- 開啟 SocketChannel
- 讀寫資料
- 關閉 SocketChannel
//開啟SocketChannel
SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress("http://jenkov.com", 80));
// 寫入資料
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()) {
socketChannel.write(buf);
}
// 讀取資料
ByteBuffer buf = ByteBuffer.allocate(48);
int bytesRead = socketChannel.read(buf)
// 關閉socketChannel
socketChannel.close();
非阻塞模式
將scoketChannel設為非阻塞模式,read和write方法會立即返回,可以非同步呼叫。
serverSocketChannel.configureBlocking(false);
5 ServerSocketChannel
ServerSocketChannel 是一個可以監聽新進來的TCP連線的通道, 就像標準IO中的ServerSocket一樣。
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket().bind(new InetSocketAddress(9999));
while(true){
SocketChannel socketChannel =
serverSocketChannel.accept();
//do something with socketChannel...
}