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