zoukankan      html  css  js  c++  java
  • JAVA NIO 之Channel

    缓冲区本质上是一块可以写入数据,然后可以从中读取数据的内存。Channel 通道就是将数据传输给 ByteBuffer 对象或者从 ByteBuffer 对象获取数据进行传输。

    Channel 用于在字节缓冲区和位于通道另一侧的实体(通常是一个文件或套接字)之间有效地传输数据。常用Channel有FileChannel、SocketChannel、DatagramChannel、ServerSocketChannel
    Socket 可以通过socket 通道的工厂方法直接创建。但是FileChannel 对象却只能通过在一个打开的 RandomAccessFile、FileInputStream 或 FileOutputStream对象上调用 getChannel( )方法来获取。
    通道可以以阻塞(blocking)或非阻塞(nonblocking)模式运行。只有面向流的(stream-oriented)的通道,如 sockets 和 pipes 才能使用非阻塞模式。

    1.打开通道

    a.打开FileChannel,以RandomAccessFile为例 :
                RandomAccessFile aFile = new RandomAccessFile("G:\we.txt", "rw");
                FileChannel inChannel = aFile.getChannel();
    

     b.打开SocketChannel:

    SocketChannel socketChannel = SocketChannel.open();
    socketChannel.connect(new InetSocketAddress("127.0.0.1",9011));
    

     c.打开ServerSocketChannel

    ServerSocketChannel ssc = ServerSocketChannel.open( );
    ssc.socket( ).bind (new InetSocketAddress (9011));
    

     2.使用通道

    a.使用FileChannel从指定文件中读取数据:
    /**
     * @author monkjavaer
     * @date 2018/10/18 21:00
     */
    public class ChannelTest {
    
        public static void main(String[] args) {
    
            //RandomAccessFile、FileInputStream 或 FileOutputStream的close()会同时关闭chanel。
            
            try (RandomAccessFile aFile = new RandomAccessFile("F:\test.txt", "rw")) {
                //1.FileChannel 对象却只能通过在一个打开的 RandomAccessFile、FileInputStream 或 FileOutputStream对象上调用 getChannel( )方法来获取
                //2.三个 socket 通道类:SocketChannel、ServerSocketChannel 和 DatagramChannel有可以直接创建新 socket 通道的工厂方法
                //随机流, mode 的值可选 "r":可读,"w" :可写,"rw":可读写;
                FileChannel inChannel = aFile.getChannel();
    
                ByteBuffer buf = ByteBuffer.allocate(48);
    
                int bytesRead = inChannel.read(buf);
                while (bytesRead != -1) {
                    //通过flip()方法将Buffer从写模式切换到读模式
                    buf.flip();
                    while (buf.hasRemaining()) {
                        System.out.print((char) buf.get());
                    }
                    //两种方式能清空缓冲区:调用clear()或compact()方法,clear()方法会清空整个缓冲区。compact()方法只会清除已经读过的数据
                    buf.clear();
                    bytesRead = inChannel.read(buf);
                }
    
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    

     b.在两个通道中复制数据:

    /**
     * 在通道之间复制数据
     * @author monkjavaer
     * @date 2018/10/18 21:12
     */
    public class ChannelCopy {
    
        public static void main(String[] argv) throws IOException {
            FileInputStream inputStream = new FileInputStream("G:\test.txt");
            FileChannel inChanel = inputStream.getChannel();
            FileOutputStream outputStream = new FileOutputStream("G:\copyTest.txt");
            FileChannel outChanel = outputStream.getChannel();
            //channelCopy1 channelCopy2方法都是两个通道复制数据
            channelCopy1(inChanel, outChanel);
    //        channelCopy2 (inChanel, outChanel);
            inChanel.close();
            outChanel.close();
        }
    
        /**
         *
         * @param inChanel
         * @param outChanel
         * @throws IOException
         */
        private static void channelCopy1(FileChannel inChanel, FileChannel outChanel) throws IOException {
            ByteBuffer buffer = ByteBuffer.allocateDirect(16 * 1024);
    
            //将数据从输入通道读入缓冲区
            while (inChanel.read(buffer) != -1) {
                // buffer切换到读模式
                buffer.flip();
    
                while (buffer.hasRemaining()) {
                    //如果buffer中有数据就将数据写入输出通道
                    outChanel.write(buffer);
                }
                // buffer切换到写模式,确保缓冲区为空,准备继续填充数据
                buffer.clear();
            }
        }
    
        private static void channelCopy2(FileChannel inChanel, FileChannel outChanel) throws IOException {
            ByteBuffer buffer = ByteBuffer.allocateDirect(16 * 1024);
            while (inChanel.read(buffer) != -1) {
                // Prepare the buffer to be drained
                buffer.flip();
                // Write to the channel; may block
                outChanel.write(buffer);
                // If partial transfer, shift remainder down
                // If buffer is empty, same as doing clear( )
                buffer.compact();
            }
            // EOF will leave buffer in fill state
            buffer.flip();
            // Make sure that the buffer is fully drained
            while (buffer.hasRemaining()) {
                outChanel.write(buffer);
            }
        }
    

     c.注意:一个连接到只读文件的 Channel 实例不能进行写操作,即使该实例所属的类可能有 write( )方法;FileChannel 实现 ByteChannel 

    下面是ByteChannel的源码:

    public interface ByteChannel
        extends ReadableByteChannel, WritableByteChannel
    {
    
    }
    

     可以看到ByteChannel其实什么也不做,这样设计起到了聚集的作用。

     FileChannel 接口既有write也有read方法,为何说不能写呢?那是当使用只读流获取通道时:

    /**
     * @author monkjavaer
     * @date 2018/10/18 21:15
     */
    public class ChannelOnlyReadTest {
        public static void main(String[] args) {
            try {
                ByteBuffer buffer = ByteBuffer.allocate(48);
                // 但是从 FileInputStream 对象的getChannel( )方法获取的 FileChannel 对象是只读的,因为 FileInputStream 对象总是以 read-only 的权限打开文件
                FileInputStream input = new FileInputStream ("G:\we.txt");
                FileChannel channel = input.getChannel( );
                // 抛出 java.nio.channels.NonWritableChannelException
                channel.write (buffer);
                //FileInputStream的close同时会关闭chanel
                input.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    

     上面的代码运行会报错:java.nio.channels.NonWritableChannelException

        因为:从 FileInputStream 对象的getChannel( )方法获取的 FileChannel 对象是只读的,因为 FileInputStream 对象总是以 read-only 的权限打开文件。所以没有写权限。

      之前我们用ServerSocket创建服务器的栗子中服务器是阻塞的。那NIO怎么实现非阻塞呢?

     上面讲的FileChannel 是非阻塞的,因为没有依靠SelectableChannel 超类。全部 socket 通道类(DatagramChannelSocketChannel ServerSocketChannel) 都可以设置成非阻塞的。
      这里就要用到SelectableChannel 的方法

    public abstract SelectableChannel configureBlocking(boolean block)
            throws IOException;
    

      通道设置configureBlocking方法为false就表示非阻塞模式。

     下面是一个客服端和服务器代码说明;

     客服端:

    /**
     * @author monkjavaer
     * @date 2018/10/18 21:08
     */
    public class ScoketChannelClient {
    
        public static void main(String[] args) {
    
            SocketChannel socketChannel = null;
    
            try {
                ByteBuffer buffer = ByteBuffer.wrap ("客服端".getBytes());
                //获取SocketChannel
                socketChannel = SocketChannel.open();
                //设置通道为非阻塞
                socketChannel.configureBlocking(false);
                //连接
                socketChannel.connect(new InetSocketAddress("127.0.0.1",9011));
    
                //connect(InetSocketAddress) 等价于 open(InetSocketAddress)
                //SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1",9010));
    
                //一些判断
                if (!socketChannel.isBlocking()){
                    System.out.println("非阻塞");
                }
                while (!socketChannel.finishConnect()){
                    System.out.println("连接没有建立");
                }
                System.out.println("连接已建立");
                if (socketChannel.isConnected()){
                    System.out.println("连接已建立");
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                try {
                    assert socketChannel != null;
                    socketChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    

     服务器:

    package com.nio.java;
    
    import java.net.InetSocketAddress;
    import java.nio.ByteBuffer;
    import java.nio.channels.ServerSocketChannel;
    import java.nio.channels.SocketChannel;
    
    /**
     * ServerSocketChannel
     * @author monkjavaer
     * @date 2018/10/18 21:29
     */
    public class ChannelAccept
    {
        public static final String HELLO = "Hello I am server.
    ";
        public static void main (String [] argv)throws Exception{
            int port = 9011;
            if (argv.length > 0) {
                port = Integer.parseInt (argv [0]);
            }
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open( );
            serverSocketChannel.socket( ).bind (new InetSocketAddress (port));
            //非阻塞模式
            serverSocketChannel.configureBlocking (false);
            while (true) {
                System.out.println ("Waiting for connections");
                //如果以非阻塞模式被调用,当没有传入连接在等待时,ServerSocketChannel.accept( )会立即返回 null
                SocketChannel sc = serverSocketChannel.accept( );
                if (sc == null) {
                    // 没有连接。。。睡觉
                    Thread.sleep (2000);
                } else {
                    System.out.println ("Incoming connection from: "+ sc.socket().getRemoteSocketAddress( ));
                    //wrap buffer的另一种创建方式
                    ByteBuffer buffer = ByteBuffer.wrap (HELLO.getBytes( ));
                    //可以使用 rewind()后退,重读已经被flip()的缓冲区中的数据。
                    buffer.rewind( );
                    sc.write (buffer);
                }
            }
        }
    }
    

     Scatter/Gather( ScatteringByteChannel 和 GatheringByteChannel
        矢量化的 I/O 使您可以在多个缓冲区上自动执行一个 I/O 操作。使用多个而不是单个缓冲区来保存数据的读写方法。处理复杂数据结构时用。

  • 相关阅读:
    牛客多校第一场 A Equivalent Prefixes 单调栈(笛卡尔树)
    HDU多校第三场 Hdu6606 Distribution of books 线段树优化DP
    (待写)
    Hdu6586 String 字符串字典序贪心
    2019HDU多校第一场1001 BLANK (DP)(HDU6578)
    iOS触摸事件
    iOS获取相册/相机图片-------自定义获取图片小控件
    自定义表情输入框
    iOS版本、iPhone版本、Xcode版本比对
    Swift备忘录
  • 原文地址:https://www.cnblogs.com/monkjavaer/p/9809907.html
Copyright © 2011-2022 走看看