当前位置: 首页 > 后端技术 > Java

JavaNIO全面讲解(看这篇文章就够了)

时间:2023-04-01 22:50:16 Java

很多技术框架都使用了NIO技术,学习和掌握JavaNIO技术对于高性能、高并发的网络应用来说是非常关键的@mikechenNIO介绍NIO中的N个都能看懂它作为Non-blocking,不仅仅是New,而是解决高并发和高I/O性能的有效途径。JavaNIO是Java1.4之后推出的一套IO接口。NIO提供了完全不同的运行模式。NIO支持面向缓冲区和基于通道的IO操作。增加了很多处理输入输出的新类,这些类都放在了java.nio包和子包下,并且重写了原来java.io包中的很多类,增加了满足NIO的新功能。NIOVSBIOBIOBIO全称是BlockingIO,同步阻塞IO,是JDK1.4之前的传统IO模型。JavaBIO:服务端的实现方式是一个线程一个连接,即当客户端有连接请求时,服务端需要开启一个线程进行处理,如下图所示:虽然此时服务端有很高的并发能力一次,它可以处理多个客户端的请求,但是带来了一个问题。随着开启线程数的增加,会消耗过多的内存资源,导致服务器变慢甚至崩溃。NIO可以在一定程度上解决这个问题。NIOJavaNIO:同步非阻塞,服务端实现方式为一个线程处理多个请求(连接),即客户端发送的连接请求会注册到多路复用器上,多路复用器以I/O请求轮询连接被处理。一个线程可以调用多路复用接口(java中的select)同时阻塞和监听来自多个客户端的IO请求。一旦接收到IO请求,就会调用相应的函数进行处理。NIO擅长用一个线程管理多个连接。节省系统资源。NIO的核心实现NIO包含三个核心组件:Channel(通道)Buffer(缓冲区)Selector(选择器)关系图说明:每个Channel对应一个Buffer。Selector对应一个线程,一个线程对应多个Channel。该图反映了有三个通道注册到选择器。程序切换到哪个Channel是由事件(Event)决定的。选择器会根据不同的事件打开每个通道。Buffer是一块内存,底部是一个数组。通过Buffer读写数据,但是需要flip()来切换读写模式,而BIO是单向的,要么是输入流,要么是输出流。Channel(通道)Channel是NIO的核心概念,代表一个开放的连接,可以连接到一个I/O设备(例如:磁盘文件,Socket)或者一个支持I/O访问的应用程序。JavaNIO使用缓冲区域和通道进行数据传输。通道的主要实现类:FileChannel类本地文件IO通道,用于对文件通道进行读、写、映射和操作。使用文件通道操作文件的大致过程是:1)获取通道文件通道通过FileChannel()的静态方法open获取,获取时需要指定文件路径和文件打开方式。//获取文件通道FileChannel.open(Paths.get(fileName),StandardOpenOption.READ);2)创建字节缓冲区与文件相关的字节缓冲区有两种,一种是基于堆的HeapByteBuffer,另一种是基于文件映射放置在堆外内存中的MappedByteBuffer。//分配字节缓冲区ByteBufferbuf=ByteBuffer.allocate(10);3)读写操作一般需要循环结构来读取数据,读取数据时需要注意切换ByteBuffer的读写模式。while(channel.read(buf)!=-1){//读取通道中的数据并写入bufbuf.flip();//将缓冲区切换为读取模式while(buf.position()0){//轮询,返回时有ready事件Setkeys=selector.selectedKeys();//Getreadyeventcollection......}有3种选择ready事件的方法有两种:1)select()阻塞方法在有ready事件时返回,或者其他线程调用wakeup()或者当前线程被中断。2)select(longtimeout)阻塞方法在有ready事件,或者其他线程调用wakeup(),或者当前线程被中断,或者阻塞时长达到timeout时返回。不会抛出超时异常。3)selectNode()不阻塞,如果没有就绪事件,则返回0;如果有就绪事件,则将就绪事件放入集合中,并返回就绪事件的个数。5处理就绪事件\每次可以选择一批就绪事件,所以需要迭代这些事件。for(SelectionKeykey:keys){if(key.isWritable()){//可写事件if("Bye".equals((line=scanner.nextLine()))){socketChannel.shutdownOutput();socketChannel.close();break;}buf.put(line.getBytes());buf.flip();socketChannel.write(buf);buf.compact();}}可以从一个SelectionKey对象中得到:1)ready事件对应频道;2)就绪事件。有了这些信息,您就可以轻松地执行I/O操作。NIO源码案例NIOServerpublicstaticvoidmain(String[]args)throwsException{//创建ServerSocketChannel,-->>ServerSocketServerSocketChannelserverSocketChannel=ServerSocketChannel.open();InetSocketAddressinetSocketAddress=newInetSocketAddress(5555.bind(inetSocketAddress);serverSocketChannel.configureBlocking(false);//设置为非阻塞//打开selector,并注册接受事件Selectorselector=Selector.open();serverSocketChannel.register(selector,SelectionKey.OP_ACCEPT);while(true){selector.select(2000);//监听所有通道//遍历selectionKeysSetselectionKeys=selector.selectedKeys();Iteratoriterator=selectionKeys.iterator();while(iterator.hasNext()){SelectionKeykey=iterator.next();if(key.isAcceptable()){//处理连接事件SocketChannelsocketChannel=serverSocketChannel.accept();socketChannel.configureBlocking(false);//设置为非阻塞System.out.println("client:"+socketChannel.getLocalAddress()+"isconnect");socketChannel.register(选择器,SelectionKey.OP_READ);//向选择器注册客户端读取事件}elseif(key.isReadable()){//处理读取事件ByteBufferbyteBuffer=ByteBuffer.allocate(1024);SocketChannel通道=(SocketChannel)key.channel();channel.read(byteBuffer);System.out.println("client:"+channel.getLocalAddress()+"发送"+newString(byteBuffer.array()));}迭代器.remove();//事件处理完记得清除它}}}NIOClientpublicclassNIOClient{publicstaticvoidmain(String[]args)throwsException{SocketChannelsocketChannel=SockettChannel.open();socketChannel.configureBlocking(false);InetSocketAddressinetSocketAddress=newInetSocketAddress("127.0.0.1",5555);if(!socketChannel.connect(inetSocketAddress)){while(!socketChannel.finishConnect()){System.out.println("客户端正在连接,请耐心等待");}}ByteBufferbyteBuffer=ByteBuffer.wrap("mikechen的互联网架构".getBytes());socketChannel.write(byteBuffer);socketChannel.close();}}以上作者简介陈锐|mikechen,10年+大工厂架构经验,《BAT架构技术500期》系列文章作者,分享十余年BAT架构经验和面试经验!|JVM|MySQL|Spring|Redis|分布式|高并发|架构师关注《Mikechen的互联网架构》公众号,回复【架构】获取我的原创《300 期 + BAT 架构技术系列与 1000 + 大厂面试题答案》