IO,Input Output的缩写。
IO根据操作数据来源的介质不同,可以分为 Disk IO,Network IO等等.
本文描述的对象主要是Disk IO.
根据JAVA IO的时间演进顺序,分为 OIO和NIO。
=====
Inputstream
及其子类主要负责将磁盘数据读入到字节数组中。
Outputstream
及其子类主要负责将字节数组写出到磁盘中。
Reader
和Writer
和前两者类似,不过主要负责读写字符。
相同的字节在不同的编码方式下,可以显示为不同的字符。
由于Disk IO的读写数据的速度远远低于CPU处理数据的速度,所以通常在读写时,通常封装一层Buffer。
通常我们使用InputStreamReader
,OutputStreamReader
的带字符集参数的构造方法,完成将字节流到字符流的转换。
慎用FileReader
,因为其不能指定文件读写编码方式,容易导致乱码。
之所以会出现乱码,大部分时因为互相转换字节,字符时,没有指定正确的编码(通常是使用了默认得编码,比如String.getBytes默认使用JVM的默认编码,J2EE容器如tomcat使用了server.xml的ISO-8859-1的编码).
====
需要知道OIO存在哪些缺陷的话,我们需要知道底层的IO底层大概是什么样子的。下面的几个章节补充一些IO基本原理。
以用户进程A发起读数据操作为例(忽略了很多细节)
需要注意如下几点:
====
虚拟内存是指使用虚拟的地址取代物理(RAM)内存地址。这样做的好处主要有:
====
磁盘属于硬件设备,磁盘把数据存在扇区上,通常一个扇区512字节。
文件系统时更高层次的抽象,文件系统定义了文件名、路径、文件、文件属性等等。当用户进程请求读取文件数据时,文件系统需要确定数据具体存在磁盘的什么位置,然后开始把相关磁盘扇区读进内存
采用分页技术的操作系统执行IO的全过程可以分为如下几个步骤:
操作系统一般会预读额外的文件系统页以提升性能。
文件系统页可能在相当长的时间内继续有效,这样该文件在后续被进程打开时,可能根本无需访问磁盘。
文件修改时,会及时将脏页写到磁盘上。
====
I/O 字节流必须顺序存取,常见的例子有 TTY(控制台)设备、打印机端口和网络连接。
流的传输一般比块设备慢,经常用于间歇性输入。多数操作系统允许把流置于非块模式,这样,进程可以查看流上是否有输入,即便当时没有也不影响它干别的。这样一种能力使得进程可以在有输入的时候进行处理,没有输入流的时候执行其他功能。
比非块模式再进一步,就是就绪性选择。就绪性选择与非块模式类似(常常就是建立在非块模式之上),但是把查看流是否就绪的任务交给了操作系统。操作系统受命查看一系列流,并提醒进程哪些流已经就绪。这样,仅仅凭借操作系统返回的就绪信息,进程就可以使用相同代码和单一线程,实现多活动流的多路传输。这一技术广泛用于网络服务器领域,用来处理数量庞大的网络连接。就绪性选择在大容量缩放方面是必不可少的。
====
在大多数情况下,Java 应用程序并非真的受着 I/O 的束缚。操作系统并非不能快速传送数据,让 Java 有事可做;相反,是 JVM 自身在 I/O 方面效率欠佳。操作系统与 Java 基于流的 I/O模型有些不匹配。操作系统要移动的是大块数据(缓冲区),这往往是在硬件直接存储器存取(DMA)的协助下完成的。而 JVM 的 I/O 类喜欢操作小块数据——单个字节、几行文本。结果,操作系统送来整缓冲区的数据,java.io 的流数据类再花大量时间把它们拆成小块,往往拷贝一个小块就要往返于几层对象。操作系统喜欢整卡车地运来数据,java.io 类则喜欢一铲子一铲子地加工数据。有了 NIO,就可以轻松地把一卡车数据备份到您能直接使用的地方(ByteBuffer 对象)。
没有提供当今大多数操作系统普遍具备的常用 I/O 功能,如文件锁定、就绪性选择和内存映射。
====
====
每个非布尔原始数据类型都有一个缓冲区类,即为ByteBuffer、CharBuffer、DoubleBuffer、FloatBuffer、IntBuffer、LongBuffer、ShortBuffer。
====
所有的缓冲区都具有四个属性来提供关于其所包含的数据元素的信息。它们是:
====
buffer.limit(buffer.position()).position(0);
Channel的常见实现类如下:
由于本文主要讲Disk IO,所以重点介绍FileChannel,其他3类会略带提过。
====
不能直接创建 FileChannel 对象,后3种可以通过调用相应里地类方法open进行创建Channel对象。FileChannel对象却只能通过在一个打开的RandomAccessFile、FileInputStream或FileOutputStream对象上调用getChannel( )方法来获取。
Channels 提供了将流和通道之间的互相转换API。
====
最主要原因是文件IO延迟较少,详细见下分析:
非阻塞模式的通道永远不会让调用的线程休眠。请求的操作要么立即完成,要么返回一个结果表明未进行任何操作。只有面向流的(stream-oriented)的通道,如sockets和pipes才能使用非阻塞模式。将非阻塞I/O和选择器组合起来可以使您的程序利用多路复用I/O(multiplexed I/O)。
文件通道总是阻塞式的,因此不能被置于非阻塞模式。现代操作系统都有复杂的缓存和预取机制,使得本地磁盘I/O操作延迟很少。网络文件系统一般而言延迟会多些,不过却也因该优化而受益。
面向流的I/O的非阻塞范例对于面向文件的操作并无多大意义,这是由文件I/O本质上的不同性质造成的。对于文件I/O,最强大之处在于异步I/O(asynchronous I/O),它允许一个进程可以从操作系统请求一个或多个I/O操作而不必等待这些操作的完成。发起请求的进程之后会收到它请求的I/O操作已完成的通知。异步I/O是一种高级性能,当前的很多操作系统都还不具备。以后的NIO增强也会把异步I/O纳入考虑范围。
====
force(boolean metaData):该方法将数据强制刷新到本次存储设备上,这对确保在系统崩溃时不会丢失重要信息特别有用。metaData 参数可用于限制此方法必需执行的 I/O 操作数量。为此参数传入 false 指示只需将对文件内容的更新写入存储设备;传入 true 则指示必须写入对文件内容和元数据的更新,这通常需要一个以上的 I/O 操作。此参数是否实际有效取决于底层操作系统,因此是未指定的。
map(MapMode, long, long):将此通道的文件区域直接映射到内存中,并返回MappedByteBuffer(通常是DirectByteBuffer)。仅建议在读写相对较大的文件时使用此方法,读写小文件时建议使用read/write方法。可以调用DirectByteBuffer的接口方法clean来及时回收内存。
transferTo(long, long, WritableByteChannel):该方法内部使用了zero-copy机制,避免了不必要的上下文切换开销和缓存区数据复制开销,能够极大的提高传输文件能力。详见参考链接。
====
http://ifeve.com/java-nio-all/
http://blog.csdn.net/historyasamirror/article/details/5778378
http://www.ibm.com/developerworks/cn/java/j-lo-javaio/
http://blog.csdn.net/tabactivity/article/details/9317143
Java NIO,概述部分总结的相当精彩,后面有些章节翻译的比较粗糙.
http://www.ibm.com/developerworks/cn/java/j-zerocopy/,通过零拷贝实现有效数据传输