• Java> Java核心卷读书笔记


    简介

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

    内存映射文件有何意义?
    下图是一组测试数据,测试内容是对JDK的jre/lib中37MB rt.jar计算校验和CRC32所需时间。

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

    如何进行映射?

    1. 从文件中获得一个通道(channel),通道是用于磁盘文件的一种抽象,使我们可以访问诸如内存映射、文件加锁机制以及文件间快速数据传递等操作系统特性。
    FileChannel channel = FileChannel.open(path, options);
    
    // option 也可以缺省, 因为对缓冲区的读写由映射到缓冲区时传入FileChannel.MapMode参数决定
    FileChannel channel = FileChannel.open(Paths.get("rss", "test.txt", StandardOpenOption.READ)); 
    
    1. 通过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());
    
    1. 通过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
    }
    

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

    1. 某些系统中,文件加锁仅仅是建议。如果一个应用未得到锁,仍可以向被另一个应用并发锁定的文件执行写操作;
    2. 某些系统中,不能锁定一个文件的同时,将其映射到内存中;
    3. 文件锁是由整个Java虚拟机持有。如果2个程序是由同一个虚拟机启动,那么它们不可能每个都同时获得同一个文件上的锁。如果虚拟机已经在同一个文件上持有了另一个重叠的锁,那么这2个方法将抛出OverlapingFileLockException;
    4. 在一些系统中,关闭一个通道会释放由Java虚拟机持有的底层文件的所有锁。因此,同一个锁定文件上,应避免使用多个通道;
    5. 在网络文件系统上锁定文件是高度依赖于系统的,应尽量避免;
  • 相关阅读:
    javascript 常见的面试题---数组 && 算法
    JavaScript内置一些方法的实现原理--new关键字,call/apply/bind方法--实现
    javascript 数组排序原理的简单理解
    随笔2
    移动端触摸事件
    前端开发模式的思考层面
    webpack & react项目搭建一:环境
    Webpack的学习
    《Soft Skills: the software developer's life manual》
    前端路由实现
  • 原文地址:https://www.cnblogs.com/fortunely/p/14053458.html
Copyright © 2020-2023  润新知