目录
前言
前文我们已经了解了nio的三大核心组件,本篇文章会详细介绍filechannel中的使用方法和原理。
filechannel
filechannel是对一个文件读,写,映射,操作的channel。filechannel是线程安全的,可以被多个线程并发使用。同一个进程中的多个filechannel看到的同一个文件的视图是相同的,由于底层操作系统执行的缓存和网络文件系统协议引起的延迟,不同进程中在同一时间看到的文件视图可能会不同。
filechannel的类图如下,filechannel是一个抽象类,它的具体实现是filechannelimpl。
filechannel的创建
获取filechannel的方式有下面四种
- filechannel.open()
直接调用filechannel的open()方法,传入path即可获得filechannel
// 直接传入path默认是只读filechannel filechannel filechannel = filechannel.open(path.of("./tmp/linshifu.txt")); // 和直接传入path相比,支持传入openoption数组 filechannel channel = filechannel.open(path.of("./tmp/linshifu.txt"), standardopenoption.write);
openoption是一个空接口,我们可以传入standardopenoption枚举,standardopenoption有如下值
public enum standardopenoption implements openoption { // 可读channel read, // 可写channel write, // 如果channel是可写(write)的,缓冲中的数据会从文件末尾开始写,而不是从头开始写 append, // 如果channel是可写(write)的,文件的长度会被置为0 truncate_existing, // 如果文件不存在,则会创建一个新的文件,如果配置了create,则create_new会失效 create, // 创建换一个新的文件,如果文件已经存在,则会失败 create_new, // channel关闭时删除 delete_on_close, // 稀疏文件 sparse, // 要求对文件内容或元数据的每次更新都同步写入基础存储设备。 sync, // 要求对文件内容的每次更新都同步写入基础存储设备。 dsync; }
- fileinputstream.getchannel()
通过fileinputstream的getchannel()方法获取filechannel
fileinputstream fileinputstream = new fileinputstream("./tmp/linshifu.txt"); filechannel filechannel = fileinputstream.getchannel();
fileinputstream创建的filechannel不可写,只能读
- fileoutputstream.getchannel()
通过fileoutputstream的getchannel()方法获取filechannel
fileoutputstream fileinputstream = new fileoutputstream("./tmp/linshifu.txt"); filechannel filechannel = fileinputstream.getchannel();
fileoutputstream创建filechannel不可读,只能写
- randomaccessfile.getchannel()
通过randomaccessfile的getchannel()方法获取filechannel
randomaccessfile file = new randomaccessfile("./tmp/linshifu.txt", "rw"); filechannel filechannel = file.getchannel();
randomaccessfile中的模式
与outputstream和inputstream不同的是创建randomaccessfile需要传入模式,randomaccessfile的模式也会影响到filechannel,创建randomaccessfile可以传入的模式有下面4种
r
只读模式,创建的randomaccessfile只能读,如果使用只读的randomaccessfile创建的filechannel写数据会抛出nonwritablechannelexception
rw
读写模式,创建的randomaccessfile即可读,也可写
rws
与rw
一样,打开以进行读取和写入,并且还要求对文件内容或元数据的每次更新同步写入基础存储设备
rwd
与rw
一样,打开以进行读取和写入,并且还要求对文件内容的每次更新都同步写入底层存储设备
filechannel操作文件
读文件操作
读文件的方法有如下三个
// 从position位置读取bytebuffer.capacity个byte,channel的position向后移动capacity个位置 public abstract int read(bytebuffer dst) throws ioexception; // 从传入的position开始读取bytebuffer.capacity个byte,channel的positon位置不变 public abstract int read(bytebuffer dst, long position) throws ioexception; // 按顺序读取文件到bytebuffer数组中,会将数据读取到bytebuffer数组的[offset,offset length)的bytebuf子数组中 public abstract long read(bytebuffer[] dsts, int offset, int length) throws ioexception;
我们准备一个
写一个demo
写文件操作
// 从position开始写入bytebuffer中的数据 public abstract int write(bytebuffer src) throws ioexception; // 从position开始将bytebuffer数组的[offset,offset length)的bytebuf子数组中的数据写入文件 public abstract long write(bytebuffer[] srcs, int offset, int length) throws ioexception; // 从position开始将bytebuffer数组中的数据写入文件 public final long write(bytebuffer[] srcs) throws ioexception { return write(srcs, 0, srcs.length); }
写一个demo
对文件的更新强制输出到底层存储设备
这种方式可以确保在系统崩缺时不会丢失数据,参数中的boolean表示刷盘时是否将文件元数据也同时写到磁盘上。
public abstract void force(boolean metadata) throws ioexception;
通道之间数据传输
如果需要将filechannel的数据快速传输到另一个channel,可以使用transferto
和transfrom
// 将字节从此通道的文件传输到给定的可写字节通道 public abstract long transferto(long position, long count,writablebytechannel target) throws ioexception; // 将字节从给定的可读字节通道传输到此通道的文件中 public abstract long transferfrom(readablebytechannel src,long position, long count) throws ioexception;
通常情况下我们要将一个通道的数据传到另一个通道。例如,从一个文件读取数据通过socket通道进行发送。比如通过http协议读取服务器上的一个静态文件,要经过下面几个阶段
- 将文件从硬盘拷贝到页缓冲区
- 从页缓冲区读拷贝到用户缓冲区
- 用户缓冲区的数据拷贝到socket内核缓冲区,最终再将socket内核缓冲区的数据拷贝到网卡中
当我们通过transferto
或者transferfrom
在通道之间传输数据时,如果内核支持,则会使用零拷贝的方式传输数据
零拷贝技术可以避免将数据拷贝到用户空间中
mappedbytebuffer
mappedbytebuffer是nio中应对的操作大文件的方式,它的读写性能极高,它是一种基于mmap的零拷贝方案,通常情况下可以映射出整个文件,如果文件比较大,也支持分段映射。这其初听起来似乎不过就是将整个文件读到内存中,但是事实上并不是这样。 一般来说,只有文件中实际读取或者写入的部分才会映射到内存中。
mmap通过内存映射,将文件映射到内核缓冲区,同时,用户空间可以共享内核空间的数据。这样在进行网络传输时,就可以减少内核空间到用户空间的拷贝次数。mmap需要4次上下文切换,3次数据拷贝。
mappedbytebuffer使用的是虚拟内存,因此分配(map)的内存大小不受jvm的-xmx
参数限制。
mappedbytebuffer使用的方式也比较简单,首先我们准备一个文件,文件内容如下所示,文件大小34b
我们利用mappedbytebuffer写一段代码,将上面文件的第一个字符改成a
,代码如下
public static void main(string[] args) throws exception { randomaccessfile file = new randomaccessfile("./tmp/01.txt", "rw"); mappedbytebuffer mbf = file.getchannel().map(filechannel.mapmode.read_write, 0, 1024); mbf.put(0, (byte) 'a'); file.close(); }
执行完上面代码之后,这个文件的第一个字符确实被改成了a
,但是在文字的末尾也多了很多奇怪的符号
再次查看文件,发现文件大小变为了1kb,我们在进行文件映射时应当注意文件的position和size不应当超出文件的范围,否则可能导致"文件空洞",磁盘上物理文件中写入数据间产生间隙。
以上就是nio深入理解filechannel的详细内容,更多关于nio深入理解filechannel的资料请关注其它相关文章!