首页 > 代码库 > Java NIO学习笔记(一)
Java NIO学习笔记(一)
文章目录:
1.什么是IO
2.什么是Java NIO
3.I/O常见概念
4.为什么使用NIO
5.IO VS NIO
一、什么是IO
I/O 或者输入/输出 , 指的是计算机与外部世界或者一个程序与计算机的其余部分的之间的接口。它对于任何计算机系统都非常关键,因而所有 I/O 的主体实际上是内置在操作系统中的。单独的程序一般是让系统为它们完成大部分的工作。在 Java 编程中,直到最近一直使用 流 的方式完成 I/O。所有 I/O 都被视为单个的字节的移动,通过一个称为 Stream 的对象一次移动一个字节。流 I/O 用于与外部世界接触。它也在内部使用,用于将对象转换为字节,然后再转换回对象。我们习惯上说到接口就是指I/O接口。
二、什么是Java NIO
nio 是 New I/O 的简称,属于当时 jdk1.4 提供的新 api。如今 jdk 版本已经到 1.8 了,新 IO 这个称谓有点不合适了,nio 还有一个更合适的叫法——非阻塞(non-blocking)IO。
三、I/O常见概念
3.1 DMA
DMA(Direct Memory Access,直接内存存取) 是所有现代电脑的重要特色,在DMA控制器和内存之间传输数据的时候CPU是空闲的。
在dma传输数据的过程中,要求源物理地址和目标物理地址必须是连续的。但在有的计算机体系中,如IA,连续的存储器地址在物理上不一定是连续的,则dma传输要分成多次完成。如果传输完一块物理连续的数据后发起一次中断,同时主机进行下一块物理连续的传输,则这种方式即为block dma方式。scatter/gather方式则不同,它是用一个链表描述物理不连续的存储器,然后把链表首地址告诉dma master。dma master传输完一块物理连续的数据后,就不用再发中断了,而是根据链表传输下一块物理连续的数据,最后发起一次中断。
很显然scatter/gather方式比block dma方式效率高。
3.2 内核空间和用户空间
用户空间是常规进程所在区域。JVM就是常规进程,驻守于用户空间。用户空间是非特权区域:比如,在该区域执行的代码就不能直接访问硬件设备。内核空间是操作系统所在区域。内核代码有特别的权力:它能与设备控制器通讯,控制着用户区域进程的运行状态,等等。最重要的是,所有I/O都直接或间接通过核空间。
当进程请求I/O操作的时候,它执行一个系统调用将控制权移交给内核。当内核以这种方式被调用,它随即采取任何必要步骤,找到进程所需数据,并把数据传送到用户空间内的指定缓冲区。内核试图对数据进行高速缓存或预读取,因此进程所需数据可能已经在内核空间里了。如果是这样,该数据只需简单地拷贝出来即可。如果数据不在内核空间,则进程被挂起,内核着手把数据读进内存。
为什么不直接让磁盘控制器把数据送到用户空间的缓冲区呢?这样做有几个问题。首先,硬件通常不能直接访问用户空间 (硬件设备通常不能直接使用虚拟内存地址) 。其次,像磁盘这样基于块存储的硬件设备操作的是固定大小的数据块,而用户进程请求的可能是任意大小的或非对齐的数据块。在数据往来于用户空间与存储设备的过程中,内核负责数据的分解、再组合工作,因此充当着中间人的角色。
3.3 虚拟内存
所有现代操作系统都使用虚拟内存。虚拟内存意为使用虚假(或虚拟)地址取代物理(硬件
RAM)内存地址。这样做好处颇多,总结起来可分为两大类:
- 一个以上的虚拟地址可指向同一个物理内存地址。
- 虚拟内存空间可大于实际可用的硬件内存。
设备控制器不能通过DMA直接存储到用户空间,但通过利用上面提到的第一项,则可以达到相同效果。把内核空间地址与用户空间的虚拟地址映射到同一个物理地址,这样,DMA硬件(只能访问物理内存地址)就可以填充对内核与用户空间进程同时可见的缓冲区。但前提条件是,内核与用户缓冲区必须使用相同的页对齐,缓冲区的大小还必须是磁盘控制器块大小(通常为512 字节磁盘扇区)的倍数。操作系统把内存地址空间划分为页,即固定大小的字节组。内存页的大小总是磁盘块大小的倍数,通常为2次幂(这样可简化寻址操作)。典型的内存页为1,024、2,048和4,096字节。
现代操作系统的分页技术
3.4 分页技术
现代CPU包含一个称为内存管理单元(MMU)的子系统,逻辑上位于CPU与物理内存之间。该设备包含虚拟地址向物理内存地址转换时所需映射信息。当CPU引用某内存地址时,MMU负责确定该地址所在页(往往通过对地址值进行移位或屏蔽位操作实现),并将虚拟页号转换为物理页号(这一步由硬件完成,速度极快)。如果当前不存在与该虚拟页形成有效映射的物理内存页,MMU会向CPU提交一个页错误。页错误随即产生一个系统调用,把控制权移交给内核,附带导致错误的虚拟地址信息,然后内核采取步骤验证页的有效性。内核会安排页面调入操作,把缺失的页内容读回物理内存。这往往导致别的页被移出物理内存,好给新来的页让地方。在这种情况下,如果待移出的页已经被碰过了(自创建或上次页面调入以来,内容已发生改变),还必须首先执行页面调出,把页内容拷贝到磁盘上的分页区。
如果所要求的地址不是有效的虚拟内存地址(不属于正在执行的进程的任何一个内存段),则该页不能通过验证,段错误随即产生。于是,控制权转交给内核的另一部分,通常导致的结果就是进程被强令关闭。一旦出错的页通过了验证,MMU随即更新,建立新的虚拟到物理的映射(如有必要,中断被移出页的映射),用户进程得以继续。造成页错误的用户进程对此不会有丝毫察觉,一切都在不知不觉中进行。
3.5 面向块(文件)的I/O和流I/O
文件I/O属文件系统范畴,文件系统与磁盘迥然不同。磁盘把数据存在扇区上,通常一个扇区512 字节。磁盘属硬件设备,对何谓文件一无所知,它只是提供了一系列数据存取窗口。在这点上,磁盘扇区与内存页颇有相似之处:都是统一大小,都可作为大的数组被访问。
文件系统是更高层次的抽象,是安排、解释磁盘(或其他随机存取块设备)数据的一种独特方式。您所写代码几乎无一例外地要与文件系统打交道,而不是直接与磁盘打交道。是文件系统定义了文件名、路径、文件、文件属性等抽象概念。文件系统把一连串大小一致的数据块组织到一起。有些块存储元信息,如空闲块、目录、索引等的映射,有些包含文件数据。单个文件的元信息描述了哪些块包含文件数据、数据在哪里结束、最后一次更新是什么时候,等等。
当用户进程请求读取文件数据时,文件系统需要确定数据具体在磁盘什么位置,然后着手把相关磁盘扇区读进内存。老式的操作系统往往直接向磁盘驱动器发布命令,要求其读取所需磁盘扇区。而采用分页技术的现代操作系统则利用请求页面调度取得所需数据。
采用分页技术的操作系统执行I/O的全过程可总结为以下几步:
? 确定请求的数据分布在文件系统的哪些页(磁盘扇区组)。磁盘上的文件内容和元数据可能跨越多个文件系统页,而且这些页可能也不连续。
? 在内核空间分配足够数量的内存页,以容纳得到确定的文件系统页。
? 在内存页与磁盘上的文件系统页之间建立映射。
? 为每一个内存页产生页错误。
? 虚拟内存系统俘获页错误,安排页面调入,从磁盘上读取页内容,使页有效。
? 一旦页面调入操作完成,文件系统即对原始数据进行解析,取得所需文件内容或属性
信息。
大多数操作系统假设进程会继续读取文件剩余部分,因而会预读额外的文件系统页。如果内存争用情况不严重,这些文件系统页可能在相当长的时间内继续有效。这样的话,当稍后该文件又被相同或不同的进程再次打开,可能根本无需访问磁盘。这种情况您可能也碰到过:当重复执行类似的操作,如在几个文件中进行字符串检索,第二遍运行得似乎快多了。
类似的步骤在写文件数据时也会采用。这时,文件内容的改变(通过write( ))将导致文件系统页变脏,随后通过页面调出,与磁盘上的文件内容保持同步。文件的创建方式是,先把文件映射到空闲文件系统页,在随后的写操作中,再将文件系统页刷新到磁盘。
并非所有I/O都是面向块的,也有流I/O,其原理模仿了通道。I/O字节流必须顺序存取,常见的例子有TTY(控制台)设备、打印机端口和网络连接。
流的传输一般(也不必然如此)比块设备慢,经常用于间歇性输入。多数操作系统允许把流置于非阻塞模式,这样,进程可以查看流上是否有输入,即便当时没有也不影响它干别的。这样一种能力使得进程可以在有输入的时候进行处理,输入流闲置的时候执行其他功能。比非阻塞模式再进一步,就是就绪性选择。就绪性选择与非阻塞模式类似(常常就是建立在非阻塞模式之上),但是把查看流是否就绪的任务交给了操作系统。操作系统受命查看一系列流,并提醒进程哪些流已经就绪。这样,仅仅凭借操作系统返回的就绪信息,进程就可以使用相同代码和单一线程,实现多活动流的多路传输。这一技术广泛用于网络服务器领域,用来处理数量庞大的网络连接。就绪性选择在大容量缩放方面是必不可少的。
3.6 内存映射文件
内存映射I/O使用文件系统建立从用户空间直到可用文件系统页的虚拟内存映射。这样做有几
个好处:
? 用户进程把文件数据当作内存,所以无需发布read( )或write( )系统调用。
? 当用户进程碰触到映射内存空间,页错误会自动产生,从而将文件数据从磁盘读进内存。如果用户修改了映射内存空间,相关页会自动标记为脏,随后刷新到磁盘,文件得到更新。
? 操作系统的虚拟内存子系统会对页进行智能高速缓存,自动根据系统负载进行内存管理。
? 数据总是按页对齐的,无需执行缓冲区拷贝。
? 大型文件使用映射,无需耗费大量内存,即可进行数据拷贝。
3.7 文件锁定
文件锁定机制允许一个进程阻止其他进程存取某文件,或限制其存取方式。通常的用途是控制共享信息的更新方式,或用于事务隔离。在控制多个实体并行访问共同资源方面,文件锁定是必不可少的。数据库等复杂应用严重信赖于文件锁定。
“文件锁定”从字面上看有锁定整个文件的意思(通常的确是那样),但锁定往往可以发生在更为细微的层面,锁定区域往往可以细致到单个字节。锁定与特定文件相关,开始于文件的某个特定字节地址,包含特定数量的连续字节。这对于协调多个进程互不影响地访问文件不同区域,是至关重要的。
文件锁定有两种方式:共享的和独占的。多个共享锁可同时对同一文件区域发生作用;独占锁则不同,它要求相关区域不能有其他锁定在起作用。
四、为什么使用NIO
I/O的终极目标是效率,而效率离不开底层操作系统和文件系统的特性支持。这些特性包括:文件锁定、非阻塞I/O、就绪性选择、和内存映射。当今操作系统大都支持这些特性,而Java传统I/O机制并没有模拟这些通用的I/O服务。NIO 的创建目的是为了让 Java 程序员可以实现高速 I/O 而无需编写自定义的本机代码。NIO 将最耗时的 I/O 操作(即填充和提取缓冲区)转移回操作系统,因而可以极大地提高速度。通常一次缓冲区操作是这样的:某个进程需要进行I/O操作,它执行了一次读(read)或者写(write)的系统调用,向底层操作系统发出了请求,操作系统会按要求把数据缓冲区填满或者排干。
对于文件I/O,集成的 I/O提供了对于NIO特性的支持。 企业级应用软件中涉及I/O的部分多半是读写文件的功能性需求,很少有在并发上的要求,那么JavaIO包已经很胜任了。
对于网络I/O,传统的阻塞式I/O,一个线程对应一个连接,采用线程池的模式在大部分场景下简单高效。当连接数非常多时,并且数据的移动非常频繁,NIO无疑是更好的选择。
说明:内容是自己根据一些
五、IO VS NIO
5.1 文件流与文件块的比较
原来的 I/O 库(在 java.io.*中) 与 NIO 最重要的区别是数据打包和传输的方式。原来的 I/O 以流的方式处理数据,经常为了处理个别字节或字符,就要执行好几个对象层的方法调用。而 NIO 以块的方式处理数据。面向流 的 I/O 系统一次一个字节地处理数据。一个输入流产生一个字节的数据,一个输出流消费一个字节的数据。为流式数据创建过滤器非常容易。链接几个过滤器,以便每个过滤器只负责单个复杂处理机制的一部分,这样也是相对简单的。不利的一面是,面向流的 I/O 通常相当慢。一个 面向块 的 I/O 系统以块的形式处理数据。每一个操作都在一步中产生或者消费一个数据块。按块处理数据比按(流式的)字节处理数据要快得多。但是面向块的 I/O 缺少一些面向流的 I/O 所具有的优雅性和简单性。
5.2 集成的 文件I/O
在 JDK 1.4 中原来的 I/O 包和 NIO 已经很好地集成了。 java.io.* 已经以 NIO 为基础重新实现了,所以现在它可以利用 NIO 的一些特性。例如, java.io.* 包中的一些类包含以块的形式读写数据的方法,这使得即使在更面向流的系统中,处理速度也会更快。也可以用 NIO 库实现标准 I/O 功能。例如,可以容易地使用块 I/O 一次一个字节地移动数据。NIO 还提供了原 I/O 包中所没有的许多好处。
引用链接:(有些链接需要FQ),感谢原创作者的知识分享
1.http://blog.csdn.net/tsyj810883979/article/details/6876594#comments
2.http://www.iteye.com/magazines/132-Java-NIO
3.http://ifeve.com/overview/
4.http://www.ibm.com/developerworks/cn/education/java/j-nio/j-nio.html
5.http://www.myexception.cn/internet/1218142.html
6.http://www.jb51.net/article/50621.htm
7.http://www.yangyong.me/java-nio%E5%85%A5%E9%97%A8%E4%B8%8E%E8%AF%A6%E8%A7%A3/
Java NIO学习笔记(一)