典型定义

Java IO方式有很多种,基于不同的 IO 抽象模型和交互方式,可以进行简单区分。

首先,传统的 java.io 包,它基于流模型实现,提供了我们最熟知的一些 IO 功能,比如 File 抽象、输入输出流等。交互方式是同步、阻塞的方式,也就是说,在读取输入流或者写入输出流时,在读、写动作完成之前,线程会一直阻塞在那里,它们之间的调用是可靠的线性顺序

java.io 包的好处是代码比较简单、直观,缺点则是 IO 效率和扩展性存在局限性,容易成为应用性能的瓶颈。

很多时候,人们也把 java.net 下面提供的部分网络 API,比如 Socket、ServerSocket、HttpURLConnection 也归类到同步阻塞 IO 类库,因为网络通信同样是 IO 行为。

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

第三,在 Java 7 中,NIO 有了进一步的改进,也就是 NIO 2,引入了异步非阻塞 IO 方式,也有很多人叫它 AIO(Asynchronous IO)。异步 IO 操作基于事件和回调机制,可以简单理解为,应用操作直接返回,而不会阻塞在那里,当后台处理完成,操作系统会通知相应线程进行后续工作。

知识扩展

  • 区分同步或异步(synchronous/asynchronous)。简单来说,同步是一种可靠的有序运行机制,当我们进行同步操作时,后续的任务是等待当前调用返回,才会进行下一步;而异步则相反,其他任务不需等待当前调用返回,通常依靠事件、回调等机制来实现任务间次序关系。

  • 区分阻塞与非阻塞(blocking/non-blocking)。在进行阻塞操作时,当前线程会处于阻塞状态,无法从事其他任务,只有当条件就绪才能继续。而非阻塞是不管IO操作是否结束,直接返回,相应操作还在后台继续处理。

1、java.io总结

  • IO不仅仅是对文件的操作,网络编程中,比如Socket通信,都是典型的IO操作目标。
  • 输入流、输出流(InputStream/OutputStream)是用于读取或写入字节的,例如操作图片文件。
  • Reader/Writer是用于操作字符,增加了字符编解码等功能,适用于类似从文件中读取或者写入文本信息。本质上计算机操作的都是字节,不管是网络通信还是文件读取,Reader/Writer相当于构建了应用逻辑和原始数据之间的桥梁。
  • BufferedOutputStream等带缓冲区的实现,可以避免频繁的磁盘读写,提高效率。这种设计利用了缓冲区,将批量数据进行一次操作,但使用中要记得flush。
  • 很多IO工具类都实现了Closeable接口,因为需要进行资源的释放。

2、Java NIO概述

NIO的主要组成部分:

  • Buffer,高效的数据容器,除了布尔类型,所有原始数据类型都有相应的Buffer实现。
  • Channel,类似在Linux之类操作系统上看到的文件描述符,是NIO中被用来支持批量式IO操作的一种抽象。File或Socket,通常被认为是比较高层次的抽象,而Channel则是更加操作系统底层的一种抽象,这也使得NIO得以充分利用现代操作系统底层机制,获得特定场景的性能优化。不同层次的抽象是相互关联的,可以通过Socket获取Channel,反之亦然。
  • Selector,是NIO实现多路复用的基础,可以检测到注册在Selector上的多个Channel中,是否有Channel处于就绪状态,进而实现了单线程对多Channel的高效管理。

3、NIO能解决什么问题?

使用java.io和java.net中的同步阻塞API可以简单实现一个服务器应用,能够同时服务多个客户端请求。

public class DemoServer extends Thread {private ServerSocket serverSocket;public int getPort() {return  serverSocket.getLocalPort();}public void run() {try {serverSocket = new ServerSocket(0); // 端口 0 表示自动绑定一个空闲端口while (true) {Socket socket = serverSocket.accept(); // 阻塞等待客户端连接RequestHandler requestHandler = new RequestHandler(socket);requestHandler.start();}} catch (IOException e) {e.printStackTrace();} finally {if (serverSocket != null) {try {serverSocket.close();} catch (IOException e) {e.printStackTrace();};}}}public static void main(String[] args) throws IOException {DemoServer server = new DemoServer();server.start();try (Socket client = new Socket(InetAddress.getLocalHost(), server.getPort())) {BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(client.getInputStream()));bufferedReader.lines().forEach(s -> System.out.println(s));}}}
// 简化实现,不做读取,直接发送字符串
class RequestHandler extends Thread {private Socket socket;RequestHandler(Socket socket) {this.socket = socket;}@Overridepublic void run() {try (PrintWriter out = new PrintWriter(socket.getOutputStream()) {out.println("Hello world!");out.flush();} catch (Exception e) {e.printStackTrace();}}}

每启动或者销毁一个线程是有明显开销,每个线程都有独立的线程栈等结构,需要占用非常明显的内存,所以每一个client启动一个线程似乎都有些浪费。引入线程池机制来避免浪费。

serverSocket = new ServerSocket(0);
executor = Executors.newFixedThreadPool(8);while (true) {Socket socket = serverSocket.accept();RequestHandler requestHandler = new RequestHandler(socket);executor.execute(requestHandler);
}

这种工作方式参考下图

弊端:线程上下文切换开销在高并发时变得很明显,这是同步阻塞的低扩展性劣势。

4、NIO引入的多路复用机制,提供了另一种思路。

public class NIOServer extends Thread {public void run() {try (Selector selector = Selector.open();ServerSocketChannel serverSocket = ServerSocketChannel.open();) {// 创建 Selector 和 ChannelserverSocket.bind(new InetSocketAddress(InetAddress.getLocalHost(), 8888));serverSocket.configureBlocking(false);//配置非阻塞模式// 注册到 Selector,并说明关注点serverSocket.register(selector, SelectionKey.OP_ACCEPT); //关注的是新的连接请求while (true) {selector.select();// 阻塞等待就绪的 Channel,当有Channel发生接入请求,就会被唤醒Set<SelectionKey> selectedKeys = selector.selectedKeys();Iterator<SelectionKey> iter = selectedKeys.iterator();while (iter.hasNext()) {SelectionKey key = iter.next();// 生产系统中一般会额外进行就绪状态检查sayHelloWorld((ServerSocketChannel) key.channel());iter.remove();}}} catch (IOException e) {e.printStackTrace();}}private void sayHelloWorld(ServerSocketChannel server) throws IOException {try (SocketChannel client = server.accept();) {          client.write(Charset.defaultCharset().encode("Hello world!"));}}// 省略了与前面类似的 main
}

IO是同步阻塞模式,所以需要多线程以实现多任务处理。而NIO则是利用了单线程轮询事件的机制,通过高效地定位就绪的Channel来决定做什么,仅仅select阶段是阻塞的,可以有效避免大量客户端连接时,频繁线程切换带来的问题。

AIO,增添了一种额外的异步IO模式,利用事件和回调,处理Accept、Read等操作。

AsynchronousServerSocketChannel serverSock = AsynchronousServerSocketChannel.open().bind(sockAddr);
serverSock.accept(serverSock, new CompletionHandler<>() { // 为异步操作指定 CompletionHandler 回调函数@Overridepublic void completed(AsynchronousSocketChannel sockChannel, AsynchronousServerSocketChannel serverSock) {serverSock.accept(serverSock, this);// 另外一个 write(sock,CompletionHandler{})sayHelloWorld(sockChannel, Charset.defaultCharset().encode("Hello World!"));}// 省略其他路径处理方法...
});
  • 基本抽象很相似,AsynchronousServerSocketChannel 对应于上面例子中的 ServerSocketChannel;AsynchronousSocketChannel 则对应 SocketChannel。
  • 业务逻辑的关键在于,通过指定 CompletionHandler 回调接口,在 accept、read、write 等关键节点,通过事件机制调用,这是非常不同的一种编程思路。

NIO的局限性

  • 由于NIO实际上是同步非阻塞IO,是一个线程在同步的进行事件处理,当一组事channel处理完毕以后,去检查有没有又可以处理的channel,这也就是同步+非阻塞。
  • 同步,指每个准备好的channel处理是依次进行的,非阻塞,是指线程不会傻傻的等待读。只有当channel准备好后,才会进行。
  • 那么就会有这样一个问题,当每个channel所进行的都是耗时操作时,由于是同步操作,就会积压很多channel任务,从而完成影响。
  • 那么就需要对nio进行类似负载均衡的操作,如用线程池去进行管理读写,将channel分给其他的线程去执行,这样既充分利用了每一个线程,又不至于都堆积在一个线程中,等待执行。

Java核心技术11 | Java IO相关推荐

  1. Java核心技术(Java白皮书)卷Ⅰ 第一章 Java程序设计概述

    第1章 Java程序设计概述 1.1 Java程序设计平台  具有令人赏心悦目的语法和易于理解的语言,与其他许多优秀语言一样,Java满足这些要求.  可移植性  垃圾收集  提供大型的库   如果想 ...

  2. java web.xml 监听器_【JAVA 核心技术】java web 中的监听器

    为什么要有监听器? 监听器是用来处理一系列事件的java类,可被配置在java web项目中. 既然如此,为什么不用代码直接调用监听器? 这可能是因为J2EE规范规定的项目工程标准,用web.xml指 ...

  3. Java核心技术:Java数据类型以及控制语句

    一.Java基本数据类型 Java原生有8种基本数据类型:整型(int short long byte)浮点型(double float)字符型(char)布尔型(boolean).注意:在Java中 ...

  4. 速读原著-Java核心技术(安装Java开发工具包)

    第 2 章 Java 程序设计环境 本章主要介绍如何安装 Java 开发工具包( JDK ) 以及如何编译和运行不同类型的程序:控制台程序. 图形化应用程序以及 applet.运行 JDK 工具的方法 ...

  5. Java 核心技术专题

      什么是 Java 核心技术? Java 语言与编程实践 Java 虚拟机技术 Java SE 平台技术 Java 高级编程技术 IBM Java 运行时与 SDK Java 理论与实践 本专题帮助 ...

  6. Java粗浅认识-java简介

    简介 java 是1991年是为了交互式电视设计,sun公司在1996年发布java1.0版本,java的特点是"一次编译,到处执行",借助于 在浏览器上可执行java apple ...

  7. Java 版本变更列表 (Java SE 8 ~ Java SE 18 )

    java 版本变更列表 Java SE 8 Java SE 9 Java SE 10 Java SE 11 Java SE 12 Java SE 13 Java SE 14 Java SE 15 Ja ...

  8. 《Java 核心技术 卷1》 笔记 第11章 异常、日志、断言和调试

    出现不可预计的问题时,需要进行如下处理: 报告错误 保存操作结果 允许用户退出 本章解决的问题: 验证程序正确性 记录程序错误 调试技巧 11.1 处理异常 程序出现错误时应该: 返回安全状态,能让用 ...

  9. Java核心技术 卷1-总结-11

    Java核心技术 卷1-总结-11 Java 集合框架 将集合的接口与实现分离 Collection接口 迭代器 泛型实用方法 集合框架中的接口 Java 集合框架 将集合的接口与实现分离 Java集 ...

  10. java实践11之网络IO BIO和NIO(上)

    文章目录 java实践11之网络IO BIO和NIO(上) 1 BIO: BIO的使用demo 优化版使用线程池实现异步处理 为何已经异步处理了还说BIO是阻塞的? BIO带来的问题 2 NIO ja ...

最新文章

  1. 超详细的Python matplotlib 绘制柱状图
  2. python批量读取grib_windows python读取grib2数据
  3. BUUCTF web(一)
  4. js检测开发者工具Devtools是否打开防调试
  5. 推荐一款ui架构--frozenui
  6. 用java实现学生管理系统
  7. PCL visualizer
  8. 班级管理servlet项目开发详细讲解,其中涉及js、jq、ajax、等多项技术合计,本项目来源自网络,如有雷同,请私聊博主
  9. mid制作乐谱_乐谱编辑(在线简谱制作软件)
  10. 文件批量改名特工 v1.0 怎么用
  11. C语言编程之学习putpixel画点
  12. 基于51单片机智能车流量检测车量统计系统设计
  13. 数据建模讲解和案例分析
  14. 怎么快速制作web原型_快速原型制作指南
  15. PCS2021:针对游戏内容的视频编码工具分析和数据集
  16. 好用的oier命令行工具(自创的)
  17. 麒麟系统查看微信聊天记录位置、微信收到的文件位置
  18. php如何锁定表,PHPExcel冻结(锁定)表头的简单实现方法
  19. FFMPEG将视频切片成ts文件并对ts文件进行ASE加密,并合并成M3U8操作方法
  20. freetype用法

热门文章

  1. 《项目经验》静态页面图片找不到
  2. oracle视频教程11g入门运维DBA性能优化OCP培训SQL数据库在线课程
  3. java编译异常有哪些_java编译时异常有哪些?java常见异常有哪些?
  4. java swing开发打飞机的小游戏源代码下载
  5. java安装教程_JAVA教程_Windows环境Java安装部署教程
  6. HBase海量数据高效入仓解决方案
  7. Java的测试方法有哪些?自动化测试让Java测试变得更简单!
  8. 心电图分析软件_窦性P波形态突变或多变的心电图分析
  9. ftp多线程上传、下载以及断点续传
  10. CentOS7 安装jdk8教程