java NIO 笔记

IO概念

输入输出就是把数据移进或移除缓冲区。
进程执行IO操作,归结起来,就是向操作系统发出请求,让它把缓冲区里的数据排干(写),或者把缓冲区填满。
进程使用read()系统调用,要求其缓冲区被填满。内核随即向磁盘控制硬件发出命令,要求其从磁盘读取数据。磁盘控制器把数据直接写入内核内存缓冲区,这一步通过DMA(Direct Memory Access,直接存储器访问)完成,无需主CPU协助。一旦磁盘控制器把缓冲区装满,内核即把数据从内核空间的临时缓冲区拷贝到进程执行read()调用时指定的缓冲区。如图所示:

在这里插入图片描述
用户空间和内核空间的概念:
用户空间是常规进程所在的区域。JVM就是常规进程,驻守于用户空间。用户空间是非特权空间:比如,在改区域执行的代码就不能直接访问硬件设备。
内核空间是操作系统所在区域。内核代码有特别的权力:它能与设备控制器通讯,控制着用户区域进程的运行状态。
IO操作都直接或间接通过内核空间。
当进程去请求IO操作时,它执行一个系统调用(有时称为陷阱)将控制权移交给内核(如C里面的open()、read()、write()、close()函数要做的无非就是建立和执行适当的系统调用)。当内核以这种方式被调用,它随机采取任何必要步骤,找到进程所需数据,并把数据传送到用户空间内的指定缓冲区。内核试图对数据进行高速缓存或预读取,因此进程所需数据可能已经在内核空间了。如果是这样,该数据只需简单地拷贝出来即可。如果数据不在内核空间,则进程被挂起,内核着手把数据读进内存。
数据从内核拷贝到用户空间似乎有些多余,为什么不直接让磁盘控制器把数据送到用户空间地缓冲区?首先,硬件通常不能直接访问用户空间。其次,像磁盘这样基于块存储地硬件设备操作的是固定大小地数据块,而用户进程请求地可能是任意大小地或非对齐的数据块。在数据往来于用户空间与存储设备的过程中,内核负责数据的分解、再组合工作,因此充当着中间人的角色。

发散/汇聚

许多操作系统能把组装/分解过程进行得更加高效。根据发散/汇聚的概念,进程只需一个系统调用,就能把一连串缓冲区地址传递给操作系统。然后,内核就可以顺序填充或排干多个缓冲区,读的时候就把数据发散到多个用户空间缓冲区,写的时候再从多个缓冲区把数据汇聚起来。如图:

在这里插入图片描述
这样用户进程就不必多次执行系统调用,内核也可以优化数据的处理过程,因为它已掌握传输数据的全部信息。如果系统配有多个CPU,甚至可以同时填充或排干多个缓冲区。

虚拟内存

虚拟内存意为使用虚假(或虚拟)地址代替物理(硬件RAM)内存地址。好处:
	1、一个以上的虚拟地址可指向同一个物理内存地址。
	2、虚拟内存空间可大于实际可用的硬件内存。
设备控制器不能通过DMA直接存储到用户空间,但利用虚拟内存,则可达到相同效果。把内核空间地址与用户空间的虚拟地址映射到同一个物理地址,这样DMA硬件(指定访问物理内存地址)就可以填充对内核与用户空间进程同时可见的缓冲区。如图:

在这里插入图片描述
这样省去了内核与用户空间的来往拷贝,但前提条件是,内核与用户缓冲区必须使用相同的页对齐,缓冲区的大小必须是磁盘控制器大小(通常是512字节磁盘扇区)的倍数。操作系统把内存地址划分为页,即固定大小的字节组。内存页的大小总是磁盘块大小的倍数,通常为2次幂(这样可简化寻址)。如图展示了多个虚拟地址的虚拟内存是如何映射到物理内存的:
在这里插入图片描述

内存页面调度

为了支持虚拟内存的第二个特性(寻址空间大于物理内存),就必须进行虚拟内存分页(经常称为交换,虽然真正的交换是再进程层面完成,而非页层面)。依照该方案,虚拟内存空间的页面能够继续存在于外部磁盘存储,这样就为物理内存中的其他虚拟页面腾出了空间。从本质上来说,物理内存充当了分页区的高速缓存;而所谓分页区,即从物理内存置换出来,转而存储于磁盘的内存页面。
图为由于高速缓存的物理内存。显示了分属于四个进程的虚拟页面,其中每个进程都有属于自己的虚拟内存页面。进程A有五个页面,其中两个装入内存,其余存储于磁盘。

在这里插入图片描述
把内存页大小设定为磁盘块大小的倍数,这样内核就可直接向磁盘控制硬件发布命令,把内存页写入磁盘,在需要时再重新装入。结果是,所有的磁盘IO都在页层面完成。对于采用分页技术的现代操作系统而言,这也是数据在磁盘与物理内存之间往来的唯一方式。
现代CPU包含了一个称为内存管理单元(MMU)的子系统,逻辑上位于CPU与物理内存之间。该设备包含虚拟地址向物理内存地址转换时所需映射信息。当CPU引用某内存地址时,MMU负责确定该地址所在页(往往通过对地址值进行移位或屏蔽位操作实现),并将虚拟页号转换为物理页号(这一步由硬件完成,速度极快)。如果当前不存在与该虚拟页形成有效映射的物理内存页,MMU会向CPU提交一个错误。

文件IO

文件系统时更高层次的抽象,是安排、解释磁盘(或其他随机存取设备)数据的一种独特方式。是文件系统定义了文件名、路径、文件、文件属性等抽象概念。
文件系统把一连串大小一致的数据块组织到一起。有些块存储元信息,如空闲块、目录、索引等的映射,有些包含文件数据。单个文件的数据描述了那些块包含文件数据、数据在哪里结束、最后一次更新时间等等。
采用分页技术的操作系统执行IO的全过程可总结为以下几步:
	1、确定请求的数据分布在文件系统的那些页(磁盘扇形区)。磁盘上的文件内容和元数据可能跨越多个文件系统页,而且这些页可能也不连续。
	2、在内核空间分配足够数量的内存页,以容纳得到确定的文件系统页。
	3、在内存页与磁盘上的文件系统页之间建立映射。
	4、为每一个内存页产生页错误。
	5、虚拟内存系统捕获页错误,安排页面调入,从磁盘上读取页内容,使页有效。
	6、一旦页面调入操作完成,文件系统即对数据进行解析,取得所需文件内容或属性信息。
需要注意的是,这些文件系统数据也会同其他内存页一样得到高速缓存。对于随后发生的IO请求,文件数据的部分或全部可能仍旧位于物理内存当中,无需再从磁盘读取即可重复使用。

内存映射IO

在这里插入图片描述
内存映射IO使用文件系统建立从用户空间直到可用文件系统页的虚拟内存映射。这样做有几个好处:
1、用户进程把文件数据当作内存,所以无需发布read()或write()系统调用。
2、当用户进程碰触到映射内存空间,页错误会自动产生,从而将文件数据从磁盘读进内存。如果用户修改了映射内存空间,相关页会自动标记为脏,随后刷新到从盘,文件得到更新。
3、操作系统的虚拟内存子系统会对页进行智能高速缓存,自动根据系统负载进行内存管理。
4、数据总是按页对齐的,无需执行缓冲区拷贝。
5、大型文件使用映射,无需耗费大量内存,即可进行数据拷贝。

文件锁定

文件锁定机制运行一个进程阻止其他进程存取某文件,或限制其存取方式。通常的用途是控共享信息的更新方式,或用于事务隔离。
文件锁定可以锁整个文件,也可以发生在更细微的方面,可以细致到单个字节。
文件锁定有两种方式:共享的和独占的。

在这里插入图片描述
在这里插入图片描述
文件锁有使用和强制使用之分。建议型文件锁会向提出请求的进程提供当前锁定信息。
强制性锁由操作系统或文件系统强制实施,不管进程对锁的存在知道与否,都会组织其对文件锁定区域的访问。

流IO

流的传输一般比块设备慢,经常用于间歇性输入。多数操作系统通常把流置于非块模式。
比非块模式再进一步,就是就绪性选择。就绪性选择与非块模式类似(常常就是建立在非块模式之上),但是把查看流是否就绪的任务交给了操作系统。

二 缓冲区

一个buffer对象是固定数量的数据的容器。其作用是一个存储器,或者分段运输区,在这里数据可被存储并在之后用于检索。尽管缓冲区作用于它们存储的原始数据类型,但缓冲区十分倾向于字节处理。非字节缓冲区可以在后台执行从字节到字节的转换,这取决去缓冲区是如何创建的。
缓冲区的工作于通道紧密联系。通道是IO传输发生时通过的入口,而缓冲区是这些数据传输的来源或目标。对于离开缓冲区的传输,你想传递出去的数据被置于一个缓冲区,被传送到通道。对于传回缓冲区的传输,一个通道将数据放置在你所提供的缓冲区中。这种协同对象(通常是你所写的对象以及一到多个channel对象)之间进行的缓冲区数据传递是高效数据处理的关键。

在这里插入图片描述

缓冲区基础

概念上,缓冲区是包在一个对象内的基本数据元素数组。Buffer类相比一个简单数组的优点是它将关于数据的数据内容和信息包含在一个单一的对象中。

属性

capacity(容量):缓冲区能够容纳的数据元素的最大数量。这一容量在缓冲区创建时被设定,并且永远不能被改变。
limit(上界):缓冲区的第一个不能被读或写的元素。或者说,缓冲区中现存元素的计数。
position(位置):下一个要被读或写的元素的索引。位置会自动由相应的get()和put()函数更新。
mark(标记):一个备忘位置。
这四个属性总是遵循以下关系:
	0 <= mark <= position <= limit <= capacity

比较

equals()函数用以测试缓冲区是否相等。
compareTo()用以比较缓冲区。
两个缓冲区被认为相等的充要条件:
	1、两个对象类型相等。
	2、两个对象剩余同样数量的元素。
	3、在每个缓冲区应被get()函数返回的剩余数据元素序列必须一致。

在这里插入图片描述
在这里插入图片描述
缓冲区也支持用compareTo()函数以词典顺序进行比较。这一函数在缓冲区参数小于,等于,或者大于引用compareTo()的对象实例时,分别返回一个负整数,0和正整数。典型的缓冲区所实现的java.lang.Comparable接口语义。这意味着缓冲区数组可以通过调用java.util.Aarrays.sort()函数按照它们的内容进行排序。
比较是针对每个缓冲区内剩余数据进行的,与他们在equls()中的方式相同,知道不相等的元素被发现或者到达缓冲区的上界。

创建缓冲区

buffer.allocate(length);  创建length长度的缓冲区。
buffer.wrap(new byte[26]);	直接用数组创建缓冲区,对缓冲区put,会影响数组。
wrap有个传数组和两个int类型的参数,并不会创建一个只占用了一个数组子集的缓冲区,而是可以存取这个数组的全部范围。
如需创建一个只占用备份数组一部分的缓冲区,可使用slice()方法。
通过allocate()和wrap()创建的缓冲区都是间接的。间接的缓冲区使用备份数组

字节缓冲区

字节顺序

多字节数组被存储在内存中的方式一般被称为endian-ness(字节顺序)。如果数字数值的最高字节——big end(大端),位于地位地址,那么系统就是大端字节顺序(图2.14).如果低位字节最先保存在内存中,那么小端字节顺序(图2.15)。

在这里插入图片描述
在这里插入图片描述

直接缓冲区

直接缓冲区被用于与通道和固有IO例程交互。它们通过使用固有代码来告知操作系统直接释放或填充内存区域,对用于通道直接或原始存取得内存区域中的字节元素的存储尽了最大的努力。
直接缓冲区只有bytebuffer可建立,通过allocateDirect()方法。
直接缓冲区和间接缓存区的区别:
	1、直接缓冲区直接建在物理内存中,间接缓冲区建在JVM内存上。
	2、直接缓冲区使用的内存是通过本地操作系统方面的代码分配的,绕过了JVM堆栈。建立和销毁直接缓冲区会明显比具有堆栈的缓冲区更加破费,这取决于主操作系统以及JVM实现。
	3、直接缓冲区的内存区域不受无用存储单元手机支配,因为它们位于标准JVM堆栈之外。

内存映射缓冲区

映射缓冲区是带有存储在文件,通过内存映射来存取数据元素的字节缓冲区。映射缓冲区通常是直接存取内存的,只能通过FileChannel类创建。映射缓冲区的用法和直接缓冲区类似,但是MappedByteBuffer对象可以处理独立于文件存取形式的许多特定字符。

通道

通道是一种途径,借助该途径,可以用最小的总开销来访问操作系统本身的IO服务。缓冲区则是通道内部用来发送和接受数据的端点。
通道只能在字节缓冲区上操作。
通道分为两类:文件(file)和套接字(socket)通道。

打开通道

filechannel只能通过文件流的getChannel()获得,如:FileInputStream.getChannel();
Socket 通道有可以直接创建新 socket 通道的工厂方法

通道可以是单向或者双向的。一个 channel 类可能实现定义read( )方法的 ReadableByteChannel 接口,而另一个 channel 类也许实现 WritableByteChannel 接口以提供 write( )方法。实现这两种接口其中之一的类都是单向的,只能在一个方向上传输数据。如果一个类同时实现这两个接口,那么它是双向的,可以双向传输数据.
byteChannel接口实现了ReadableByteChannel 和WritableByteChannel ,它是一个用来聚焦它自己以一个新名称继承的多个接口的便捷接口。
通道会连接一个特定IO服务且通道实例的性能受它所连接的IO服务的特征限制。一个连接到只读文件的channel实例不能进行写操作,即使该实例所属的类可能有write方法。

Scatter/Gather (分散读取/聚集写入)

通道提供了一种被称为Scatter/Gather的重要新功能(也称矢量IO)。指在多个缓冲区上实现一个简单的IO操作。对于一个write操作而言,数据是从几个缓冲区按照顺序抽取(称为Gather)并沿着通道发送的。缓冲区本身并不具备这种gather的能力。该grther过程的效果就好比全部缓冲区的内容被连结起来,并在发送数据前存放到一个大的缓冲区中。对于read操作而言,从通道读取数据会按顺序被散布(称为scatter)到多个缓冲区,将每个缓冲区填满直至通道中的数据或者缓冲区的最大空间被消耗完。
使用得当的话,是一个极其强大的工具。它委托操作系统来完成辛苦活:将读取到的数据分开存放到多个存储桶(bucket)或者将不同的数据区块合并成一个整体。节省了来回移动数据的工作,也就避免了缓冲区拷贝和减少了代码数量。

文件锁定

锁和文件管理,而不是与通道关联。我们使用锁来判优外部进程,而不是判优同一个java虚拟机上的进程。
文件锁旨在在进程级别上判优文件访问。

内存映射文件

finechannel上的map()方法会创建一个由磁盘文件支持的虚拟内存映射,并在那块虚拟内存空间外部封装一个MappedByteBuffer对象。
由map()方法返回的MappedByteBuffer对象的行为在多数方面类似一个基于内存的缓冲区,只不过该对象的数据元素存储在磁盘上的一个文件中。调用get()方法会从磁盘文件中获取数据,此数据反应该文件的当前内容,即使在映射建立之后文件以及被一个外部进程做了修改。通过文件映射看到的数据同您用常规方法读取文件看到的内容是完全一样的。相似的,对映射的缓冲区实现一个put(0会更新磁盘上的那个文件(假设对该文件您有写的权限),并且您做的修改对于文件的其他阅读者也是见的。
通过内存映射机制来访问一个文件会比使用常规文件读写高效得多,甚至比使用通道得效率都高。因为不需要明确得系统调用。更重要的是,操作系统得虚拟内存可以自动缓存内存页(memory page)。这些页使用系统内存来缓存的,所以不会消耗java虚拟机内存堆(memory heap)。
一旦一个内存页已经生效(从磁盘上缓存进来)	,它就能以完全的硬件速度再次被访问而不需要再次调用系统命令来获取数据。那些包含索引以及其他需频繁引用或更新的内容的巨大而结构化文件能因内存映射机制受益非常多。如果同时结合文件锁定来保护关键区域和控制事务原子性。
一个映射一旦建立之后将保持有效,知道MappedByteBuffer对象被施以垃圾收集动作为止。同锁不一样的是,映射缓冲区没有绑定到创建它们的通道上。关闭相关联的FileChannel不会破坏映射,只有丢弃缓冲区对象本身才会破坏映射。
当创建一个虚拟内存映射之后,文件数据通常不会因此被从磁盘读取到内存(这取决于操作系统)。该过程类似打开一个文件:文件先被定位,然后一个文件句柄会被创建,当准备好之后可以通过这个句柄来访问文件数据。

Channel-to-Channel 传输

transferTo( )和 transferFrom( )方法允许将一个通道交叉连接到另一个通道,而不需要通过中间缓冲区来传递数据。只有fileChannel类有这两个方法。socket不能直接传输数据,不过可以transferTo( )方法传输给一个socket通道,或者用transferFrom( )方法将数据从一个socket通道直接读取到一个文件中。
直接的通道传输不会更新与某个FileChannel关联的position值。请求的数据传输将从position参数指定的位置开始,传输的字节数不超过count参数的值。实际传输的字节数会由方法返回,可能少于请求的字节数。
对于传输数据来源是一个文件的 transferTo( )方法,如果 position + count 的值大于文件的 size 值,传输会在文件尾的位置终止。假如传输的目的地是一个非阻塞模式的 socket 通道,那么当发送队列(send queue)满了之后传输就可能终止,并且如果输出队列(output queue)已满的话可能不会发送任何数据。类似地,对于 transferFrom( )方法:如果来源 src 是另外一个 FileChannel并且已经到达文件尾,那么传输将提早终止;如果来源 src 是一个非阻塞 socket 通道,只有当前处于队列中的数据才会被传输(可能没有数据)。由于网络数据传输的非确定性,阻塞模式的socket 也可能会执行部分传输,这取决于操作系统。许多通道实现都是提供它们当前队列中已有的数据而不是等待您请求的全部数据都准备好

socket通道

全部 socket 通道类都是由位于 java.nio.channels.spi 包中的AbstractSelectableChannel 引申而来。这意味着我们可以利用一个selector对象来执行socket通道的有条件的选择。
DatagramChannel和SocketChannel实现定义读和写功能的接口而ServerSocketChannel不是想。ServerSocketChannel负责监听传入的连接和创建新的SocketChannel对象,它本身不传输数据。
全部socket通道类在被实例化时都会创建一个对等socket对象。java.net的类(socket、serversocket、datagramsocket),它们已经被更新以识别通道。对等socket可以通过调用socket()方法从一个通道上获取。此外,这三个java.net类现在都要getChannel()方法。

非阻塞模式

socket通道可以在非阻塞模式下运行。

socketChannel

socket和socketChannel类封装点对点、有序的网络连接,类似我们所熟知并喜爱的TCP/IP网络连接。socketChannel扮演客户端发起同一个监听服务器的连接。直到连接成功。
socket通道是线程安全的。并发访问时无需特别措施来保护发起访问的多个线程,不过任何时候只有一个读操作和写操作在进行中。请记住,sockets是面向流而非包导向的。它们可以保证发送的字节会安装顺序到达但无法承诺维持字节分组。
connect( )和 finishConnect( )方法是互相同步的,并且只要其中一个操作正在进行,任何读或写的方法调用都会阻塞,即使是在非阻塞模式下。如果此情形下您有疑问或不能承受一个读或写操作在某个通道上阻塞,请用 isConnected( )方法测试一下连接状态。

datagramChannel

socketchannel模拟连接导向的流协议(如TCP/IP),DatagramChannel模拟包导向的无连接协议(如UDP/IP)。
创建DatagramChannel的模式和创建其他socket通道是一样的:调用静态的open()方法来创建一个新实例。新DatagramChannel会有一个可以通过调用socket()方法获取的对等DatagramSocket对象。DatagramChannel对象既可以充当服务器(监听者)也可以充当客户端(发送者)。
DatagramChannel是无连接的。每个数据报(datagram)都是一个自包含的实体,拥有它自己的目的地址及不依赖其他数据报的数据净荷。与面向流的socket不同,DatagramChannel可以发送单独的数据报给不同的目的地址。同样,DatagramChannel对象也可以接收来自任意的数据包。每个到达的数据报都含有关于它来自何处的信息(源地址)。
receive( )方法将下次将传入的数据报的数据净荷复制到预备好的 ByteBuffer 中并返回一个SocketAddress 对象以指出数据来源。如果通道处于阻塞模式,receive( )可能无限期地休眠直到有包到达。如果是非阻塞模式,当没有可接收的包时则会返回 null。如果包内的数据超出缓冲区能承受的范围,多出的数据都会被悄悄地丢弃。
调用 send( )会发送给定 ByteBuffer 对象的内容到给定 SocketAddress 对象所描述的目的地址和端口,内容范围为从当前 position 开始到末尾处结束。如果 DatagramChannel 对象处于阻塞模式,调用线程可能会休眠直到数据报被加入传输队列。如果通道是非阻塞的,返回值要么是字节缓冲区的字节数,要么是“0”。发送数据报是一个全有或全无(all-or-nothing)的行为。如果传输队列没有足够空间来承载整个数据报,那么什么内容都不会被发送。
数据报协议的不可靠性是固有的,它们不对数据传输做保证。send()方法返回的非零值并不表示数据报到达了目的地,仅代笔数据报被成功加到本地网络层的传输队列。此外,传输过程中的协议可能将数据报分解成碎片。

管道

广义上讲,管道是一个用来在两个实体之间单向传输数据的导管。Unix系统中,管道被用来连接一个进程的输出和另一个进程的输入。Pipe类实现一个管道规范,不过它创建的管道是进程内(在java虚拟机进程内部)而非进程间使用的。
Pipe类创建一对提供回环机制的Channel对象。这两个通道的远端是连接起来的,以便在SinkChannel对象上的数据都能出现在SourceChannel对象上。
SinkChannel 和 SourceChannel 都由 AbstractSelectableChannel 引申而来(所以也是从 SelectableChannel 引申而来),这意味着 pipe 通道可以同选择器一起使用。
通道可以被用来仅在同一个java虚拟机内部传输数据。虽然有更加有效率的方式来在线程之间传输数据,但是使用管道的好处在于封装性。

选择器

选择器提供选择执行已经就绪的任务的能力,这使得多元IO称为可能。
需要将之前创建的一个或多个可选择的通道注册到选择器对象中。一个表示通道和选择器的键将会被返回。选择键会记住关心得通道。它们也会追踪对应的通道是否已经就绪。当您调用一个选择器的select()方法时,相关的键会被更新,用来检查所有被注册到该选择器的通道。可以获取一个键的集合,从而找到当时已经就绪的通道。通过遍历这些键,可以选择出每个从上次调用的select()开始直到选择,已经就绪的通道。
选择器类管理着一个被注册的通道集合的信息和它们的就绪状态。通道是和选择器一起被注册的,并且使用选择器来更新通道的就绪状态。
Selector类的核心是选择过程。选择器是对select()、poll()等本地调用(native call)或者类似的操作系统特定的系统的一个包装。

概念理解

缓冲区是写数据的,比如下载文件,不能下一点写一点,而是存进buffer到一定量后才进行写操作。
缓存区是读数据的,将磁盘读到cache中,加快访问速度。
操作系统层面的read系统调用并不是直接从物理设备把数据读取到应用的内存中,write系统调用也不是直接把数据写入物理设备。上层应用无论是调用操作系统的read还是调用操作系统的write,都会涉及缓冲区。具体来说,上层应用通过操作系统的read系统调用把数据从内核缓冲区复制到应用程序的进程缓冲区,通过操作系统的write系统调用把数据从应用程序的进程缓冲区复制到操作系统的内核缓冲区。
应用程序的IO操作实际上不是物理设备级别的读写,而是缓存的复制。read和write两大系统调用都不负责数据在内核缓冲区和物理设备(如磁盘、网卡等)之间的交换。这个底层的读写交换操作是由操作系统内核(Kernel)来完成的。所以,在应用程序中,无论是对socket的IO操作还是对文件的IO操作,都属于上层应用的开发,它们在输入(Input)和输出(Output)维度上的执行流程是类似的,都是在内核缓冲区和进程缓冲区之间进行数据交换。
上层应用使用read系统调用时,仅仅把数据从内核缓冲区复制到应用的缓冲区(进程缓冲区);上层应用使用write系统调用时,仅仅把数据从应用的缓冲区复制到内核缓冲区。
这里以read系统调用为例,看一下一个完整输入流程的两个阶段:
	应用程序等待数据准备好。
	从内核缓冲区向用户缓冲区复制数据。
如果是读取一个socket(套接字),那么以上两个阶段的具体处理流程如下:
	第一个阶段,应用程序等待数据通过网络到达网卡,当所等待的分组到达时,数据被操作系统复制到内核缓冲区中。这个工作由操作系统自动完成,用户程序无感知。
	第二个阶段,内核将数据从内核缓冲区复制到应用的用户缓冲区。
可以将同步与异步看成发起IO请求的两种方式。同步IO是指用户空间(进程或者线程)是主动发起IO请求的一方,系统内核是被动接收方。异步IO则反过来,系统内核是主动发起IO请求的一方,用户空间是被动接收方。同步阻塞IO(Blocking IO)指的是用户空间(或者线程)主动发起,需要等待内核IO操作彻底完成后才返回到用户空间的IO操作。在IO操作过程中,发起IO请求的用户进程(或者线程)处于阻塞状态。
非阻塞IO(Non-Blocking IO,NIO)指的是用户空间的程序不需要等待内核IO操作彻底完成,可以立即返回用户空间去执行后续的指令,即发起IO请求的用户进程(或者线程)处于非阻塞状态,与此同时,内核会立即返回给用户一个IO状态值。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值