zoukankan      html  css  js  c++  java
  • Java NIO技术概述

    NIO(no-blocking I/O,也有人叫它new I/O),是一种非阻塞型I/O,是I/O多路复用的基础。NIO对于高并发长连接处理器,或者大文件在网络中的传输,具有很大的意义。

    那么NIO对BIO的优势是什么呢?

    1. 高并发,大量长连接情形下。

    先说BIO的解决方案,即“一个连接占用一个线程”。

    那么可想而知,对于连接较多的服务器,会因为线程的创建和切换而浪费非常多的资源。NIO对于这种资源的浪费有很好的解决方式。

    解决的方式是:将连接和数据传输分离。

    简单的说NIO的思想是,先将连接放到一个管道里(channel),而当真正进行数据传输的时候,才把管道放到线程池里面去进行数据传输。

    下面是一个NIO的serverSocket写法,注释写的比较明白了,需要注意的地方就是channel,selector,ByteBuffer,这三个是NIO包里非常重要的东西。

    NIO能做到这种分离的原因就在这“三剑客”里,selector相当于起了个单线程,在channel里面轮循来判断是否有数据过来了,ByteBuffer直接操作的是堆外内存,就摆脱了GC的管控。

    package DealSocket;
    
    
    import java.io.IOException;
    import java.net.InetSocketAddress;
    import java.nio.ByteBuffer;
    import java.nio.channels.*;
    import java.util.Iterator;
    import java.util.Set;
    import java.util.concurrent.LinkedBlockingDeque;
    import java.util.concurrent.ThreadPoolExecutor;
    import java.util.concurrent.TimeUnit;
    
    /**
     * NIO服务器
     */
    public class SocketServerNIO {
        private static ServerSocketChannel serverSocketChannel;
        public static final ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(25,50,60,TimeUnit.SECONDS,new LinkedBlockingDeque<Runnable>());
        public static Selector selector;
        public static void main(String[] args) throws IOException {
            serverSocketChannel = ServerSocketChannel.open();
            serverSocketChannel.configureBlocking(false); //非阻塞状态
            serverSocketChannel.bind(new InetSocketAddress(8080));
    
            System.out.println("NIO启动" + "监听在8080");
    
            // Selector
            // socket 操作系统层面保存的
    
            selector = Selector.open();
    
            // 查询有哪些客户端和服务端建立连接
            // 查询条件是:OP_ACCEPT建立新连接的意思
            // 注册一个选择器,这儿是把主线程放了进去,一直来监听连接
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
    
            while (true){
                // 根据已有的条件去绑定的channel查
                // 如果一直没结果,加个超时时间
                selector.select(1000L);
    
                // 获取选择器中的结果
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                Iterator<SelectionKey> iterator = selectionKeys.iterator();
                while (iterator.hasNext()){
                    // 返回值
                    SelectionKey result = (SelectionKey) iterator.next();
                    if (result.isAcceptable()){ // 如果是isAcceptable,新的连接建立了
                        // 从通道中获取连接
                        SocketChannel connect = serverSocketChannel.accept();
                        connect.configureBlocking(false);
    
    
                        // 不知道有没有数据请求,仅仅是个连接,还不需要创建线程
                        // 有数据的筛选条件,是这个判断条件做的判断的,不需要自己看是否有数据
                        connect.register(selector,SelectionKey.OP_READ);
    
                        System.out.println("新连接来了");
                    }else if (result.isReadable()){ //如果isReadable,有数据过来了
                        // 从结果中,取出socket连接
                        // 告诉selector,接下来这个socket连接,不要帮我去查了,因为我已经在处理了。
                        SocketChannel connect = (SocketChannel) result.channel();
    
                        // 接下来真正读数据才丢到线程池
                        threadPoolExecutor.execute(new NioSocketProcesser(connect));
                    }
                }
                //清空上一次的查询结果
                selectionKeys.clear();
    
                //清除正在被处理的,不需要被查询的记录
                selector.selectNow();
            }
    
    
    
        }
    }
    
    class NioSocketProcesser implements Runnable{
    
        SocketChannel socketChannel;
    
        public NioSocketProcesser(SocketChannel socketChannel){
            this.socketChannel = socketChannel;
        }
        @Override
        public void run() {
            // no-blocking IO
            try{
                ByteBuffer dst = ByteBuffer.allocate(1024);
                socketChannel.read(dst);
                // 将缓冲区转换为读取的模式
                dst.flip();
                // 转换为字符串
                byte[] array = dst.array();
                String request = new String(array);
                System.out.println("收到新数据,当前线程数量:" + SocketServerNIO.threadPoolExecutor.getActiveCount() + ",请求内容" + request);
                // 读完就清空了
                dst.clear();
    
                /**
                 * 响应客户端
                 */
                byte[] reponse = ("tony" + System.currentTimeMillis()).getBytes();
                ByteBuffer resp = ByteBuffer.wrap(reponse);
    
                //响应
                socketChannel.write(resp);
                resp.clear();
                // ByteBuffer的wrap将字符串写入的
            } catch (IOException e) {
                e.printStackTrace();
            }finally {
                // 最终还是要继续去监听是否有可读数据过来,再注册一遍是因为上面判断有可读数据过来后,停止监听了。
                try {
                    socketChannel.register(SocketServerNIO.selector, SelectionKey.OP_READ);
                } catch (ClosedChannelException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    2. NIO在大文件传输中的优势

    BIO读程序是将程序的用户态切换到内核态,然后内核再调用os的read()、write()将内容放到内核缓冲区,java程序再从内核缓冲区读文件。

    而NIO呢,是通过“内存映射机制”来读文件,简单的说就是一块内存来映射磁盘上的文件,程序直接操作内存,并且映射出来的文件并不是在堆内存里的,所以也省了从内核缓冲区到jvm缓冲区的时间了。

    package NIO;
    
    
    import org.junit.Test;
    
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.IOException;
    import java.io.RandomAccessFile;
    import java.nio.MappedByteBuffer;
    import java.nio.channels.Channel;
    import java.nio.channels.FileChannel;
    import java.nio.charset.StandardCharsets;
    import java.util.Scanner;
    
    public class TestNio {
        FileInputStream inputStream = null;
        Scanner sc = null;
        String path = System.getProperty("user.dir");
        String filePath = path + File.separator + "src" + File.separator + "file.txt";
        /**
         * 用scanner一行一行读取,获取换行符System.getProperty("file.separator")
         */
        @Test
        public void testScan(){
    //        File file = new File(path);
            String FilePathname = null;
            try {
                FileInputStream fileInputStream = new FileInputStream(filePath);
                sc = new Scanner(fileInputStream);
                while (sc.hasNextLine()) {
                    String s = sc.nextLine();
                    System.out.println(s);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        /**
         * NIO内存映射读取文件
         */
        @Test
        public void testNio(){
            File file = new File(java.lang.String.valueOf(filePath));
            long length = file.length();
            try {
                FileChannel channel = new RandomAccessFile(filePath, "r").getChannel();
                MappedByteBuffer mappedByteBuffer = channel.map(FileChannel.MapMode.READ_ONLY, 0, length);
                int buffersize = 1024 * 1024 *5;
                mappedByteBuffer.rewind();
    
                int remain;
                int position = 0;
                while((remain = mappedByteBuffer.remaining()) > 0){
                    byte[] dst = new byte[remain > buffersize ? buffersize : remain];
                    mappedByteBuffer.get(dst, 0, dst.length);
                    String string = new String(dst);
                    System.out.println(string);
                    channel.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }finally {
            }
        }
    
    }
  • 相关阅读:
    保险行业电话外呼型呼叫中心方案
    12355青少年服务台呼叫中心解决方案
    未能找到类型集或命名空间名称 "xxxxxx" (是否缺少using 指令或引用?)
    Smarty中section的使用
    什么是Asterisk,它如何帮助我们的呼叫中心?
    高效呼叫中心的8个健康工作习惯
    Python 爬起数据时 'gbk' codec can't encode character 'xa0' 的问题
    Python 网页解析器
    Python 爬虫入门3种方法
    Python open 读写小栗子
  • 原文地址:https://www.cnblogs.com/NoYone/p/8493785.html
Copyright © 2011-2022 走看看