美文网首页
NIO核心组件 channel、buffer、selector

NIO核心组件 channel、buffer、selector

作者: 南园故剑00 | 来源:发表于2020-06-06 23:07 被阅读0次

    1. 三个核心组件 channel、buffer、selector


    1. 每个channel都会对应一个buffer
    2. selector对应一下线程,一个线程对应多个channel
    3. 程序切换到哪个channel是由事件event决定的
      4.selector会根据不同的事件在各个通道上切换
    4. buffer是一个内存块。底层是有一个数组的
    5. 数据的读取写入是通过buffer。BIO要么是输入流或者是输出流,不能双向,但是NIO是可以读也可以写的,需要flip方法切换。
    6. channel是双向的,可以反映底层操作系统的情况。比如linux,底层的操作系统通道是双向的。

    2. 面向缓冲区、或者面向块编程的

    数据读取到一个它稍后处理的缓冲区,需要时可以在缓冲区中前后移动,增加了处理过程的灵活性,使用它可以提供非阻塞的高伸缩性网络。

    3. 非阻塞

    一个线程从某通道发送请求或者读取数据,但是它仅得到目前可用的数据。如果目前没有数据可用时,就什么都不会获取,而彼不是保持线程阻塞,所以直至数据变得可以读取之前,该线程可以继续做其他的事情。非阻塞写也是如如,一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以做其他的事情

    4. BIO和NIO的比较

    1. BIO以流的方式处理数据,而NIO以块的方式处理数据,块IO的效率比流IO高很多
    2. BIO通过字节流和字符流进行操作,而NIO基于channel和buffer进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中
    3. selector用于监听多个通道的事件(连接请求、数据到达等)。因为使用单个线程就可以监听多个客户端通道

    5. HTTP2.0使用了多路复用技术,做到了同一个连接并发处理多个请求,而且并发数量比HTTP1.1大了好几个数量级。

    6. Buffer

    1. Buffer可以理解成数组,它通过以下3个值描述状态:

      a) position:下一个元素的位置;

      b) limit:可读取或写入的元素总数,position总是小于或者等于limit;

      c) capacity:Buffer最大容量,limit总是小于或者等于capacity






    package com.sgg.buffer;
    
    import java.nio.IntBuffer;
    
    /**
     * @description: Buffer 基本使用
     * @date : 2020/6/6 19:25
     * @author: zwz
     */
    public class BasicBuffer {
        public static void main(String[] args) {
            //举例说明一个buffer的使用
            //创建一个buffer,大小为5,即可以存放5个int
            IntBuffer intBuffer = IntBuffer.allocate(5);
    //        intBuffer.put(10);
    //        intBuffer.put(11);
    
            for (int i = 0; i < intBuffer.capacity(); i++) {
                intBuffer.put(i * 2);
            }
    
            //从buffer读取数据
            //将buffer读写切换
            intBuffer.flip();
            while (intBuffer.hasRemaining()) {
                System.out.println(intBuffer.get());
            }
        }
    }
    
    package com.sgg.buffer;
    
    import java.io.IOException;
    import java.io.RandomAccessFile;
    import java.nio.MappedByteBuffer;
    import java.nio.channels.FileChannel;
    
    /**
     * @description: MappedByteBuffer
     * 1. 可以让文件直接在内存(堆外内存)中修改,操作系统不需要拷贝一次
     * 堆外内存  https://blog.csdn.net/ZYC88888/article/details/80228531
     * @date : 2020/6/6 21:33
     * @author: zwz
     */
    public class MappedByteBufferTest {
        public static void main(String[] args) throws IOException {
    
            /*
             * RandomAccessFile
             * 三、对该工具类的价值分析
                    1、大型文本日志类文件的快速定位获取数据:
    
                    得益于seek的巧妙设计,我认为我们可以从超大的文本中快速定位我们的游标,例如每次存日志的时候,我们可以建立一个索引缓存,索引是日志的起始日期,value是文本的poiniter 也就是光标,这样我们可以快速定位某一个时间段的文本内容
    
                    2、并发读写
    
                    emmm也是得益于seek的设计,我认为多线程可以轮流操作seek控制光标的位置,从未达到不同线程的并发写操作。
    
                    3、更方便的获取二进制文件
    
                    通过自带的读写转码(readDouble、writeLong等),我认为可以快速的完成字节码到字符的转换功能,对使用者来说比较友好。
                    *
                    原文链接:https://blog.csdn.net/qq_31615049/article/details/88562892
             *
             */
            RandomAccessFile randomAccessFile = new RandomAccessFile("E:\\CODE\\Architecture\\netty\\netty-all\\file01.txt", "rw");
            FileChannel channel = randomAccessFile.getChannel();
            /*
             * 1. 使用读写模式
             * 2. 可以直接修改的起始位置
             * 3. 是映射内存的大小,即将file01.txt的多少个字节映射到内存.
             */
            MappedByteBuffer map = channel.map(FileChannel.MapMode.READ_WRITE, 0, 5);
            map.put(0, (byte) 'H');
            //put 超过5 下标越界
            map.put(3, (byte) '9');
            randomAccessFile.close();
        }
    }
    
    package com.sgg.buffer;
    
    import java.nio.ByteBuffer;
    
    /**
     * @description: 按相应的类型获取
     * @date : 2020/6/6 21:19
     * @author: zwz
     */
    public class NIOByteBufferPutGet {
    
        public static void main(String[] args) {
            ByteBuffer buffer = ByteBuffer.allocate(64);
            buffer.putInt(100000000);
            buffer.putLong(9);
            buffer.putChar('尚');
            buffer.putShort((short) 4);
            buffer.flip();
            System.out.println();
            System.out.println(buffer.getShort());
            System.out.println(buffer.getInt());
            System.out.println(buffer.getLong());
            System.out.println(buffer.getChar());
    
        }
    }
    
    package com.sgg.buffer;
    
    import java.nio.ByteBuffer;
    
    /**
     * @description: 只读
     * @date : 2020/6/6 21:19
     * @author: zwz
     */
    public class NIOByteBufferReadOnly {
    
        public static void main(String[] args) {
            ByteBuffer buffer = ByteBuffer.allocate(64);
            for (int i = 0; i < 64; i++) {
                buffer.put((byte) i);
            }
            buffer.flip();
            //得到一个只读的buffer
            ByteBuffer readOnlyBuffer = buffer.asReadOnlyBuffer();
            System.out.println(readOnlyBuffer.getClass());  // class java.nio.HeapByteBufferR
    
            while (readOnlyBuffer.hasRemaining()) {
                System.out.println(readOnlyBuffer.get());
            }
    
            readOnlyBuffer.put((byte) 5);  //java.nio.ReadOnlyBufferException
        }
    }
    
    package com.sgg.buffer;
    
    import java.io.IOException;
    import java.net.InetSocketAddress;
    import java.nio.Buffer;
    import java.nio.ByteBuffer;
    import java.nio.channels.ServerSocketChannel;
    import java.nio.channels.SocketChannel;
    import java.util.Arrays;
    
    /**
     * @description: ScatteringAndGatheringTest
     * Scattering 将数据写入到buffer时,可以采用buffer数据依次写 分散
     * Gatherging 从buffer读取数据时,可以采用buffer数组,依次读 聚合
     *
     * byteRead=8
     * position=5,limit=5
     * position=3,limit=3
     * byteRead=8   byteWrite=8   messageLength=8
     *
     * cmd
     * telnet 127.0.0.1 7000
     * send hell000
     *
     * @date : 2020/6/6 21:46
     * @author: zwz
     */
    public class ScatteringAndGatheringTest {
        public static void main(String[] args) throws IOException {
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
            InetSocketAddress inetSocketAddress = new InetSocketAddress(7000);
    
            //绑定端口到socket并启动
            serverSocketChannel.socket().bind(inetSocketAddress);
            ByteBuffer[] byteBuffers = new ByteBuffer[2];
            byteBuffers[0] = ByteBuffer.allocate(5);
            byteBuffers[1] = ByteBuffer.allocate(3);
    
            //等待客户端连接 telnet
            SocketChannel socketChannel = serverSocketChannel.accept();
            int messageLength = 8; //从客户端接收8个字节
            //循环读取
            while (true) {
                long byteRead = 0;
                while (byteRead < messageLength) {
                    //从SocketChannel中读取数据到byteBuffers.从position处开始写
                    long read = socketChannel.read(byteBuffers);
                    byteRead += read;
                    System.out.println("byteRead=" + byteRead);
                    //使用流打印,看看当前的这个buffer的position和limit
                    Arrays.stream(byteBuffers).map(byteBuffer ->
                            "position=" + byteBuffer.position() + ",limit=" + byteBuffer.limit())
                            .forEach(System.out::println);
                }
    
                //limit = position,position = 0
                Arrays.asList(byteBuffers).forEach(Buffer::flip);
                //将数据读出显示到客户端
                long byteWrite = 0;
                while (byteWrite < messageLength) {
                    //write方法使得byteBuffer的position到limit中的元素写入通道中
                    long write = socketChannel.write(byteBuffers);
                    byteWrite += write;
                }
    
                //复位
                Arrays.asList(byteBuffers).forEach(Buffer::clear);
                System.out.println("byteRead=" + byteRead + "   byteWrite=" + byteWrite + "   messageLength=" + messageLength);
            }
        }
    
    }
    

    7. channel


    7.1 FileChannel


    字符串写入本地文件
    package com.sgg.channel;
    
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.nio.ByteBuffer;
    import java.nio.channels.FileChannel;
    
    /**
     * @description:
     * @date : 2020/6/6 20:21
     * @author: zwz
     */
    public class NIOFileChannel01 {
        public static void main(String[] args) throws IOException {
            String str = "hello sgg";
            //创建一个输出流
            FileOutputStream fileOutputStream = new FileOutputStream("E:\\CODE\\Architecture\\netty\\netty-all\\file01.txt");
            //通过输出流获取对应的 FileChannel
            //FileChannel 真实的类型时 FileChannelImpl
            FileChannel fileChannel = fileOutputStream.getChannel();
    
            //创建一个缓冲区 ByteBuffer
            ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
            //将str放入到byteBuffer
            byteBuffer.put(str.getBytes());
            //对byteBuffer进行flip
            byteBuffer.flip();
    
            //将byteBuffer 数据写入到 fileChannel
            fileChannel.write(byteBuffer);
            fileOutputStream.close();
        }
    }
    

    文件流包裹了channel


    文件写入程序
    package com.sgg.channel;
    
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.IOException;
    import java.nio.ByteBuffer;
    import java.nio.channels.FileChannel;
    
    /**
     * @description:
     * @date : 2020/6/6 20:21
     * @author: zwz
     */
    public class NIOFileChannel01 {
        public static void main(String[] args) throws IOException {
            String str = "hello sgg";
            //创建一个文件输入流
            File file = new File("E:\\CODE\\Architecture\\netty\\netty-all\\file01.txt");
            FileInputStream inputStream = new FileInputStream(file);
    
            //通过fileInputStream获取对应的FileChannel 实际类型 FileChannelImpl
            FileChannel channel = inputStream.getChannel();
    
            //创建缓冲区
            ByteBuffer buffer = ByteBuffer.allocate((int) file.length());
    
            //将通道的数据读入到buffer
            channel.read(buffer);
    
            //将buffer的字节数据转为string
            System.out.println(new String(buffer.array()));
        }
    }
    

    文件拷贝

    package com.sgg.channel;
    
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.nio.ByteBuffer;
    import java.nio.channels.FileChannel;
    
    /**
     * @description:
     * @date : 2020/6/6 20:21
     * @author: zwz
     */
    public class NIOFileChannel03 {
        public static void main(String[] args) throws IOException {
            //创建一个输出流
            FileOutputStream fileOutputStream = new FileOutputStream("E:\\CODE\\Architecture\\netty\\netty-all\\file02.txt");
            //通过输出流获取对应的 FileChannel
            //FileChannel 真实的类型时 FileChannelImpl
            FileChannel fileChannel = fileOutputStream.getChannel();
    
            //创建一个文件输入流
            File file = new File("E:\\CODE\\Architecture\\netty\\netty-all\\file01.txt");
            FileInputStream inputStream = new FileInputStream(file);
            //通过fileInputStream获取对应的FileChannel 实际类型 FileChannelImpl
            FileChannel channel = inputStream.getChannel();
    
            //创建一个缓冲区 ByteBuffer
            ByteBuffer byteBuffer = ByteBuffer.allocate(512);
            while (true) {
                //将byteBuffer的标记位重置
                byteBuffer.clear();
                int read = channel.read(byteBuffer);
                System.out.println("read: " + read);
                if (read == -1) {//-1表示读取完毕
                    break;
                }
                //将byteBuffer 数据写入到 fileChannel
                byteBuffer.flip();
                fileChannel.write(byteBuffer);
            }
    
            inputStream.close();
            fileOutputStream.close();
        }
    }
    

    文件拷贝 transFrom

    package com.sgg.channel;
    
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.nio.channels.FileChannel;
    
    /**
     * @description:
     * @date : 2020/6/6 20:21
     * @author: zwz
     */
    public class NIOFileChannel04 {
        public static void main(String[] args) throws IOException {
            //创建一个输出流
            FileOutputStream fileOutputStream = new FileOutputStream("E:\\CODE\\Architecture\\netty\\netty-all\\2.png");
            //通过输出流获取对应的 FileChannel
            //FileChannel 真实的类型时 FileChannelImpl
            FileChannel destChannel = fileOutputStream.getChannel();
    
            //创建一个文件输入流
            File file = new File("E:\\CODE\\Architecture\\netty\\netty-all\\redis持久化的意义.png");
            FileInputStream inputStream = new FileInputStream(file);
            //通过fileInputStream获取对应的FileChannel 实际类型 FileChannelImpl
            FileChannel sourceChannel = inputStream.getChannel();
    
            //使用transfrom完成拷贝
            destChannel.transferFrom(sourceChannel, 0, sourceChannel.size());
    
            sourceChannel.close();
            destChannel.close();
            inputStream.close();
            fileOutputStream.close();
        }
    }
    

    注意

    7.2

    7.3

    8. Selector 选择器





    1. 当客户端连接时,会通过serverSocketChannel 得到 socketChannel
    2. 将socketChannel注册到selector上,register。selector上可以注册多个SocketChannel
    3. 注册后返回一个SelectionKey,会和该Selector关联(集合)
    4. Selector 进行监听Select方法,返回有事件发生的通道的个数。
    5. 进一步得到各个SelectionKey(有事件发生的)
    6. 在通过selectionKey反向获取socketChannel,方法channel()
    7. 可以通过得到的channel,完成业务处理



    相关文章

      网友评论

          本文标题:NIO核心组件 channel、buffer、selector

          本文链接:https://www.haomeiwen.com/subject/acdftktx.html