详解Java中的BIO、NIO、AIO

简介: IO流是Java中比较难理解的一个知识点,但是IO流在实际的开发场景中经常会使用到,比如Dubbo底层就是NIO进行通讯。本文将介绍Java发展过程中出现的三种IO:BIO、NIO以及AIO,重点介绍NIO。

本文收录于JavaStarter ,里面有我完整的Java系列文章,学习或面试都可以看看


(一)引言


IO流是Java中比较难理解的一个知识点,但是IO流在实际的开发场景中经常会使用到,比如Dubbo底层就是NIO进行通讯。本文将介绍Java发展过程中出现的三种IO:BIO、NIO以及AIO,重点介绍NIO。


(二)什么是BIO


BIO即同步阻塞IO,实现模型为一个连接就需要一个线程去处理。这种方式简单来说就是当有客户端来请求服务器时,服务器就会开启一个线程去处理这个请求,即使这个请求不干任何事情,这个线程都一直处于阻塞状态。


BIO模型有很多缺点,最大的缺点就是资源的浪费。想象一下如果QQ使用BIO模型,当有一个人上线时就需要一个线程,即使这个人不聊天,这个线程也一直被占用,那再多的服务器资源都不管用。


(三)BIO代码实践


我们通过socket模拟BIO的实现逻辑


首先建立Server,建立一个ServerSocket对象,绑定端口,然后等待连接,如果连接成功就新建一个线程去处理连接。


publicclassserver {
privatestaticSocketsocket=null;
publicstaticvoidmain(String[] args) {
try {
//绑定端口ServerSocketserverSocket=newServerSocket();
serverSocket.bind(newInetSocketAddress(8080));
while (true){
//等待连接  阻塞System.out.println("等待连接");
socket=serverSocket.accept();
System.out.println("连接成功");
//连接成功后新开一个线程去处理这个连接newThread(newRunnable() {
@Overridepublicvoidrun() {
byte[] bytes=newbyte[1024];
try {
System.out.println("等待读取数据");
//等待读取数据    阻塞intlength=socket.getInputStream().read(bytes);
System.out.println(newString(bytes,0,length));
System.out.println("数据读取成功");
                        } catch (IOExceptione) {
e.printStackTrace();
                        }
                    }
                }).start();
            }
        } catch (IOExceptione) {
e.printStackTrace();
        }
    }
}

接着建立Client代码

publicclassClient {
publicstaticvoidmain(String[] args) {
Socketsocket=null;
try {
socket=newSocket("127.0.0.1",8080);
socket.getOutputStream().write("一条数据".getBytes());
socket.close();
        } catch (IOExceptione) {
e.printStackTrace();
        }
    }
}

客户端的代码就连接一个服务器,然后发出一条数据即可。


这样就实现了一个BIO,但是BIO的缺点实在太明显了,因此在JDK1.4的时候,NIO出现了。


(四)什么是NIO


BIO是阻塞的,如果没有多线程,BIO就需要一直占用CPU,而NIO则是非阻塞IO,NIO在获取连接或者请求时,即使没有取得连接和数据,也不会阻塞程序。NIO的服务器实现模式为一个线程可以处理多个请求(连接)。


NIO有几个知识点需要掌握,Channel(通道)Buffer(缓冲区), Selector(多路复用选择器)


Channel既可以用来进行读操作,又可以用来进行写操作。NIO中常用的Channel有FileChannel 、SocketChannel、ServerSocketChannel、DatagramChannel。


Buffer缓冲区用来发送和接受数据。


Selector 一般称为选择器或者多路复用器 。它是Java NIO核心组件中的一个,用于检查一个或多个NIO Channel(通道)的状态是否处于可读、可写。在javaNIO中使用Selector往往是将Channel注册到Selector中。


网络异常,图片无法展示
|


下面我通过代码的方式模拟javaNIO的运行流程。


(五)NIO代码实践


首先贴上NIO的实践代码:


NIO服务端详细的执行过程是这样的:


1、创建一个ServerSocketChannel和Selector,然后将ServerSocketChannel注册到Selector上


2、Selector通过select方法去轮询监听channel事件,如果有客户端要连接时,监听到连接事件。


3、通过channel方法将socketchannel绑定到ServerSocketChannel上,绑定通过SelectorKey实现。


4、socketchannel注册到Selector上,关心读事件。


5、Selector通过select方法去轮询监听channel事件,当监听到有读事件时,ServerSocketChannel通过绑定的SelectorKey定位到具体的channel,读取里面的数据。


publicclassNioServer {
publicstaticvoidmain(String[] args) throwsIOException {
//创建一个socket通道,并且设置为非阻塞的方式ServerSocketChannelserverSocketChannel=ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false);
serverSocketChannel.socket().bind(newInetSocketAddress(9000));
//创建一个selector选择器,把channel注册到selector选择器上Selectorselector=Selector.open();
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
while (true){
System.out.println("等待事件发生");
selector.select();
System.out.println("有事件发生了");
Iterator<SelectionKey>iterator=selector.selectedKeys().iterator();
while (iterator.hasNext()){
SelectionKeykey=iterator.next();
iterator.remove();
handle(key);
            }
        }
    }
privatestaticvoidhandle(SelectionKeykey) throwsIOException {
if (key.isAcceptable()){
System.out.println("连接事件发生");
ServerSocketChannelserverSocketChannel= (ServerSocketChannel) key.channel();
//创建客户端一侧的channel,并注册到selector上SocketChannelsocketChannel=serverSocketChannel.accept();
socketChannel.configureBlocking(false);
socketChannel.register(key.selector(),SelectionKey.OP_READ);
        }elseif (key.isReadable()){
System.out.println("数据可读事件发生");
SocketChannelsocketChannel= (SocketChannel) key.channel();
ByteBufferbuffer=ByteBuffer.allocate(1024);
intlen=socketChannel.read(buffer);
if (len!=-1){
System.out.println("读取到客户端发送的数据:"+newString(buffer.array(),0,len));
            }
//给客户端发送信息ByteBufferwrap=ByteBuffer.wrap("hello world".getBytes());
socketChannel.write(wrap);
key.interestOps(SelectionKey.OP_READ|SelectionKey.OP_WRITE);
socketChannel.close();
        }
    }
}

客户端代码:NIO客户端代码的实现比BIO复杂很多,主要的区别在于,NIO的客户端也需要去轮询自己和服务端的连接情况。

publicclassNioClient {
publicstaticvoidmain(String[] args) throwsIOException {
//配置基本的连接参数SocketChannelchannel=SocketChannel.open();
channel.configureBlocking(false);
Selectorselector=Selector.open();
channel.connect(newInetSocketAddress("127.0.0.1",9000));
channel.register(selector, SelectionKey.OP_CONNECT);
//轮询访问selectorwhile(true){
selector.select();
Iterator<SelectionKey>iterator=selector.selectedKeys().iterator();
while (iterator.hasNext()){
SelectionKeykey=iterator.next();
iterator.remove();
//连接事件发生if (key.isConnectable()){
SocketChannelsocketChannel= (SocketChannel) key.channel();
//如果正在连接,则完成连接if (socketChannel.isConnectionPending()){
socketChannel.finishConnect();
                    }
socketChannel.configureBlocking(false);
ByteBufferbuffer=ByteBuffer.wrap("客户端发送的数据".getBytes());
socketChannel.write(buffer);
socketChannel.register(selector,SelectionKey.OP_READ);
                }elseif (key.isReadable()){
//读取服务端发送过来的消息read(key);
                }
            }
        }
    }
privatestaticvoidread(SelectionKeykey) throwsIOException {
SocketChannelsocketChannel= (SocketChannel) key.channel();
ByteBufferbuffer=ByteBuffer.allocate(512);
intlen=socketChannel.read(buffer);
if (len!=-1){
System.out.println("客户端收到信息:"+newString(buffer.array(),0,len));
        }
    }
}

效果大概是这样的:首先服务端等待事件发生,当客户端启动时,服务器端先接受到连接的请求,接着接受到数据读取的请求,读完数据后继续等待。


网络异常,图片无法展示
|


客户端发送数据后,获取到了来自服务端的回复。


网络异常,图片无法展示
|


(六)NIO总结


NIO通过一个Selector,负责监听各种IO事件的发生,然后交给后端的线程去处理。NIO相比与BIO而言,非阻塞体现在轮询处理上。BIO后端线程需要阻塞等待客户端写数据,如果客户端不写数据就一直处于阻塞状态。而NIO通过Selector进行轮询已注册的客户端,当有事件发生时才会交给后端去处理,后端线程不需要等待。


(七)什么是AIO


AIO是在JDK1.7中推出的新的IO方式--异步非阻塞IO,也被称为NIO2.0,AIO在进行读写操作时,直接调用API的read和write方法即可,这两种均是异步的方法,且完成后会主动调用回调函数。简单来讲,当有流可读取时,操作系统会将可读的流传入read方法的缓冲区,并通知应用程序;对于写操作而言,当操作系统将write方法传递的流写入完毕时,操作系统主动通知应用程序。


Java提供了四个异步通道:AsynchronousSocketChannel、AsynchronousServerSocketChannel、AsynchronousFileChannel、AsynchronousDatagramChannel。


(八)AIO代码实践


服务器端代码:AIO的创建方式和NIO类似,先创建通道,再绑定,再监听。只不过AIO中使用了异步的通道。


publicclassAIOServer {
publicstaticvoidmain(String[] args) {
try {
//创建异步通道AsynchronousServerSocketChannelserverSocketChannel=AsynchronousServerSocketChannel.open();
serverSocketChannel.bind(newInetSocketAddress(8080));
System.out.println("等待连接中");
//在AIO中,accept有两个参数,// 第一个参数是一个泛型,可以用来控制想传递的对象// 第二个参数CompletionHandler,用来处理监听成功和失败的逻辑//  如此设置监听的原因是因为这里的监听是一个类似于递归的操作,每次监听成功后要开启下一个监听serverSocketChannel.accept(null, newCompletionHandler<AsynchronousSocketChannel, Object>() {
//请求成功处理逻辑@Overridepublicvoidcompleted(AsynchronousSocketChannelresult, Objectattachment) {
System.out.println("连接成功,处理数据中");
//开启新的监听serverSocketChannel.accept(null,this);
handledata(result);
                }
@Overridepublicvoidfailed(Throwableexc, Objectattachment) {
System.out.println("失败");
                }
            });
try {
TimeUnit.SECONDS.sleep(Integer.MAX_VALUE);
            } catch (InterruptedExceptione) {
e.printStackTrace();
            }
        } catch (IOExceptione) {
e.printStackTrace();
        }
    }
privatestaticvoidhandledata(AsynchronousSocketChannelresult) {
ByteBufferbyteBuffer=ByteBuffer.allocate(1024);
//通道的read方法也带有三个参数//1.目的地:处理客户端传递数据的中转缓存,可以不使用//2.处理客户端传递数据的对象//3.处理逻辑,也有成功和不成功的两个写法result.read(byteBuffer, byteBuffer, newCompletionHandler<Integer, ByteBuffer>() {
@Overridepublicvoidcompleted(Integerresult, ByteBufferattachment) {
if (result>0){
attachment.flip();
byte[] array=attachment.array();
System.out.println(newString(array));
                }
            }
@Overridepublicvoidfailed(Throwableexc, ByteBufferattachment) {
System.out.println("失败");
            }
        });
    }
}

客户端代码基本上没有太多差别,主要还是实现数据的发送功能

publicclassAIOClient {
publicstaticvoidmain(String[] args) {
try {
AsynchronousSocketChannelsocketChannel=AsynchronousSocketChannel.open();
socketChannel.connect(newInetSocketAddress("127.0.0.1",8080));
Scannerscanner=newScanner(System.in);
Stringnext=scanner.next();
ByteBufferbyteBuffer=ByteBuffer.allocate(1024);
byteBuffer.put(next.getBytes());
byteBuffer.flip();
socketChannel.write(byteBuffer);
        } catch (IOExceptione) {
e.printStackTrace();
        }
    }
}

观察结果:


网络异常,图片无法展示
|

相关文章
|
1月前
|
网络协议 Dubbo Java
一文搞懂NIO、AIO、BIO的核心区别(建议收藏)
本文详细解析了NIO、AIO、BIO的核心区别,NIO的三个核心概念,以及NIO在Java框架中的应用等。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
一文搞懂NIO、AIO、BIO的核心区别(建议收藏)
|
5天前
|
监控 Java API
探索Java NIO:究竟在哪些领域能大显身手?揭秘原理、应用场景与官方示例代码
Java NIO(New IO)自Java SE 1.4引入,提供比传统IO更高效、灵活的操作,支持非阻塞IO和选择器特性,适用于高并发、高吞吐量场景。NIO的核心概念包括通道(Channel)、缓冲区(Buffer)和选择器(Selector),能实现多路复用和异步操作。其应用场景涵盖网络通信、文件操作、进程间通信及数据库操作等。NIO的优势在于提高并发性和性能,简化编程;但学习成本较高,且与传统IO存在不兼容性。尽管如此,NIO在构建高性能框架如Netty、Mina和Jetty中仍广泛应用。
20 3
|
12天前
|
存储 监控 Java
Java的NIO体系
通过本文的介绍,希望您能够深入理解Java NIO体系的核心组件、工作原理及其在高性能应用中的实际应用,并能够在实际开发中灵活运用这些知识,构建高效的Java应用程序。
28 5
|
1月前
|
Java
BIO、NIO、AIO 有什么区别
BIO(阻塞I/O)模型中,服务器实现模式为一个连接一个线程;NIO(非阻塞I/O)使用单线程或少量线程处理多个请求;AIO(异步I/O)则是在NIO基础上进一步优化,采用事件通知机制,提高并发处理能力。
58 5
|
1月前
|
消息中间件 监控 Java
BIO、NIO、AIO在不同场景下的应用对比
BIO(阻塞I/O)、NIO(非阻塞I/O)和AIO(异步I/O)是Java中处理I/O操作的三种模式。BIO适用于连接数少且稳定的场景;NIO通过非阻塞模式提高并发处理能力,适合高并发场景;AIO则完全异步,适合需要高效、低延迟的I/O操作场景。
101 4
|
1月前
|
消息中间件 缓存 Java
java nio,netty,kafka 中经常提到“零拷贝”到底是什么?
零拷贝技术 Zero-Copy 是指计算机执行操作时,可以直接从源(如文件或网络套接字)将数据传输到目标缓冲区, 而不需要 CPU 先将数据从某处内存复制到另一个特定区域,从而减少上下文切换以及 CPU 的拷贝时间。
java nio,netty,kafka 中经常提到“零拷贝”到底是什么?
|
2月前
|
Java
让星星⭐月亮告诉你,Java NIO之Buffer详解 属性capacity/position/limit/mark 方法put(X)/get()/flip()/compact()/clear()
这段代码演示了Java NIO中`ByteBuffer`的基本操作,包括分配、写入、翻转、读取、压缩和清空缓冲区。通过示例展示了`position`、`limit`和`mark`属性的变化过程,帮助理解缓冲区的工作原理。
38 2
|
3月前
|
存储 网络协议 Java
Java NIO 开发
本文介绍了Java NIO(New IO)及其主要组件,包括Channel、Buffer和Selector,并对比了NIO与传统IO的优势。文章详细讲解了FileChannel、SocketChannel、ServerSocketChannel、DatagramChannel及Pipe.SinkChannel和Pipe.SourceChannel等Channel实现类,并提供了示例代码。通过这些示例,读者可以了解如何使用不同类型的通道进行数据读写操作。
Java NIO 开发
|
2月前
|
Java Linux 应用服务中间件
【编程进阶知识】高并发场景下Bio与Nio的比较及原理示意图
本文介绍了在Linux系统上使用Tomcat部署Java应用程序时,BIO(阻塞I/O)和NIO(非阻塞I/O)在网络编程中的实现和性能差异。BIO采用传统的线程模型,每个连接请求都会创建一个新线程进行处理,导致在高并发场景下存在严重的性能瓶颈,如阻塞等待和线程创建开销大等问题。而NIO则通过事件驱动机制,利用事件注册、事件轮询器和事件通知,实现了更高效的连接管理和数据传输,避免了阻塞和多级数据复制,显著提升了系统的并发处理能力。
78 0
|
Java
Java NIO系列教程三
​ 今天主要给大家介绍的是Buffer的基本使用这个也是NIO里面最总要的概率之一,里面的操作也是有一些复杂的同时也是需要大家必须要重点掌握的知识点,同时也介绍了一下Selector的用法下一篇文章我们将为大家介绍Pipe管道以及FileLock文件锁这也是NIO里面最后的一分部内容了。
104 0