• 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 {
            }
        }
    
    }
  • 相关阅读:
    写给太阳村张老师及其员工的公开信
    不尽的想法,不够的时间
    XP+新装SQL Server 2005出现无法连接的问题+解决
    【Windows编程】【网络编程】【基于网络端口通信的客户端应用程序】解决方案【示意程序】
    [VS2005SP1]如何创建从母版页继承的Web窗体?(SP1所带来的小小变更)
    小程序大问题,MSDN中一个小小示例所带来的疑问,一个关于DataList的一个简单应用
    [Oracle]ASP.NET+Oracle连接类conn.cs
    SQLServer2005出了点怪事~(应该是编码问题~)
    [ASPNET2.0]Membership类+SQLServer2005,AspNet_regsql.exe的使用
    Originality Life~Some Desktop Design (From Google Ideas)+ Pictures & PNG Files & 3DMAX Files download!
  • 原文地址:https://www.cnblogs.com/NoYone/p/8493785.html
Copyright © 2020-2023  润新知