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 {
        }
    }

}

 

posted @ 2018-03-02 16:09  NoYone  阅读(328)  评论(0编辑  收藏  举报