java中的IO和NIO的区别原理

2023-10-31

Java NIO和IO的主要区别

IO NIO
面向Stream 面向Buffer
阻塞IO 非阻塞IO
  Selectors


面向Stream和面向Buffer

Java NIO和IO之间最大的区别是IO是面向流(Stream)的,NIO是面向块(buffer)的,所以,这意味着什么?

面向流意味着从流中一次可以读取一个或多个字节,拿到读取的这些做什么你说了算,这里没有任何缓存(这里指的是使用流没有任何缓存,接收或者发送的数据是缓存到操作系统中的,流就像一根水管从操作系统的缓存中读取数据)而且只能顺序从流中读取数据,如果需要跳过一些字节或者再读取已经读过的字节,你必须将从流中读取的数据先缓存起来

面向块的处理方式有些不同,数据是先被 读/写到buffer中的,根据需要你可以控制读取什么位置的数据。这在处理的过程中给用户多了一些灵活性然而,你需要额外做的工作是检查你需要的数据是否已经全部到了buffer中,你还需要保证当有更多的数据进入buffer中时,buffer中未处理的数据不会被覆盖

阻塞IO和非阻塞IO

所有的Java IO流都是阻塞的,这意味着,当一条线程执行read()或者write()方法时,这条线程会一直阻塞知道读取到了一些数据或者要写出去的数据已经全部写出,在这期间这条线程不能做任何其他的事情

java NIO的非阻塞模式(Java NIO有阻塞模式和非阻塞模式,阻塞模式的NIO除了使用Buffer存储数据外和IO基本没有区别)允许一条线程从channel中读取数据,通过返回值来判断buffer中是否有数据,如果没有数据,NIO不会阻塞,因为不阻塞这条线程就可以去做其他的事情,过一段时间再回来判断一下有没有数据

NIO的写也是一样的,一条线程将buffer中的数据写入channel,它不会等待数据全部写完才会返回,而是调用完write()方法就会继续向下执行

具体的原理实现:

整个IO过程的流程如下:

1)程序员写代码创建一个缓冲区(这个缓冲区是用户缓冲区):哈哈。然后在一个while循环里面调用read()方法读数据(触发"syscall read"系统调用)

byte[] b = new byte[1024];

while((read = inputStream.read(b))>=0) {
        total = total + read;
            // other code....
        }

2)当执行到read()方法时,其实底层是发生了很多操作的:

①内核给磁盘控制器发命令说:我要读磁盘上的某某块磁盘块上的数据。--kernel issuing a command to the disk controller hardware to fetch the data from disk.

②在DMA的控制下,把磁盘上的数据读入到内核缓冲区。--The disk controller writes the data directly into a kernel memory buffer by DMA

③内核把数据从内核缓冲区复制到用户缓冲区。--kernel copies the data from the temporary buffer in kernel space

这里的用户缓冲区应该就是我们写的代码中 new 的 byte[] 数组。

从上面的步骤中可以分析出什么?

ⓐ对于操作系统而言,JVM只是一个用户进程,处于用户态空间中。而处于用户态空间的进程是不能直接操作底层的硬件的。而IO操作就需要操作底层的硬件,比如磁盘。因此,IO操作必须得借助内核的帮助才能完成(中断,trap),即:会有用户态到内核态的切换。

ⓑ我们写代码 new byte[] 数组时,一般是都是“随意” 创建一个“任意大小”的数组。比如,new byte[128]、new byte[1024]、new byte[4096]....

但是,对于磁盘块的读取而言,每次访问磁盘读数据时,并不是读任意大小的数据的,而是:每次读一个磁盘块或者若干个磁盘块(这是因为访问磁盘操作代价是很大的,而且我们也相信局部性原理) 因此,就需要有一个“中间缓冲区”--即内核缓冲区。先把数据从磁盘读到内核缓冲区中,然后再把数据从内核缓冲区搬到用户缓冲区。

这也是为什么我们总感觉到第一次read操作很慢,而后续的read操作却很快的原因吧。因为,对于后续的read操作而言,它所需要读的数据很可能已经在内核缓冲区了,此时只需将内核缓冲区中的数据拷贝到用户缓冲区即可,并未涉及到底层的读取磁盘操作,当然就快了。

The kernel tries to cache and/or prefetch data, so the data being requested by the process may already be available in kernel space.
If so, the data requested by the process is copied out.
If the data isn
't available, the process is suspended while the kernel goes about bringing the data into memory.

如果数据不可用,process将会被挂起,并需要等待内核从磁盘上把数据取到内核缓冲区中。

 

那我们可能会说:DMA为什么不直接将磁盘上的数据读入到用户缓冲区呢?一方面是 ⓑ中提到的内核缓冲区作为一个中间缓冲区。用来“适配”用户缓冲区的“任意大小”和每次读磁盘块的固定大小。另一方面则是,用户缓冲区位于用户态空间,而DMA读取数据这种操作涉及到底层的硬件,硬件一般是不能直接访问用户态空间的(OS的原因吧)

综上,由于DMA不能直接访问用户空间(用户缓冲区),普通IO操作需要将数据来回地在 用户缓冲区 和 内核缓冲区移动,这在一定程序上影响了IO的速度。那有没有相应的解决方案呢?

那就是直接内存映射IO,也即JAVA NIO中提到的内存映射文件,或者说 直接内存....总之,它们表达的意思都差不多。示例图如下:

从上图可以看出:内核空间的 buffer 与 用户空间的 buffer 都映射到同一块 物理内存区域。

它的主要特点如下:

①对文件的操作不需要再发read 或者 write 系统调用了---The user process sees the file data asmemory, so there is no need to issue read() or write() system calls.

②当用户进程访问“内存映射文件”地址时,自动产生缺页错误,然后由底层的OS负责将磁盘上的数据送到内存。关于页式存储管理,可参考:内存分配与内存管理的一些理解

As the user process touches the mapped memory space, page faults will be generated automatically to bring in the file data from disk. 
If the user modifies the mapped memory space, the affected page is automatically marked as dirty and will be subsequently
flushed to disk to update the file.

 

这就是是JAVA NIO中提到的内存映射缓冲区(Memory-Mapped-Buffer)它类似于JAVA NIO中的直接缓冲区(Directed Buffer)。MemoryMappedBuffer可以通过java.nio.channels.FileChannel.java(通道)的 map方法创建。

使用内存映射缓冲区来操作文件,它比普通的IO操作读文件要快得多。甚至比使用文件通道(FileChannel)操作文件 还要快。因为,使用内存映射缓冲区操作文件时,没有显示的系统调用(read,write),而且OS还会自动缓存一些文件页(memory page)

 

追加的只是:

缓冲区的出现是为了减少频繁的系统调用,由于系统调用需要保存之前的进程数据和状态等信息,而结束调用之后回来还需要回复之前的信息,为了减少这种耗时耗性能的调用于是出现了缓冲区。在linux系统中,每个进程有自己独立的缓冲区,叫做进程缓冲区,而系统内核也有个缓冲区叫做内核缓冲区

当进程需要指定的磁盘数据的时候,进程先到内核缓冲区去看看数据是否在内核缓冲区,如果没有,内核把读取磁盘中此数据的请求添加到内核的请求队列,然后挂起此进程,等解决了别的进程的问题之后,磁盘中的数据也读取到了内核缓冲区,然后复制数据到进程缓冲区,接着唤醒这个被挂起的进程,进程从自己的进程缓冲区中拿到数据,然后继续工作。

但是一般的操作系统在存储数据的时候也并不是立即存储的,这个是磁盘文件系统设计的问题,有些UNIX操作系统除外,相对数据安全,但是IO不可避免的会降低。操作系统使用read函数把数据从内核缓冲区复制到进程缓冲区,write把数据从进程缓冲区 复制到内核缓冲区中。至于什么时候读取和存储则由内核来决定,内核缓冲区满则存储到磁盘,读取的时候内核缓冲区空则从磁盘读取到内核缓冲区中,当然,在写入的时候如果系统突然关闭则会造成内核缓冲区的数据无法存储到磁盘的事情发生。




本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

java中的IO和NIO的区别原理 的相关文章

  • 网络编程知识预备(5) ——libcurl库安装及其编程访问百度首页(断点续传)

    本文为学习笔记 整合课程内容以及下列文章 其中 libcurl函数库常用字段解读部分 参考博文 原文地址 作者 冬冬他哥哥 目录 Libcurl库简介 Libcurl等三方库的通用编译方法 三方库使用前通读方法 库的配置 编译 安装 调用l
  • protobuf生成prototxt文件

    使用protobuf可以分为以下几步 1 proto文件的定义 在这个文件中定义了最终生成的prototxt格式 举个例子如下所示 syntax proto2 package label proto message DetectLabel
  • scrapy缺点

    Scrapy 是一个功能强大的网络爬虫框架 但也有一些缺点 Scrapy 只能爬取静态网页 无法爬取动态网页 Scrapy 只能爬取一个网站的数据 无法爬取多个网站的数据 Scrapy 需要编写代码 对于那些不会编写代码的人来说可能会有些困
  • LiteOS内存管理

    1 内存管理简介 内存管理模块管理系统的内存资源 它是操作系统的核心模块之一 主要包括内存的初始化 分配以及释放 在系统运行过程中 内存管理模块通过对内存的申请 释放操作 来管理用户和OS对内存的使用 使内存的利用率和使用效率达到最优 同时
  • obs无法连接服务器?PotPlayer获取不到视频

    搭建基于nginx的rtmp直播服务器 https hywlovexyc info blog archives 572 rtmp server listen 1935 监听的端口 chunk size 4000 application yu
  • 删除中间节点

    题目 若链表中的某个节点 既不是链表头节点 也不是链表尾节点 则称其为该链表的 中间节点 假定已知链表的某一个中间节点 请实现一种算法 将该节点从链表中删除 例如 传入节点 c 位于单向链表 a gt b gt c gt d gt e gt
  • sqlserver查看数据表结构

    可以通过两种方式 1 sp help 表名 2 sp columns 表名
  • 我的世界服务器config文档,Essentials/配置文件/config.yml

    ops name color none nickname prefix max nick length 15 change displayname true change playerlist true add prefix suffix
  • Unity--虚拟轴

    1 查看虚拟轴 Edit gt project settings gt Input 2 获取虚拟轴 input GetAxis 用法 GetAxis Mouse X GetAxis Mouse Y GetAxis Mouse ScrollW
  • centos7Linux中的lvm、pv、vg、lv,以及Linux扩容,创建新分区

    概念 本着言简意赅的目的 以下概念会在扩容时用到 需熟知 不然会不理解具体操作在干什么 lvm 逻辑卷管理器 Logical Volume Manager 它使系统管理员可以更方便的为应用与用户分配存储空间 在LVM管理下的存储卷可以按需要
  • QT信号槽原理(一)connect函数

    目录 2个类型和1个签名 Qt ConnectionType QMetaMethod MethodType 方法签名 connect函数源码解析 函数原型 第一步 参数检查 第二步 获取信号和槽的index 第三步 检查信号和槽的参数是否匹
  • Opencv之图像固定阈值二值化处理threshold

    目录 一 图像二值化 二 threshold 三 代码 一 图像二值化 定义 图像的二值化 就是将图像上的像素点的灰度值设置为0或255 也就是将整个图像呈现出明显的只有黑和白的视觉效果 灰度值0 黑 灰度值255 白 一幅图像包括目标物体
  • Cygwin编译JNI的环境配置

    一 什么是NDK NDK提供了一系列的工具 帮助开发者快速开发C 或C 的动态库 并能自动将so和java应用一起打包成apk 这些工具对开发者的帮助是巨大的 NDK 集成了交叉编译器 并提供了相应的mk文件隔离CPU 平台 ABI 等差异
  • MacBook怎么安装使用Windows软件和虚拟机?

    作为一名MacBook用户 你可能会发现有些软件和应用程序只适用于Windows操作系统 但别担心 今天我们将介绍两种方法 让你的MacBook轻松安装Windows软件和运行Windows虚拟机 为你的工作 学习和娱乐任务带来更多可能性
  • Vue 3 使用 vue-echarts 的柱状图 barItem 和 x, y 轴点击事件实现

    需求背景 最近使用 Vue 3 开发柱状图功能 图表已经绘制完成了 但是突然有需求加进来要可以点击柱状图的柱子 barItem 和 x y 轴的坐标 然后根据点击的位置 处理不同区域的点击事件 作为 CV 程序员 第一时间搜索网上的解决方案
  • 抖音养号步骤有哪些?最重要的3步你做到了吗?

    国仁网络资讯 为大家整理一份超详细的 短视频运营手册 回复私信限时免费领取 有人问我 新手养号怎么养 老账号养号要怎么养 其实 严格来说 养号不分新老 不管你是刚注册的抖音号 还是已经用过很久的抖音号 养号都是运营账号必做的事项之一 那么
  • C++文件的读取详解,以及使用eof出现末行数据被读取两次情况

    目录 0 前言 对文件流的读写 文件打开方式 1 写文本文件 2 读文本文档 2 1使用efo函数判断出现数据被读取两次 3 二进制方式写文件 4 二进制读文件 5 按指定格式读写数据stringstream 0 前言 对文件流的读写 if
  • text/html & text/plain的区别

    需要了解的概念 Content Type 用于定义用户的浏览器或相关设备如何显示将要加载的数据 或者如何处理将要加载的数据 MIME MIME类型就是设定某种扩展名的文件用一种应用程序来打开的方式类型 当该扩展名文件被访问的时候 浏览器会自
  • Red Hat Enterprise Linux 7.9 下载

    1 Red Hat Linux 所有版本 5 6 7 8 9 下载 https pan baidu com s 1eSYuljc2Bt9OXZmWdZQ96A 提取码 cqfu 推荐 https pan baidu com s 1RISly
  • c#:委托与回调函数示例

    c 委托与回调函数示例 上一篇文章已经给大家详细讲解过委托的意思 如何声明定义 如何通过委托调用函数 以及委托中的多播等 今天是刚刚学到委托新的用法 可以将方法当作参数来传递 并且不需用示例委托 所以今天来给大家再次拿出来详细讲解一下 一

随机推荐

  • Spring Boot日志配置logback

    目录 一 logback介绍 1 logback定义 2 logback启动加载文件 3 pom依赖 3 日志级别 二 配置文件详解 1 configuration 2 property 3 springProperty 4 logger
  • Flutter 自定义marquee垂直滚动

    自定义垂直marquee插件 用来向上垂直滚动列表 一般滚动公告 自定义类 import dart async import package flutter material dart class Marquee extends State
  • mongodb的sharding(分片)横向扩容的方法

    sharding是mongodb进行横向扩容的方法 sharding把一个集合的不同部分存储到不同的机器上 当一个数据库集合变得很大时 你只需要加入新的机器即可 sharding自动把集合的数据分布到新的服务器上 sharding自动实现数
  • uni-app小程序中v-show与display:flex一起使用时v-show不生效!

    问题 同时使用v show与display flex的时候 发现v show不能隐藏元素 原因 经排查原因后发现是因为display flex样式的优先级高于了v show中的display none 解决办法 display flex不要
  • xpah的使用

    目录 基本介绍 基本使用 案例 基本介绍 为什么要学习xpath scrapy框架支持xpath提取 selenium 经常使用xpath提取 使用简洁 语法稍稍复杂 xpath是什么 全称 xml path language 从xml中提
  • Jackson快速入门

    Java生态圈中有很多处理JSON和XML格式化的类库 Jackson是其中比较著名的一个 虽然JDK自带了XML处理类库 但是相对来说比较低级 使用本文介绍的Jackson等高级类库处理起来会方便很多 引入类库 由于Jackson相关类库
  • How to use jupyterlab in Ubuntu 22.04

    How to use jupyterlab in Ubuntu 22 04 Install Start Stop Install lwk qwfys pip install jupyterlab upgrade i http mirrors
  • matplotlib中颜色映射的使用

    matplotlib3 3中使用颜色映射 问题描述 在Python编程从入门到实践一书中15 2 8节 按照书中代码 在python3 8与matplotlib3 3中 camp plt cm Blues报错 解决 搜索发现应将此处语法改为
  • WDS桥接副路由器有线上网方法

    副路由器DHCP一定要关闭 或者IP池设置的范围不能包括有线设备IP
  • 替换字符串中出现的某段字符串的C语言和C++实现方式

    C语言实现方式 使用了循坏来重复替换操作 直到无法再找到需要替换的子串为止 具体实现如下 include
  • ajax地址url和action,jsp,ajax_JSP中 post url地址重写or action如何实现,jsp,ajax - phpStudy...

    JSP中 post url地址重写or action如何实现 求解在JSP中 post url地址 ctx web webuser getRights 这个是获取导航JSON ctx 是应用地址知道 后面 web webuser getRi
  • Docker实践(一)应用场景和安装

    随着互联网的发展 容器技术越来越得到大规模应用厂家的重视 Docker作为paas平台的容器 最近几年引起众多技术人士的关注 并且也越来越流行 一 Docker是什么 Docker 是一个开源的应用容器引擎 让开发者可以打包他们的应用以及依
  • (c/c++)——类型转换

    文章目录 一 C风格的类型转换 二 C 的强制类型转换 1 static cast 最常用 2 reinterpret cast 只用于指针或引用转换 3 const cast 去除指针或引用的const限定 4 dynamic cast
  • QT 项目中 配置文件 pro详解

    QT 的配置文件 pro 解析 1 注释 单行注释 2 QT core gui QT gui 新增 或者减去 QT使用的模块 各个模块间使用空格分开 默认会包含 core 与 gui两个模块 3 TEMPLATE VALUE模板 VALUE
  • mysql group by 后求和_MySQL分组求和GROUP BY

    源代码实例 工资统计表 SELECT wages header bill date wages name SUM wages money AS moneys FROM wages header LEFT JOIN wages ON wage
  • 输入字符串,统计各个字符出现的个数(C语言实现)

    include
  • java 线程同步的list_java集合框架线程同步代码详解

    List接口的大小可变数组的实现 实现了所有可选列表操作 并允许包括null在内的所有元素 除了实现List接口外 此类还提供一些方法来操作内部用来存储列表的数组的大小 此类大致上等同于Vector类 除了此类是不同步的 size isEm
  • C语言——输入3个整数进行比较,要求从小到大输出

    方法一 3个数值从小到大进行排序 把所有可能罗列出来 输出的时候选择运行其中的一种可能输出 include
  • MySQL集群高可用架构

    MySQL集群高可用架构 前言 高可用架构对于互联网服务基本是标配 无论是应用服务还是数据库服务都需要做到高可用 对于一个系统而言 可能包含很多模块 比如前端应用 缓存 数据库 搜索 消息队列等 每个模块都需要做到高可用 才能保证整个系统的
  • java中的IO和NIO的区别原理

    Java NIO和IO的主要区别 IO NIO 面向Stream 面向Buffer 阻塞IO 非阻塞IO Selectors 面向Stream和面向Buffer Java NIO和IO之间最大的区别是IO是面向流 Stream 的 NIO是