1. 程式人生 > >Java NIO——channel

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...
}