java中的内存映射的缺点_Java> Java核心卷读书笔记 - 内存映射文件

简介

内存映射文件是操作系统利用内存,来实现将一个文件或者文件的一部分“映射”到内存中的文件。内存映射文件可当做数组访问,速度比传统文件访问快。

内存映射文件有何意义?

下图是一组测试数据,测试内容是对JDK的jre/lib中37MB rt.jar计算校验和CRC32所需时间。

6ad9addb5daa987992127f83c5a1c832.png

可以明显看到,内存映射文件比随机访问RandomAccessFile要快很多,不过对比带缓冲的输入流优势不是很明显。

如何进行映射?

从文件中获得一个通道(channel),通道是用于磁盘文件的一种抽象,使我们可以访问诸如内存映射、文件加锁机制以及文件间快速数据传递等操作系统特性。

FileChannel channel = FileChannel.open(path, options);

// option 也可以缺省, 因为对缓冲区的读写由映射到缓冲区时传入FileChannel.MapMode参数决定

FileChannel channel = FileChannel.open(Paths.get("rss", "test.txt", StandardOpenOption.READ));

通过FileChannel类的map方法,从这个通道获取一个MappedByteBuffer,可以想要映射的文件区域(起始位置、元素个数)和映射模式。

支持的映射模式

映射模式

描述

备注

FileChannel.MapMode.READ_ONLY

所产生的缓冲区只读,任何写缓冲区操作将导致ReadOnlyBufferException

FileChannel.MapMode.READ_WRITE

所产生的缓冲区可读写,任何修改都会在某个时刻写回文件中

多个程序同时进行文件映射的确切行为依赖于操作系统

FileChannel.MapMode.PRIVATE

多产生的缓冲器可读写,不过修改是私有的,不会影响到文件

MappedByteBuffer buffer = channel.map(FileChannel.MapMode.READ_ONLY, 0, (int)channel.size());

通过MappedByteBuffer类的buffer对象,读写数据

注意:MappedByteBuffer缓冲区支持顺序访问和随机访问。

访问内存映射文件缓冲区

通过MappedByteBuffer读写缓冲区

顺序访问示例

while(buffer.hasRemaining()) {

byte b = buffer.get();

...

}

随机访问示例

for(int i = 0; i < buffer.limit(); i ++) {

byte b = buffer.get(i);

...

}

示例

示例代码

比较InputStream(普通输入流), BufferedInputStream(带缓冲的输入流), RandomAccessFile(随机访问文件),MappedByteBuffer(内存映射文件)这几种方式进行文件校验和CRC32计算时间。

// InputStream 计算校验和

public static long checksumInputStream(Path fileName) throws IOException{

try (InputStream in = Files.newInputStream(fileName)) {

CRC32 crc = new CRC32();

int c;

while ((c = in.read()) != -1) {

crc.update(c);

}

return crc.getValue();

}

}

// BufferedInputStream 计算校验和

public static long checksumBufferedInputStream(Path filename) throws IOException {

try(InputStream in = new BufferedInputStream(Files.newInputStream(filename))) {

CRC32 crc = new CRC32();

int c;

while ((c = in.read()) != -1) {

crc.update(c);

}

return crc.getValue();

}

}

// RandomAccessFile 计算校验和

public static long checksumRandomAccessFile(Path filename) throws IOException {

try (RandomAccessFile file = new RandomAccessFile(filename.toFile(), "r")) {

long length = file.length();

CRC32 crc = new CRC32();

for (int i = 0; i < length; i++) {

file.seek(i);

int c = file.readByte();

crc.update(c);

}

return crc.getValue();

}

}

// MappedByteBuffer 计算校验和

public static long checksumMappedFile(Path filename) throws IOException {

try(FileChannel channel = FileChannel.open(filename)) {

CRC32 crc = new CRC32();

int length = (int)channel.size();

MappedByteBuffer buffer = channel.map(FileChannel.MapMode.READ_ONLY, 0, length);

for (int i = 0; i < length; i++) {

int c = buffer.get(i);

crc.update(c);

}

return crc.getValue();

}

}

public static void main(String[] args) throws IOException {

System.out.println("Input Stream:");

long start = System.currentTimeMillis();

Path filename = Paths.get("rss", "rt.jar");

long crcValue = checksumInputStream(filename);

long end = System.currentTimeMillis();

System.out.println(Long.toHexString(crcValue));

System.out.println((end - start) + "ms");

System.out.println("Buffer Stream:");

start = System.currentTimeMillis();

crcValue = checksumBufferedInputStream(filename);

end = System.currentTimeMillis();

System.out.println(Long.toHexString(crcValue));

System.out.println((end - start) + "ms");

System.out.println("Random Access File:");

start = System.currentTimeMillis();

crcValue = checksumRandomAccessFile(filename);

end = System.currentTimeMillis();

System.out.println(Long.toHexString(crcValue));

System.out.println((end - start) + "ms");

System.out.println("Mapped File:");

start = System.currentTimeMillis();

crcValue = checksumMappedFile(filename);

end = System.currentTimeMillis();

System.out.println(Long.toHexString(crcValue));

System.out.println((end - start) + "ms");

}

示例运行结果

结果如下,可以看到运行与开始性能对比截图一致。 这也暗示着,对于追求处理速度的大文件,不建议使用InputStream和RandomAccessFile, 建议使用MappedByteBuffer或者BufferedInputStream。

Input Stream:

d6b12853

158067ms

Buffer Stream:

d6b12853

385ms

Random Access File:

d6b12853

181429ms

Mapped File:

d6b12853

203ms

文件加锁

锁定文件

使用FileChannel.lock()或者FileChannel.tryLock()。文件锁定后,将保持锁定, 直到通道关闭或者锁上调用了release()方法

FileChannel channel = FileChannel.open(path);

// 加锁方式1

FileLock lock = channel.lock(); // 会阻塞直至可获得锁

// 加锁方式2

FileLock lock = channel.tryLock(); // 立即返回, 要么返回锁, 要么不可获得锁时返回null.

锁定文件的一部分

/**

* shared: false表示这是独占锁, 锁定文件的目的是读写; true表示这是一个共享锁, 允许多个进程从文件读入, 并阻止任何进程获得独占的锁. 不是所有操作系统都支持共享锁

*/

FileLock lock(long start, long size, boolean shared)

// or

FileLock tryLock(long start, long size, boolean shared)

// 查询支持的锁类型

FileLock.isShared();

如果锁定尾部,而文件后来长度增长超过锁定部分,那么增长出来的区域是未锁定的,要想锁住所有字节,使用Long.MAX_VALUE来表示尺寸。

例如,

FileLock lock = channel.lock(0, Long.MAX_VALUE, true);

释放锁

确保操作完成时释放锁,最好使用try语句

try (FileLock lock = channel.lock()) {

access the locked file segment

}

文件加锁机制依赖于操作系统,需要注意:

某些系统中,文件加锁仅仅是建议。如果一个应用未得到锁,仍可以向被另一个应用并发锁定的文件执行写操作;

某些系统中,不能锁定一个文件的同时,将其映射到内存中;

文件锁是由整个Java虚拟机持有。如果2个程序是由同一个虚拟机启动,那么它们不可能每个都同时获得同一个文件上的锁。如果虚拟机已经在同一个文件上持有了另一个重叠的锁,那么这2个方法将抛出OverlapingFileLockException;

在一些系统中,关闭一个通道会释放由Java虚拟机持有的底层文件的所有锁。因此,同一个锁定文件上,应避免使用多个通道;

在网络文件系统上锁定文件是高度依赖于系统的,应尽量避免;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值