Java NIO之通道

通道

channel用於字節緩衝區和位於通道另外一側的實體(一般是一個文件或套接字)之間有效的傳輸數據。
通道時一種途徑,經過這種途徑,能夠用最小的總開銷來訪問操做系統自己的IO服務,緩衝區則是通道內部用於發送和接受數據的斷點。java

通道基礎

頂層接口Channel,次級接口WritableByteChannel、ReadableByteChannel、InterruptibleChannel等。描述通道行爲的接口在java.nio.channels包中定義,具體的通道實現都是從java.nio.channels.spi中的類引伸來的。git

通道打開

IO能夠分爲廣義的兩大類:File IO和Stream IO,對應File通道和Socket通道。體如今FileChannel類和三個socket通道類:SocketChannel、ServerSocketChannel和DatagramChannel。
代碼以下:github

//打開SocketChannel
            SocketChannel sc = SocketChannel.open( );
            sc.connect (new InetSocketAddress("somehost", port));
            
            //打開ServerSocketChannel
            ServerSocketChannel ssc = ServerSocketChannel.open( );
            ssc.socket( ).bind (new InetSocketAddress (port));

            DatagramChannel dc = DatagramChannel.open( );

            //FileChannel只能經過 RandomAccessFile、FileInputStream 或 FileOutputStream 對象上調用 getChannel( )方法來獲取
            RandomAccessFile raf = new RandomAccessFile ("somefile", "r");
            FileChannel fc = raf.getChannel( );

通道使用

利用通道,從控制檯接收輸入,並在控制檯打印出接收的輸入。代碼以下:web

public static void main(String[] args) throws IOException {

        //一個讀通道,一個寫通道
        ReadableByteChannel source = Channels.newChannel(System.in);
        WritableByteChannel dest = Channels.newChannel(System.out);

        channelCopy(source,dest);

        source.close();
        dest.close();
    }

    private static void channelCopy(ReadableByteChannel source, WritableByteChannel dest) throws IOException{

        ByteBuffer byteBuffer = ByteBuffer.allocate(16 * 1024);
        ByteBuffer flag = ByteBuffer.allocate(4);
        while (source.read(byteBuffer) != -1) {
            byteBuffer.flip();
            //輸出標記
            flag.put((byte)'-').put((byte)'-').put((byte)'-').put((byte) '>');
            flag.flip();
            dest.write(flag);
            dest.write(byteBuffer);
            flag.clear();

            byteBuffer.compact();
        }
        byteBuffer.flip();
        //確保緩衝區排乾淨
        while (byteBuffer.hasRemaining()) {
            flag.putChar('-').putChar('-').putChar('-');
            flag.flip();
            dest.write(byteBuffer);
            flag.clear();
        }

    }

測試輸入輸出以下:spring

通道關閉

與緩衝區不一樣,通道不能重複利用,打開通道即表明與一個特定的IO服務的特定連接並封裝該連接的狀態,通道關閉時,鏈接丟失,通道不在鏈接任何東西。
調用close方法時,可能致使線程暫時阻塞,關閉的通道上調用close方法不會產生任何操做,只會當即返回。能夠經過isOpen方法判斷通道狀態。
若是一個線程被中斷,那麼這個線程訪問的通道將當即關閉,這也是爲程序健壯性而採用的一種權衡。數組

Scatter/Gather

在多個緩衝區實現一個簡單的IO操做:
對於write,數據是從幾個緩衝區按順序抽取(gather)並沿着通道發送。該gather過程,比如所有緩衝區內容被鏈接起來,並在發送前存放到一個大的緩衝區。
對於read,從通道讀取的數據會被按順序散佈(scatter)到多個緩衝區,將每一個緩衝區填滿直至通道中的數據或緩衝區的空間被消耗完。
接口定義以下, 其中read和write入參時Buffer數組:瀏覽器

public interface ScatteringByteChannel
    extends ReadableByteChannel
{
    public long read (ByteBuffer [] dsts)
        throws IOException;
    public long read (ByteBuffer [] dsts, int offset, int length)
        throws IOException;
}
public interface GatheringByteChannel
    extends WritableByteChannel
{
    public long write(ByteBuffer[] srcs)
        throws IOException;
    public long write(ByteBuffer[] srcs, int offset, int length)
        throws IOException;
}

文件通道

具體來說FileChannel,接口以下:安全

public abstract class FileChannel
    extends AbstractInterruptibleChannel
    implements SeekableByteChannel, GatheringByteChannel, ScatteringByteChannel

FileChannel 對象是線程安全(thread-safe)的.
對於文件IO,最強大之處在於異步IO,它容許一個進程能夠從操做系統請求一個或多個IO操做而沒必要等待這些操做完成。springboot

文件通道代碼示例

  • 將緩衝區數據,經過文件channel寫入文件
public static void write(String filePath) throws Exception {
        /*寫文件,使用FileOutputStream,RandomAccessFile均可以。*/
/*      RandomAccessFile file = new RandomAccessFile(filePath,"rw");*/
        FileOutputStream file = new FileOutputStream(new File(filePath));
        ByteBuffer byteBuffer = ByteBuffer.allocate(500);
        String str = "hello LK";
        /*數據寫入緩衝區*/
        byteBuffer.put(str.getBytes());
        byteBuffer.flip();

        FileChannel fileChannel = file.getChannel();

        //將緩衝區數據寫入文件通道
        fileChannel.write(byteBuffer);

        byteBuffer.clear();
        fileChannel.close();
    }
  • 經過文件channel,將文件中的數據讀入緩衝區
public static void read(String filePath) throws Exception {
        FileInputStream fileInputStream = new FileInputStream(new File(filePath));
        /*一個FileChannel對象卻只能經過
        在一個打開的RandomAccessFile、FileInputStream或FileOutputStream對象上調用getChannel()方法來獲取,
        開發者不能直接建立一個FileChannel*/
        FileChannel fileChannel = fileInputStream.getChannel();

        ByteBuffer byteBuffer = ByteBuffer.allocate(500);
        //將文件channel讀入緩衝區
        fileChannel.read(byteBuffer);

        byteBuffer.flip();

        while (byteBuffer.hasRemaining()){
            System.out.print((char)byteBuffer.get());
        }

        byteBuffer.clear();
        fileChannel.close();
    }

Socket通道

新的Socket通道類能夠運行非阻塞模式,而且是可選擇的。藉助新的NIO類,一個或幾個線程能夠管理成百上千的活動socket鏈接,而且只有不多的性能順勢。
所有 socket 通道類(DatagramChannel、SocketChannel 和 ServerSocketChannel)都是由位於java.nio.channels.spi 包中的AbstractSelectableChannel引伸而來。
DatagramChannel 和 SocketChannel 實現定義讀和寫功能的接口而 ServerSocketChannel 不實現。ServerSocketChannel 負責監聽傳入的鏈接和建立新的SocketChannel 對象,它自己從不傳 輸數據。服務器

Socket通道代碼示例

啓動一個ServerSocketChannel,監聽8001端口,非阻塞模式。啓動10個SocketChannel線程向ServerSocketChannel寫數據。
ServerSocketChannel代碼以下:

ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        /*非阻塞*/
        serverSocketChannel.configureBlocking(false);
        serverSocketChannel.bind(new InetSocketAddress(port));
        System.out.println("ServerSocketChannel is OK,waiting @[" + LocalDateTime.now() + "]");

        for (; ; ) {
            SocketChannel socketChannel = serverSocketChannel.accept();
            if (socketChannel == null) {
                Thread.sleep(1000);
                System.out.println("ServerSocketChannel sleep 1000ms.");
                continue;
            }
            String connectIP = socketChannel.socket().getRemoteSocketAddress().toString();
            System.out.println("客戶端已有數據到來,客戶端ip爲:" + connectIP + ", 時間爲" + LocalDateTime.now());
            ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
            socketChannel.read(byteBuffer);
            byteBuffer.flip();
            while (byteBuffer.hasRemaining()) {
                System.out.print((char) byteBuffer.get());
            }
            socketChannel.close();
        }
    }

啓動10個SocketCHannel代碼以下:

private static final int port = 8001;
    public static void main(String[] args) {
        for (int i=0;i<10;i++) {
            new SocketChannelImpl(port,i).start();
        }
    }
    private static class SocketChannelImpl extends Thread {
        private  int count = 0;
        private int port;
        public SocketChannelImpl(int port,int count){
            this.port = port;
            this.count = count;
        }
        @Override
        public void run() {

            try {
                SocketChannel socketChannel = SocketChannel.open();
                /*非阻塞*/
                socketChannel.configureBlocking(false);
                socketChannel.connect(new InetSocketAddress(port));

                for (;!socketChannel.finishConnect();) {
                    System.out.println("connectting....");
                    Thread.sleep(50);
                }
                ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                String content = "hello, i am client--------->" + count;
                byteBuffer.put(content.getBytes());
                byteBuffer.flip();
                socketChannel.write(byteBuffer);

                byteBuffer.clear();
                socketChannel.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

運行結果以下:

補充下:
ServerSocketChannel監聽的是8001端口,你能夠在瀏覽器,輸入:http://localhost:8001/helloworld,你會發現你的ServerSocketChannel也是能夠收到數據了,這也web服務器處理的基礎了。

總結

以上,瞭解了基本的通道操做,文件通道和socket通道的使用示例,我以爲點個贊,不過度=。=
以上全部代碼示例,能夠fork這裏:github

謝謝

以上來自天團運營總監:坤少

相關文章
相關標籤/搜索