Java-IO流(三)-NIO
在前面所介绍的输入输出流都是阻塞式的输入、输出,即当数据源中没有数据时,它会阻塞该线程。传统的输入、输出都是通过字节的移动来处理的,就是输入输出系统一次只能处理一个字节,因此效率并不高。从JDK1.4开始,Java改进了IO流体系,提供来一些新功能,被称作NIO
。新增的功能类被放在java.nio
包及子包下,并且对原java.io
包中的很多类都以NIO
为基础进行改写,新增满足NIO
功能。
NIO
采用不同的方式来处理输入/输出,用内存映射文件的方式来处理输入/输出,NIO
将文件或文件的一段区域映射到内存中,这样就可以像访问内存一样访问文件,这种方式比传统的方式快。它有如下包:
java.nio
包:主要各种与Buffer
相关的类java.nio.channels
包:主要包含与Channel
和Selector
相关的类java.nio.charset
包:字符集相关类java.nio.channels.spi
:与Channel
相关的服务接口java.nio.charset.spi
:包含与字符集相关的服务接口
Channel
(通道)和Buffer
(缓冲)是新IO中两个核心对象,CHannel
是对传统的输入/输出的模拟,在NIO
中所有数据都需要通过通道传输;Channel
与传统的InputStream
、OutputStream
区别在于它提供一个map()
方法,通过该map()
方法可以直接将一块数据
映射到内存中。
Buffer
可以被理解成一个容器,它本质是一个数组,发送到Channel
中都所有对象都必须首先放在Buffer
中,而从Channel
中读取的数据也必须放到Buffer
中。
除Channel
和Buffer
外,NIO
还提供了将Unicode
字符串映射成字节序列以及逆映射操作的Channel
类,也提供了非阻塞式输入/输出的Selector
类。
使用Buffer
Buffer
是一个抽象类,其最常用的子类是ByteBuffer
,它可以在底层字节数组上进行get/set
操作。对应其他类型的Buffer
类有:CharBuffer
、ShortBuffer
、IntBuffer
、LongBuffer
、FloatBuffer
等。以上类都没有提供构造器,而是提供static XxxBuffer allocate(int capacity)
方法,创建一个容量为capacity
的XxxBuffer
对象。
MappedByteBuffer
是ByteBuffer
子类,用于表示Channel
将磁盘文件的部分或全部内容映射到内存中后得到的结果,相应对象由Channel
的map()
方法返回。
在Buffer
中有三个重要的概念:容量(Capacity)、界限(limit)、位置(position)
- 容量(capacity):缓冲区的容量(capacity)表示最大数据容量,创建后不能改变。
- 界限(limit):第一个不能被读写的缓冲区位置索引。
- 位置(position):指明下一个可以被读出或写入的缓冲区位置索引,类似记录指针。
Buffer
还有一个可选标记mark
,允许直接将position
定位到该mark
处,并满足:
0<=mark<=position<=limit<=capacity
Buffer
的主要作用就是装入数据,然后输出数据。开始时,position
为0,limit
为capacity
,程序可以通过put()
方法向Buffer
中放入一些数据,每放入一些数据,Buffer的position相应的向后移动一些。当Buffer
装入数据结束后,调用Buffer
的flip()
方法,该方法将limit
设置为position
所在位置,并将position
设为0,为输出作准备。输出数据后,Buffer
调用clear()
方法,将position设为0,将limit
设为capacity
,这样为装数据做准备。
总结来讲:就是
flip()
方法为取出数据做好准备,clear()
方法为装数据做好准备。此外Buffer
的常用方法还有capacity()
、limit()
、hasRemaining()
等。
除了有移动position
、limit
、mark
的方法外,Buffer
的所有子类还提供了两个重要的方法:put()
和get()
方法,用于向Buffer
中放入和取出数据,可以单个也可以批量。当用这两个方法访问数据时,分为相对
和绝对
两种:
- 相对(relative):从
Buffer
的当前Position
处开始读取或写入数据,然后Position
值按处理元素个数增加。 - 绝对(Absolute):直接根据索引向
Buffer
中读取或写入数据,使用绝对方式访问Buffer
的数据,position
值不变。
通过allocate()
方法创建的对象是普通的Buffer
对象,ByteBuffer
还提供一个allocateDirect()
方法来创建直接Buffer
,成本会比普通Buffer
创建·成本高,但好处是读取效率更高。
由于只有ByteBuffer
提供了allocateDirect()
方法,所以只能在ByteBuffer
级别上创建直接Buffer
。如果需要使用其他类型,则应该将该Buffer
转换成其他类型Buffer
。直接Buffer
更适于长期生存的Buffer
,因为创建成本较高。
使用Channel
Channel
类似于传统的流对象,但还是有区别。Channel
可以直接将指定文件的部分或全部直接映射成Buffer
;程序不能直接访问Channel
中的数据,读取、写入都不行;Channel
只能和Buffer
进行交互,也就是说程序要取出数据要通过Buffer
,写入数据要还要通过Buffer
。
Java为Channel
接口提供了DatagramChannel
、FileChannel
、Pipe.SinkChannel
、ServerSocketChannel
、SocketChannel
等实现类,并且有各自相应的功能。所有的Channel
都不应该通过构造器来直接创建,而是通过传统的节点InputStream
、OutputStream
的getChannel()
方法来返回对应的channel
,不同的节点流获得的Channel
不一样。
Channel
中最常用的方法是map()
、read()
、write()
,其中map()
方法用于将Channel
对应的部分或全部数据映射成ByteBuffer
;而read()
或write()
方法都有一系列重载形式,这些方法用于从Buffer
中读取或写入数据。
map(FileChannel.MapMode mode,long position,long size)
,映射模式有只读、读写等,第二三个参数用于映射数据的范围。在RandomAccessFile
中也包含了一个getChannel()
方法,RandomAccessFile
返回的FileChannel()
是只读的还是读写的,取决RandomAccessFile
打开文件的模式。
字符集和charset
数据是以字节码的形式储存的,明文字符序列经过编码成二进制序列。Java默认使用Unicode
字符集,当读取数据到java程序时,就可能出现乱码。JDK1.4提供了Charset来处理字节序列和字符序列之间的转换关系,该类包含了用于创建解码器和编码器的方法,还提供了获取Charset
所支持字符集方法,Charset
类是不可变的。Charset
类还提供了一个availableCharsets()
静态方法来获取当前JDK所支持的所有字符集。
- GBK:简体中文
- BIG5:繁体中文
- ISO-8859-1:ISO拉丁字母表
- UTF-8:8位UCS转换格式
- UTF-16BE:16位UCS转换格式,地位地址放高位字节
- UTF-16:16位UCS转换格式
可以使用System
的getProperties()
方法访问本地系统的文件编码格式,属性名为file.encoding
。
一旦知道字符集别名,就可以调用Charset
的forName()
方法来创建对应的Charset
对象,forName()
的参数是相应字符集的别名。通过对象的newDecoder()
和newEncoder()
方法分别返回CharsetDecoder
和CharsetEncoder
对象,代表解码器和编码器,将字符和字节序列相互转换。
文件锁
使用文件锁可以阻止多个进程同时修改一个文件,在NIO中java提供了FileLock
来支持文件锁定功能,在FieChannel
中提供的lock()/tryLock()
方法可以获得文件锁FileLock
对象,从而锁定文件。当lock()
试图锁定某个文件时,如果无法得到文件锁,程序将一直阻塞;而tryLock()
是直接返回文件锁,否则返回null
。
lock(long position,long size,boolean shared)
:对文件从position
位置开始,长度为size
的内容加锁。tryLock(long position,long size,boolean shared)
:非阻塞式的加锁方法,参数与上面类似。
当shared
为true时,表明是一个共享锁,它允许多个进程读取该文件,阻止进程获取该文件的排它锁。当为false时,表明是一个排它锁,可提高FileLock
的isShared()
来判断。注意:直接使用上述两个方法获取的都是排它锁
,处理完文件后通过FileLock
的release()
方法释放文件锁。
NIO.2
Java7对原有的NIO
进行了改进,主要有:
- 提供来全面的文件IO和文件系统访问支持
- 基于异步的
Channel
的IO
第一个表现为新增的java.nio.file
包及各个子包;第二个表现为在java.nio.channels
包下增加多个以Asynchronous
开头的Channel
接口和类。
早期只能通过File
类来访问文件系统,现在引入一个path
接口,代表一个与平台无关的平台路径。除此之外,还提供了Files
、Paths
两个工具类,Files
包含了静态的工具方法;Paths
包含了两个返回path
的静态工厂方法。命名都加上s,代表一个工具类。
使用FileVisitor
遍历文件和目录
Files
类提供了如下方法来遍历文件和子目录:
walkFileTree(Path start,FileVisitor<? super Path> visitor)
:遍历start路径下的所以文件和子目录walkFileTree(Path start,Set<FileVisitOption> options,int maxDepth,FileVisitor<? super Path> visitor)
:该方法最多遍历maxDepth
深度的文件
使用WatchService
监控文件变化
早期是通过一个后台线程每隔一段时间去遍历指定文件目录,如果结果与上一次不同,则发生变化。NIO
的Path
类提供来一个方法来监听文件变化。
register(WatchService watcher,WatchEvent.kind<?> ...events)
:用watcher监听该path
代表的目录下的文件变化,events
参数指定要监听那些事件。
获取文件系统的WatchService
对象:
1 | WatchService watchservice = FileSystem.getDefault().newWatchService; |
接下来使用WatchService
的方法获取文件事件:
WatchKey poll()
:获取下一个watchkey
,没有返回null。WatchKey poll(long timeout,TimeUnit unit)
:尝试等待timeout
时间去获取下一个WatchKey
。WatchKey take()
:获取下一个watchkey
,没有就一直等。
公众号:菜鸡干Java