NIO基础入门(三)之文件锁定与内存映射文件

[TOC]

1.概括

文件锁定:JDK1.4之前,Java I/O模型未能提供文件锁定,1.4以后发布

作用:集成许多其他非java程序时,需要使用文件锁定,他除了锁定外还可以判优(判断多个访问请求的优先级)一个大系统的多个java组件访问顺序.

内存映射文件:FileChannel类中提供一个map()的方法,该方法可以在一个打开的文件和一个特殊类型的ByteBuffer之间建立一个虚拟内存映射.

即:在FileChannel上调用map()方法,会创建一个由磁盘文件支持的虚拟内存映射(virtual memory mapping) ,并在这块内存空间外部封装一个MappedByteBuffer对象

内存映射文件不需要将数据Copy到内核空间,直接读写,速度更快

性能对比:本地磁盘文件复制,17MB文本为例,内存映射大概20-90毫秒,而通道耗时40-100毫秒(测试通道大小设置为略大于文本长度的最佳效率,如果指定缓存池为1024,则速度更慢400-900毫秒,频繁复制清空缓冲区导致)

2.文件锁定

2-1.概念

文件锁定的锁可以是共享锁(Shared),也可以是独占锁(exclusive)

文件锁定特性很大程度上依赖本地操作系统实现,并非所有操作系统都支持共享锁,

但是不支持共享锁的系统,会被自动提升为独占锁,因此大多数情况使用共享锁即可(交换机也可以单槽向多槽自动提升)

文件锁定是与文件本身关联,并非单个文件句柄或者通道

文件锁定判优:只对进程级别判优,对线程级别无效

注意:文件锁定属于进程级别锁定,JVM内部无法锁定(锁定的是文件)

即:

JVM中一个线程使用独占锁,JVM内部另一个线程也能访问

如果此时JVM外部访问该文件,则进入阻塞等待

2-2.方法

lock()

1
2
3
public final FileLock lock() throws IOException {
return lock(0L, Long.MAX_VALUE, false);
}

不带参数默认锁定整个文件,也可以指定大小锁定

第一个参数:指定锁定起点: position

第二个参数:指定size,锁定大小(锁定范围可以任意指定,可以比文件还要大,这样未有位置也会被锁定,未被锁定的范围则不受文件锁定保护)

第三个参数:是否开启Shared共享锁,true共享锁/false独占锁

trylock()非阻塞锁定,如果返回不及时,会返回null

lock衍生方法略…

3.内存映射文件

3-1.概念

如上所说,内存映射的方式效率有时候更高于通道(数据越大越明显)

不需要做明确的系统操作(那会消耗很大的时间)

系统操作的虚拟内存可以缓存内存页,这些页(虚拟内存)是系统内存来缓存的,不会消耗java内存堆

3-2.方法

1
public abstract MappedByteBuffer map(MapMode mode,long position, long size)throws IOException;

第一个参数MapMode:指定内存映射策略

1
2
3
MapMode READ_ONLY //只读,force操作无法在该策略下生效
MapMode READ_WRITE //读写
MapMode PRIVATE //私有副本拷贝页,所有操作不影响本地文件,修改的是内存副本,如果内存清空会丢失,force操作无法在该策略下生效

第二个参数position:映射文件的起始位置,0是从头开始,与lock参数相同意思

第三个参数size:cap,映射容量,与lock参数相同意思,通道.size 映射整个文件

没有unmap()方法

也就是说,一个映射一旦建立之后将保持有效,直到MappedByteBuffer对象被垃圾回收为止.

注意:同锁不一样的是,内存映射并没有绑定在通道上,关闭通道也不会对内存映射造成破坏,只有丢弃缓冲区本身才会破坏映射

内存映射可以和Gather/Scatter结合.SocketChannel之类通道的read()和write()方法也可以有效的进行传递(具备ByteBuffer的功能)

load()方法

将磁盘数据读取到内存,有的操作系统可能不会自动读取到内存(win7可以自动读取),因此可以使用load方法

过程:

类似打开一个文件:文件先被定位,然后创建一个文件句柄,准备好之后就可以通过这个句柄来访问文件数据,虚拟内存系统将根据指定需要,将文件中相应区域的数据读进来,因为将文件读到内存中也需要一次或多次,如果将所有数据读到内存中,那么相当于文件的所有页是常住内存,那么他的访问速度就和基于内存的缓冲区一样了

3-4.示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
long start = System.currentTimeMillis();
File src = new File("d:\\testdd.txt");
File target = new File("d:\\testddTarget.txt");

RandomAccessFile readRaf = new RandomAccessFile(src, "rw");
RandomAccessFile writeRaf = new RandomAccessFile(target, "rw");

FileChannel readChannel = readRaf.getChannel();
FileChannel writeChannel = writeRaf.getChannel();

MappedByteBuffer map = readChannel.map(MapMode.READ_WRITE, 0, readChannel.size()); //内存映射整个文件

writeChannel.write(map);
readChannel.close();
writeChannel.close();
readRaf.close();
writeRaf.close();
long end = System.currentTimeMillis();
System.out.println(end-start); //28-90