java输出 一文看懂java io系统

当前位置:首页 > 财经

java输出 一文看懂java io系统

发布时间:2020-12-18 19:29:41

学习java IO系统,重点是学习IO模型。了解了各种IO模型之后,可以更好的理解java IO

Java IO是Java用来读写数据(输入输出)的一套API。大多数程序必须处理一些输入,并从中产生一些输出。Java为此提供了java.io包

java中的Io系统可以分为三种io模式:Bio、Nio和Aio

关于Bio,我们需要知道什么是同步阻塞IO模型,Bio操作的对象:流,以及如何使用Bio进行网络编程,使用Bio进行网络编程的问题关于Nio,我们需要知道什么是同步非阻塞IO模型,什么是多路复用Io模型,以及Nio中的Buffer,Channel,Selector的概念,以及如何使用Nio进行网络编程关于Aio,我们需要知道什么是异步非阻塞IO模型,Aio可以使用几种方式实现异步操作,以及如何使用Aio进行网络编程

个人简历

BIO是同步阻塞IO。JDK1.4之前,只有一个IO模型。BIO操作的对象是流。一个线程只能处理一个流的IO请求。如果您想同时处理多个流,您需要使用多线程

流包括字符流和字节流,在概念上是连续的数据流。当程序需要读取数据时,需要使用输入流来读取数据,当需要写出数据时,需要输出流

阻塞IO模型

在这种模式下,我们通常使用一个线程来接受请求,然后使用一个线程池来处理请求,并以这种方式同时管理多个套接字客户端连接,如下所示:

使用BIO模型进行网络编程的问题在于缺乏灵活性和可扩展性。并发客户端访问的数量与服务器线程的数量之间的关系是1:1。此外,大量线程正在等待输入或输出数据准备就绪,导致资源浪费。面对大量并发,如果不使用线程池直接新建线程,线程会膨胀,系统性能下降,可能导致堆栈内存溢出。而且频繁的创建和销毁线程会浪费资源。

使用线程池可能是更好的解决方案,但不能解决阻塞IO的阻塞问题。而且需要考虑的是,如果线程池的数量设置的很少,那么就会有大量的Socket客户端被拒绝。如果线程池的数量设置得很大,就会导致大量的上下文切换。此外,程序应该为每个线程的调用堆栈分配内存,其默认大小范围是64 KB到1 MB,这浪费了虚拟机内存

BIO模型适用于链接数固定、链接相对较少的架构,但使用该模型编写的代码更直观、简单、易懂

尼奥

自1.4版以来,JDK发布了一个全新的输入输出类库,简称NIO,这是一个同步无阻塞IO模型

非阻塞输入输出模型

同步非阻塞io模型的实现;

非阻塞输入输出模型

应用程序进程从系统调用中调用recvfrom。如果内核数据没有准备好,它将直接返回EWOULDBLOCK错误,应用程序进程不会被阻塞。但是它需要应用程序进程不断轮询和调用recvfrom,直到内核数据准备好,然后等待数据从内核复制到用户空(这个时间会被阻塞,但是花费的时间很少),复制完成后返回。

输入输出重用模型

IO重用模型利用Linux系统提供的select和poll系统调用转移一个或多个文件句柄(网络编程中的客户端链接)来选择或轮询系统调用,应用进程在select上被阻塞,从而形成多个Socket链接对应的进程,然后select/poll会对Socket链接集合进行线性扫描。当只有几个套接字有数据时,效率会降低,并且select/poll受到持有的文件句柄数量的限制,默认值为1024

信号驱动输入输出模型

系统调用sigaction来执行信号处理功能。这个系统调用不会阻塞应用程序进程。当数据准备好时,它会为进程生成一个SIGIO信号,并通知应用程序调用recvfrom通过信号回调读取数据

NIO的核心概念

缓冲区

缓冲区是一个对象,它包含一些要写入或读取的数据。在NIO中,所有的数据都由缓冲区处理。读取数据的时候应该是从缓冲区读取,写入数据的时候会先写入缓冲区。缓冲区本质上是一个数组,数据可以从其中写入,然后读取。它提供对数据的结构化访问,并维护内部读写位置。

实例化字节缓冲区

//创建一个1024字节的缓冲区

字节缓冲区=字节缓冲区.分配(1024);

如何使用缓冲区:

写入数据到Buffer调用flip方法将Buffer从写模式切换到读模式从Buffer中读取数据调用clear方法或者compact方法清空缓冲区,让它可以再次被写入

更多细节请看这里:http://ifeve.com/buffers/

频道

通道数据总是从通道读取到缓冲区,或者从缓冲区写入通道。通道只负责传输数据,而操作数据是缓冲区

渠道类似于流程,除了:

在于条通道是双向的,可以同时进行读,写操作,而流是单向流动的,只能写入或者读取流的读写是阻塞的,通道可以异步读写

数据从通道读取到缓冲区

inChannel.read(缓冲区);

数据从缓冲区写入通道

outChannel.write(缓冲区);

更多细节请看这里:http://ifeve.com/channels/

以复制文件为例

文件输入流文件输入流=新文件输入流(新文件(src));

文件输出流文件输出流=新文件输出流(新文件(dst));

//获取输入/输出通道

file channel in channel = file inputstream . GetChannel;

file channel outChannel = file output stream . getchannel;

//创建一个1024字节的缓冲区

字节缓冲区=字节缓冲区.分配(1024);

while( true){

//从inChannel读取数据,读不到字节返回-1,文件读取完毕

inteof =inChannel.read(缓冲区);

if(eof== -1){

打破;

{}

//将缓冲区从写入模式切换到读取模式

buffer.flip

//开始向输出通道写入数据

outChannel.write(缓冲区);

//clear空缓冲区

buffer.clear

{}

在加拿大。关闭;

outChannel。关闭;

fileInputStream。关闭;

文件输出流。关闭;

选择器(复用器)

选择器是NIO编程的基础,它的主要功能是在选择器上注册多个通道。如果通道上发生读或写事件,通道将处于就绪状态,选择器将对其进行轮询,然后通过输入输出操作的选择键获得就绪通道集

选择器与通道和缓冲器的关系

更多细节请看这里:http://ifeve.com/selectors/

NIO模型网络编程

JDK的NIO使用多路输入输出模型。通过将多个输入输出块多路复用到一个选择块,系统可以在一个线程中同时处理多个客户端请求,从而节省系统开销。在JDK1.4和1.5更新10之前,JDK选择器是基于选择/轮询模型实现的。在JDK 1.5更新10和更高版本中,epoll用于底层,而不是选择/轮询。

Epoll比select/poll具有优势:

epoll支持打开的文件描述符数量不在受限制,select/poll可以打开的文件描述符数量有限select/poll使用轮询方式遍历整个文件描述符的集合,epoll基于每个文件描述符的callback函数回调

选择、轮询和epoll都是IO复用的机制。I/O复用是一种机制,通过这种机制,一个进程可以监视多个描述符,一旦一个描述符就绪(通常是读就绪或写就绪),就可以通知程序执行相应的读写操作。但是select、poll、epoll本质上都是同步I/O,因为读写事件准备好之后都需要负责读写,这意味着读写过程被阻塞,而异步I/O不需要负责读写。

NIO提供了两种不同的SocketChannel来实现网络编程,ServerSocketChannel和client socket channel,两者都支持阻塞和非阻塞模式

服务器代码

服务器接受客户端发送的消息输出,并向客户端发送消息

//创建多路选择器

选择器选择器=选择器。打开;

//创建一个通道对象通道,并监听端口9001

serversocontchchannel = serversocontchchannel . open . bind(Newinetsocketaddress(9001));

//将频道设置为非阻塞

channel . configureblocking(false);

//

/**

* 1。选择键。操作_连接:连接事件

* 2。选择键。操作接受:接收事件

* 3。选择键。操作_读取:读取事件

* 4。选择键。写事件

*

*将通道绑定到选择器,并注册OP_ACCEPT事件

*/

频道。寄存器(选择器,选择键。OP _ ACCEPT);

while( true){

//只有当OP_ACCEPT事件到达时,selector.select才会返回(一个键),如果事件没有到达,就会一直被阻塞

选择器. select;

//当一个事件到达时,选择不被阻止,然后选择键。选择键将得到已经到达的事件的选择键集。

Set keys = selector.selectedKeys

迭代器= keys.iterator

while(iterator.hasNext){

SelectionKey key = (SelectionKey)迭代器. next;

//删除此选择键,防止下一个选择方法返回已处理的通道

iterator.remove

//根据选择键的状态

if(key.isConnectable){

//连接成功

} elseif(key.isAcceptable){

/**

*接受客户请求

*

*因为我们只注册了OP_ACCEPT事件,所以有客户端链接,我们只去这里。

*我们要做的是读取客户端的数据,所以我们需要根据选择键获取服务器通道

*根据服务器通道获取客户端通道,然后为其注册一个OP_READ事件。

*/

// 1,获取服务器套接字通道

服务器套接字通道服务器通道=(服务器套接字通道)密钥.通道;

// 2,因为已经确定事件已经到达,所以accept方法不会阻塞

socket channel client channel = server channel . accept;

// 3,将频道设置为非阻塞

client channel . configureblocking(false);

// 4,寄存器OP_READ事件

clientChannel。寄存器(键选择器,选择键。OP _ READ);

} elseif(key.isReadable){

//通道可以读取数据

/**

*因为客户端连接到服务器后,会注册一个OP_READ事件并发送一些数据

*因此,您仍然需要首先获得clientChannel。

*然后通过缓冲区读取客户端通道的数据

*/

socket channel client channel =(socket channel)key . channel;

字节缓冲区字节缓冲区=字节缓冲区.分配(BUF _ SIZE);

longbyteslead = client channel . read(Bytebuffer);

while(BytesRead & gt;0){

byteBuffer.flip

System.out.println("客户端数据:"+ newString(byteBuffer。array));

byteBuffer.clear

bytes read = client channel . read(Bytebuffer);

{}

/**

*在我们的服务器收到信息后,我们将向客户端发送另一个数据。

*/

byteBuffer.clear

ByteBuffer.put("你好客户端,我是服务器,你看NIO多难"。UTF-8);

byteBuffer.flip

client channel . write(Bytebuffer);

} else if(key . IsWritable & amp;& ampkey.isValid){

//通道可以写数据

{}

{}

{}

客户端代码

客户端连接到服务器后,首先向服务器发送消息,并接受服务器发送的消息

选择器选择器=选择器。打开;

socket channel client channel = socket channel . open;

//将频道设置为非阻塞

client channel . configureblocking(false);

//连接到服务器

client channel . connect(Newinetsocketaddress(9001));

//注册OP_CONNECT事件

clientChannel。寄存器(选择器,选择键。OP _ CONNECT);

while( true){

//如果事件没有到达,将一直被阻止

选择器. select;

迭代器<。选择键>iterator = selector . selected Keys . iterator;

while(iterator.hasNext){

SelectionKey key = iterator.next

iterator.remove

if(key.isConnectable){

/**

*与服务器的连接成功

*

*先获取clientChannel,然后通过Buffer写入数据,再为clientChannel注册OP_READ时间。

*/

client channel =(SocketChannel)key . channel;

if(client channel . IsConnectionPending){

client channel . finish connect;

{}

client channel . configureblocking(false);

字节缓冲区字节缓冲区=字节缓冲区.分配(1024);

byteBuffer.clear

ByteBuffer.put("你好服务器,我是客户端,你觉得这个NIO难吗"。UTF-8);

byteBuffer.flip

client channel . write(Bytebuffer);

clientChannel。寄存器(键选择器,选择键。OP _ READ);

} elseif(key.isReadable){

//通道可以读取数据

client channel =(SocketChannel)key . channel;

字节缓冲区字节缓冲区=字节缓冲区.分配(BUF _ SIZE);

longbyteslead = client channel . read(Bytebuffer);

while(BytesRead & gt;0){

byteBuffer.flip

System.out.println("服务器数据:"+ newString(byteBuffer。array));

byteBuffer.clear

bytes read = client channel . read(Bytebuffer);

{}

} else if(key . IsWritable & amp;& ampkey.isValid){

//通道可以写数据

{}

{}

{}

使用本机NIO类库非常复杂。NIO的类库和Api使用起来比较复杂麻烦。要写出高质量的NIO程序,需要熟悉网络编程。所以不建议直接使用原生NIO进行网络编程,而是使用一些成熟的框架,比如Netty。

免疫球蛋白源的淀粉样蛋白

JDK1.7升级了Nio类库,变成了Nio2.0,最重要的是提供异步文件的io操作和事件驱动io。AIO的异步套接字通道是真正的异步无阻塞IO

异步输入输出模型

在Linux系统中,应用进程发起读操作,可以立即做其他事情。内核将准备并将数据复制到空中,然后告诉应用程序进程数据已经被复制,以完成读取操作

Aio模型网络编程

异步操作

Aio可以异步读写,无需通过多路复用器轮询注册的通道,简化了NIO的编程模型

Aio通过异步通道实现异步操作,提供了两种获取操作结果的方式:

通过Future类来获取异步操作的结果,不过要注意的是future.get是阻塞方法,会阻塞线程通过回调的方式进行异步,通过传入一个CompletionHandler的实现类进行回调,CompletionHandler定义了两个方法,completed和failed两方法分别对应成功和失败

Aio中的通道都支持上述两种方法

AIO提供了相应的异步套接字来实现网络编程。服务器端是异步服务器套接字通道,客户端是异步套接字通道

服务器

服务器向客户端发送消息,并接受客户端发送的消息

异步服务器SocketChannel服务器=异步服务器socketchannel . open . bind(Newinetsocketaddress(" 127 . 0 . 0 . 1 ",9001));

//异步接受请求

server.accept( null,newCompletionHandler & lt异步套接字通道,无效>{

//成功时

@覆盖

publicvoidcompleted(异步套接字通道结果,无效附件){

尝试{

字节缓冲区=字节缓冲区.分配(1024);

Buffer.put("我是服务器,你好客户端"。getBytes);

buffer.flip

result.write(buffer,null,newCompletionHandler & lt整数,无效>{

@覆盖

publicvoidcompleted(整数结果,无效附件){

System.out.println("服务器发送消息成功");

{}

@覆盖

publicvoidfailed(Throwable exc,Void attachment){

System.out.println("发送失败");

{}

});

ByteBuffer read buffer = ByteBuffer . allocate(1024);

result.read(readBuffer,null,newCompletionHandler & lt整数,无效>{

//成功时调用

@覆盖

publicvoidcompleted(整数结果,无效附件){

system . out . println(NewString(ReadBuffer . array));

{}

//失败时调用

@覆盖

publicvoidfailed(Throwable exc,Void attachment){

System.out.println("读取失败");

{}

});

}捕获(例外e) {

e.printStackTrace

{}

{}

//万一失败

@覆盖

publicvoidfailed(Throwable exc,Void attachment){

exc.printStackTrace

{}

});

//阻止线程完成执行

时间单位。seconds . sleep(1000 L);

客户

客户端向服务器发送消息,并接受服务器发送的消息

异步SocketChannel客户端=异步socketchannel . open;

未来<。Void>。future = client . connect(Newinetsocketaddress(" 127 . 0 . 0 . 1 ",9001));

//Block,获取连接

未来。get

字节缓冲区=字节缓冲区.分配(1024);

//读取数据

client.read(buffer,null,newCompletionHandler & lt整数,无效>{

//成功时调用

@覆盖

publicvoidcompleted(整数结果,无效附件){

系统。out . println(NewString(buffer . array));

{}

//失败时调用

@覆盖

publicvoidfailed(Throwable exc,Void attachment) {

系统。out.println("客户端无法接收消息");

{}

});

字节缓冲区写缓冲区=字节缓冲区.分配(1024);

WriteBuffer.put("我是客户端,向服务器问好"。getBytes);

writeBuffer.flip

//阻塞方法

未来<。整数>write = client . write(WriteBuffer);

整数r =写。get

if(r >;0){

系统。out.println("客户端消息发送成功");

{}

//睡眠线程

时间单位。seconds . sleep(1000 L);

摘要

IO型号对比:

伪异步输入输出指的是使用线程池处理请求的生物模型

参考:

Netty权威指南第二版

Http://ifeve.com/java-nio-all/并发编程网络

Https://tech.meituan.com/2016/11/04/nio.html美团技术团队

陈明宇

Https://chenmingyu.top/nio/#流

欢迎分享转载 →java输出 一文看懂java io系统

Copyright © 2002-2020 鲁旭娱乐网 版权所有 备案号:粤ICP备14025430号-1

收藏本站 - 网站地图 - 关于我们 - 网站公告 - 广告服务