和catch的区别_BIO、NIO、AIO 的区别是什么?

BIO、NIO、AIO 的区别是什么?
同/异步、阻/非阻塞的区别是什么?
文件读写最优雅的实现方式是什么?
NIO 如何实现多路复用功能?

带着以上这几个问题,跟着芒果一起进入IO的世界吧。

在开始之前,我们先来思考一个问题:我们经常所说的“IO”的全称到底是什么?

可能很多人看到这个问题和我一样一脸懵逼,IO的全称其实是:Input/Output的缩写。

一、IO 介绍

我们通常所说的 BIO 是相对于 NIO 来说的,BIO 也就是 Java 开始之初推出的 IO 操作模块,BIO 是 BlockingIO 的缩写,顾名思义就是阻塞 IO 的意思。

1.1 BIO、NIO、AIO的区别

  1. BIO 就是传统的 java.io 包,它是基于流模型实现的,交互的方式是同步、阻塞方式,也就是说在读入输入流或者输出流时,在读写动作完成之前,线程会一直阻塞在那里,它们之间的调用时可靠的线性顺序。它的有点就是代码比较简单、直观;缺点就是 IO 的效率和扩展性很低,容易成为应用性能瓶颈。

  2. NIO 是 Java 1.4 引入的 java.nio 包,提供了 Channel、Selector、Buffer 等新的抽象,可以构建多路复用的、同步非阻塞 IO 程序,同时提供了更接近操作系统底层高性能的数据操作方式。

  3. AIO 是 Java 1.7 之后引入的包,是 NIO 的升级版本,提供了异步非堵塞的 IO 操作方式,所以人们叫它 AIO(Asynchronous IO),异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。

1.2 全面认识 IO

传统的 IO 大致可以分为4种类型:

  • InputStream、OutputStream 基于字节操作的 IO

  • Writer、Reader 基于字符操作的 IO

  • File 基于磁盘操作的 IO

  • Socket 基于网络操作的 IO

java.net 下提供的 Scoket 很多时候人们也把它归为 同步阻塞 IO ,因为网络通讯同样是 IO 行为。

java.io 下的类和接口很多,但大体都是 InputStream、OutputStream、Writer、Reader 的子集,所有掌握这4个类和File的使用,是用好 IO 的关键。

1.3 IO 使用

接下来看 InputStream、OutputStream、Writer、Reader 的继承关系图和使用示例。

1.3.1 InputStream 使用

继承关系图和类方法,如下图:

dfbba48157669d05d237b5fc707367fc.png

InputStream 使用示例:

InputStream inputStream = new FileInputStream("D:\\log.txt");byte[] bytes = new byte[inputStream.available()];inputStream.read(bytes);String str = new String(bytes, "utf-8");System.out.println(str);inputStream.close();

1.3.2 OutputStream 使用

继承关系图和类方法,如下图:

3076e49e21a72d7337cb1608f6187626.png

OutputStream 使用示例:

OutputStream outputStream = new FileOutputStream("D:\\log.txt",true);// 参数二,表示是否追加,true=追加outputStream.write("你好,老王".getBytes("utf-8"));outputStream.close();

1.3.3 Writer 使用

Writer 继承关系图和类方法,如下图:

26f2a6344f609636262a20e38be6c338.png

Writer 使用示例:

Writer writer = new FileWriter("D:\\log.txt",true); // 参数二,是否追加文件,true=追加writer.append("老王,你好");writer.close();

1.3.4 Reader 使用

Reader 继承关系图和类方法,如下图:

71dd023f5b2461c0b047f48ae799ec2f.png

Reader 使用示例:

Reader reader = new FileReader(filePath);BufferedReader bufferedReader = new BufferedReader(reader);StringBuffer bf = new StringBuffer();String str;while ((str = bufferedReader.readLine()) != null) {    bf.append(str + "\n");}bufferedReader.close();reader.close();System.out.println(bf.toString());

二、同步、异步、阻塞、非阻塞

上面说了很多关于同步、异步、阻塞和非阻塞的概念,接下来就具体聊一下它们4个的含义,以及组合之后形成的性能分析。

2.1 同步与异步

同步就是一个任务的完成需要依赖另外一个任务时,只有等待被依赖的任务完成后,依赖的任务才能算完成,这是一种可靠的任务序列。要么成功都成功,失败都失败,两个任务的状态可以保持一致。而异步是不需要等待被依赖的任务完成,只是通知被依赖的任务要完成什么工作,依赖的任务也立即执行,只要自己完成了整个任务就算完成了。至于被依赖的任务最终是否真正完成,依赖它的任务无法确定,所以它是不可靠的任务序列。我们可以用打电话和发短信来很好的比喻同步与异步操作。

2.2 阻塞与非阻塞

阻塞与非阻塞主要是从 CPU 的消耗上来说的,阻塞就是 CPU 停下来等待一个慢的操作完成 CPU 才接着完成其它的事。非阻塞就是在这个慢的操作在执行时 CPU 去干其它别的事,等这个慢的操作完成时,CPU 再接着完成后续的操作。虽然表面上看非阻塞的方式可以明显的提高 CPU 的利用率,但是也带了另外一种后果就是系统的线程切换增加。增加的 CPU 使用时间能不能补偿系统的切换成本需要好好评估。

2.3 同/异、阻/非堵塞 组合

同/异、阻/非堵塞的组合,有四种类型,如下表:

6b8ee429cb9ef3e6e10847bf42877b6c.png
bf9c1b4a634239203103b13c9f840cc3.png

# 三、优雅的文件读写

Java 7 之前文件的读取是这样的:

// 添加文件FileWriter fileWriter = new FileWriter(filePath, true);fileWriter.write(Content);fileWriter.close();// 读取文件FileReader fileReader = new FileReader(filePath);BufferedReader bufferedReader = new BufferedReader(fileReader);StringBuffer bf = new StringBuffer();String str;while ((str = bufferedReader.readLine()) != null) {    bf.append(str + "\n");}bufferedReader.close();fileReader.close();System.out.println(bf.toString());Java 7 引入了Files(java.nio包下)的,大大简化了文件的读写,如下:// 写入文件(追加方式:StandardOpenOption.APPEND)Files.write(Paths.get(filePath), Content.getBytes(StandardCharsets.UTF_8), StandardOpenOption.APPEND);// 读取文件byte[] data = Files.readAllBytes(Paths.get(filePath));System.out.println(new String(data, StandardCharsets.UTF_8));

读写文件都是一行代码搞定,没错这就是最优雅的文件操作。

Files 下还有很多有用的方法,比如创建多层文件夹,写法上也简单了:

// 创建多(单)层目录(如果不存在创建,存在不会报错)
new File("D://a//b").mkdirs();

四、Socket 和 NIO 的多路复用

本节带你实现最基础的 Socket 的同时,同时会实现 NIO 多路复用,还有 AIO 中 Socket 的实现。

4.1 传统的 Socket 实现

接下来我们将会实现一个简单的 Socket,服务器端只发给客户端信息,再由客户端打印出来的例子,代码如下:

int port = 4343; //端口号
// Socket 服务器端(简单的发送信息)
Thread sThread = new Thread(new Runnable() {    @Override    public void run() {        try {            ServerSocket serverSocket = new ServerSocket(port);            while (true) {                // 等待连接                Socket socket = serverSocket.accept();                Thread sHandlerThread = new Thread(new Runnable() {                    @Override                    public void run() {                        try (PrintWriter printWriter = new PrintWriter(socket.getOutputStream())) {                            printWriter.println("hello world!");                            printWriter.flush();                        } catch (IOException e) {                            e.printStackTrace();                        }                    }                });                sHandlerThread.start();            }        } catch (IOException e) {            e.printStackTrace();        }    }});sThread.start();// Socket 客户端(接收信息并打印)try (Socket cSocket = new Socket(InetAddress.getLocalHost(), port)) {    BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(cSocket.getInputStream()));    bufferedReader.lines().forEach(s -> System.out.println("客户端:" + s));} catch (UnknownHostException e) {    e.printStackTrace();} catch (IOException e) {    e.printStackTrace();}

  • 调用 accept 方法,阻塞等待客户端连接;

  • 利用 Socket 模拟了一个简单的客户端,只进行连接、读取和打印;

在 Java 中,线程的实现是比较重量级的,所以线程的启动或者销毁是很消耗服务器的资源的,即使使用线程池来实现,使用上述传统的 Socket 方式,当连接数极具上升也会带来性能瓶颈,原因是线程的上线文切换开销会在高并发的时候体现的很明显,并且以上操作方式还是同步阻塞式的编程,性能问题在高并发的时候就会体现的尤为明显。

以上的流程,如下图:

4f114fe9cb023a46ed53e5b47f790438.png

4.2 NIO 多路复用

介于以上高并发的问题,NIO 的多路复用功能就显得意义非凡了。

NIO 是利用了单线程轮询事件的机制,通过高效地定位就绪的 Channel,来决定做什么,仅仅 select 阶段是阻塞的,可以有效避免大量客户端连接时,频繁线程切换带来的问题,应用的扩展能力有了非常大的提高。

// NIO 多路复用
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(4, 4,        60L, TimeUnit.SECONDS, new LinkedBlockingQueue());threadPool.execute(new Runnable() {    @Override    public void run() {        try (Selector selector = Selector.open();             ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();) {            serverSocketChannel.bind(new InetSocketAddress(InetAddress.getLocalHost(), port));            serverSocketChannel.configureBlocking(false);            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);            while (true) {                selector.select(); // 阻塞等待就绪的Channel                Set selectionKeys = selector.selectedKeys();                Iterator iterator = selectionKeys.iterator();                while (iterator.hasNext()) {                    SelectionKey key = iterator.next();                    try (SocketChannel channel = ((ServerSocketChannel) key.channel()).accept()) {                        channel.write(Charset.defaultCharset().encode("你好,世界"));                    }                    iterator.remove();                }            }        } catch (IOException e) {            e.printStackTrace();        }    }});// Socket 客户端(接收信息并打印)try (Socket cSocket = new Socket(InetAddress.getLocalHost(), port)) {    BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(cSocket.getInputStream()));    bufferedReader.lines().forEach(s -> System.out.println("NIO 客户端:" + s));} catch (IOException e) {    e.printStackTrace();}

  • 首先,通过 Selector.open() 创建一个 Selector,作为类似调度员的角色;

  • 然后,创建一个 ServerSocketChannel,并且向 Selector 注册,通过指定 SelectionKey.OP_ACCEPT,告诉调度员,它关注的是新的连接请求;

  • 为什么我们要明确配置非阻塞模式呢?这是因为阻塞模式下,注册操作是不允许的,会抛出 IllegalBlockingModeException 异常;

  • Selector 阻塞在 select 操作,当有 Channel 发生接入请求,就会被唤醒;

下面的图,可以有效的说明 NIO 复用的流程:

f23cc3e95d01a70811cc92c41716c42e.png

就这样 NIO 的多路复用就大大提升了服务器端响应高并发的能力。

4.3 AIO 版 Socket 实现

Java 1.7 提供了 AIO 实现的 Socket 是这样的,如下代码:

// AIO线程复用版
Thread sThread = new Thread(new Runnable() {    @Override    public void run() {        AsynchronousChannelGroup group = null;        try {            group = AsynchronousChannelGroup.withThreadPool(Executors.newFixedThreadPool(4));            AsynchronousServerSocketChannel server = AsynchronousServerSocketChannel.open(group).bind(new InetSocketAddress(InetAddress.getLocalHost(), port));            server.accept(null, new CompletionHandler() {                @Override                public void completed(AsynchronousSocketChannel result, AsynchronousServerSocketChannel attachment) {                    server.accept(null, this); // 接收下一个请求                    try {                        Future f = result.write(Charset.defaultCharset().encode("你好,世界"));                        f.get();                        System.out.println("服务端发送时间:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));                        result.close();                    } catch (InterruptedException | ExecutionException | IOException e) {                        e.printStackTrace();                    }                }                @Override                public void failed(Throwable exc, AsynchronousServerSocketChannel attachment) {                }            });            group.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS);        } catch (IOException | InterruptedException e) {            e.printStackTrace();        }    }});sThread.start();


// Socket 客户端
AsynchronousSocketChannel client = AsynchronousSocketChannel.open();Future future = client.connect(new InetSocketAddress(InetAddress.getLocalHost(), port));future.get();ByteBuffer buffer = ByteBuffer.allocate(100);client.read(buffer, null, new CompletionHandler() {    @Override    public void completed(Integer result, Void attachment) {        System.out.println("客户端打印:" + new String(buffer.array()));    }    @Override    public void failed(Throwable exc, Void attachment) {        exc.printStackTrace();        try {            client.close();        } catch (IOException e) {            e.printStackTrace();        }    }});

Thread.sleep(10 * 1000);

五、总结

以上基本就是 IO 从 1.0 到目前版本(本文的版本)JDK 8 的核心使用操作了,可以看出来 IO 作为比较常用的基础功能,发展变化的改动也很大,而且使用起来也越来越简单了,IO 的操作也是比较好理解的,一个输入一个输出,掌握好了输入输出也就掌握好了 IO,Socket 作为网络交互的集成功能,显然 NIO 的多路复用,给 Socket 带来了更多的活力和选择,用户可以根据自己的实际场景选择相应的代码策略。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/502398.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

302状态码_HTTP协议详解(基础概念 方法 状态码 首部 连接 Cookie 新特性 安全)

一 、基础概念URIURI 包含 URL 和 URN。请求和响应报文1. 请求报文2. 响应报文二、HTTP 方法客户端发送的 请求报文 第一行为请求行,包含了方法字段。GET获取资源当前网络请求中,绝大部分使用的是 GET 方法。HEAD获取报文首部和 GET 方法类似&#xff0c…

flask get 参数_用它 5 分钟以后,我放弃用了四年的 Flask

“ 阅读本文大概需要 3 分钟。 ”有一个非常简单的需求:编写一个 HTTP 接口,使用 POST 方式发送一个 JSON 字符串,接口里面读取发送上来的参数,对其中某个参数进行处理,并返回。如果我们使用 Flask 来开发这个接口&…

android sse 人脸识别,基于Android Camera2之openCamera 流程

简介frameworks\base\core\java\android\hardware\camera2Camera2在Android 5.0上首次出现,主要重新定义了一套Camera 的使用接口API,设计思想出现了变化,具体的可自行搜索,此处主要介绍下Camera2的常见类以及使用流程。CameraCap…

vscode编辑python_VSCode+Python开发环境

准备开始转向用VSCode做开发,所以把一些常用的开发环境转移到VSCode上。 这次搭建的是Python3的开发环境。 其他相关博文: 一、测试环境 Windows 10 VSCode v1.11.1 Python v3.6.1 二、安装Visual Studio Code 三、安装Python这一步,记得…

html5 css3浏览器,五大主流浏览器CSS3和HTML5兼容性大比拼

五大主流浏览器CSS3和HTML5兼容性大比拼出处:快科技 2011-05-26 16:15:42 编辑:萧萧[爆料] 收藏文章各大主流浏览器对CSS3和HTML5的支持越来越完善,曾经让多少前端开发人员心碎的IE系也开始拥抱标准。就在前几天,W3C的HTML5社…

一个控制器怎么转发到另外一个控制器_楼宇自动化系统(BAS),DDC,一个最核心的控制器...

楼宇自动化系统(BAS),一个熟悉又陌生的系统楼宇自控系统(BAS系统)设有一个中央监控中心,系统配置一个或多个网络控制器,由多条总线或计算机网络将各种功能的控制器与中央工作站相连,完成对空调、给排水、通风、电梯等子系统的监控…

html封装windows,windows 系统封装,打造一份属于自己的系统!

在电脑的使用过程中,由于我们每个人的使用习惯和使用方式不同,所以我们都会对Windows系统进行自己的设置,尤其是一些搞数码软件的,如果不小心系统坏了,重装系统后,还得一一去进行重新设置,非常麻…

python语言例子_【Python】SimPy的使用示例-Go语言中文社区

使用SimPY进行离散事件仿真 SimPY是一个Python下的第三方库,可以方便的进行离散事件的仿真。仿真速度比较快。下面记录一下我的一点心得,不保证完全正确,供参考。 安装 $ pip install -U simpy pycharm可以再File | Settings | Project: Simu…

vue变量传值_VUE 学习——父组件传值给子组件

在我们编写前端代码时,经常遇到的一种场景,子组件需要使用父组件的值,这种情况下,我们可以使用props帮助我们进行父子组件间的通信。这里我们先模拟一个场景,展示如何使用。场景:在父组件修改值&#xff0c…

matlab 最小二乘法拟合_Scripy实现最小二乘法与股票K线回归

python的Scripy提供了丰富的数学工具,python的科学计算包scipy的里面提供了一个函数,可以求出任意的想要拟合的函数的参数。那就是scipy.optimize包里面的leastsq函数。函数原型是:leastsq(func, x0, args(), DfunNone, full_output0, col_de…

html 调高德地图 导航,在H5页面内通过地址调起高德地图实现导航

项目中用到的一个功能是要通过点击地址来实现打开地图app实现地址导航。如下图:实现思路就是在H5页面内通过点击marker图标然后进行当前位置与页面上地址的路程规划与导航。由于项目中用到的是高德地图,所以这里用到的是调起高德地图APP来实现该功能。首…

.jar文件如何打开_ofd发票文件如何打开

有时候大家查找文件的时候会不会觉得很心烦,因为经常能碰到OFD格式的文件,但是我们却不知道怎么打开,造成了很多困扰。我以前也常常遇到这样的情况,但是最近我发现了一个好办法,迫不及待地想分享给大家了,可…

印刷体是什么意思_家长晒出4年级小学霸课前笔记,字迹堪比“印刷体”,老师都羡慕...

目前,很多小学生都在家里上网课,为了达到最佳学习效果,要提前做好预习工作,尤其是语文这一学科,更需要预习。这不就有一位4年级学霸的家长晒出了孩子日常课前预习笔记。他不仅对文章进行了合理的布置和预习&#xff0c…

不使用自带函数求区域的周长_Excel表格中最强大求和函数______DSUM函数

在Excel表格中说起求和函数,朋友们首先会想到的是sum、sumif、和sumifs函数。这篇文章为朋友们分享最强大求和函数,数据库函数之DSUM函数。这个函数不仅能完成各种要求的求和,还可以用于查找。一.DSUM函数说明:1.语法:…

python 用if判断一个数是不是整数_Python基础教程07-函数和模块的使用

在讲解本章节的内容之前,我们先来研究一道数学题,请说出下面的方程有多少组正整数解。x1x2x3x48事实上,上面的问题等同于将8个苹果分成四组每组至少一个苹果有多少种方案。想到这一点问题的答案就呼之欲出了。可以用Python的程序来计算出这个…

前端跨域请求get_(单点登录)跨域SSO看这篇文章就够了:前端篇

前言前俩篇文章,我们从概念,聊到了服务器中设计的内容。不知道大家是否觉得通俗易懂呢?接下来的内容则有些偏向前端部分。正文三、Cookie传递3.1、通过URL参数实现跨域信息传递我们要在A域实现写入token到B域,需要在A域设计一个se…

win10 如何锁定计算机,Win10 1909 专业版怎么锁定计算机屏幕

Win10 1909 专业版怎么锁定计算机屏幕?如果我们需要暂时离开计算机,但不想关机或者注销当前登录,那么为了防止未经授权的使用,我们可以将计算机锁定。在本文中,win10之家小编给大家分享如何自动锁定Windows 10计算机。Win10 1909…

qfile指定从多少行开始_大牛进化路上之Linux基础命令,看看你了解多少?

玩转Linux系统还是要从基础命令开始,基础命令是你大牛发展之路的第一步,扎实的基本命令操作功底才能在工作中游刃有余,下面我们就来看看吧。路径切换说明Linux中分绝对路径和相对路径,绝对路径一定是从/开始写的,还可能…

如何开机进入grub界面_如何进入http://192.168.1.1的设置界面 ?

登入192.168.1.1管理界面需要确保前期的硬件连接和已获知路由器的账号密码。接下来详细介绍:1、首先做好准备,检查一下是否已经连接好无线路由器和网线、调制解调器,要确保网络畅通、所有指示灯都正常亮着;2、然后打开手机设置&am…

手机html5雪花飘落,如何使用HTML5canvas实现雪花飘落

这篇文章主要为大家详细介绍了HTML5 canvas实现雪花飘落特效,效果实现引人入胜,很逼真的动画效果,感兴趣的小伙伴们可以参考一下看到网上很多展示html5雪花飞动的效果,确实非常引人入胜,我相信大家也跟我一样看着心动的…