java内存映射文件(mmap)

Posted by Xsp on October 21, 2018

《think in java》18.10.6

内存映射文件

内存映射文件允许我们创建和修改那些因为太大而不能放入内存的文件。有了内存映射文件,我们就可以假定整个文件都在内存中,可以把它当做非常大的数组来访问。这种方法极大简化用于修改文件的代码。

很大的文件(可达2G, Integer.MAX_VALUE)也可以很容易地修改。

为什么是2G?

public class LargeMappedFiles {
    static int length = 0x8FFFFFF; // 128 MB

    public static void main(String[] args) throws Exception {
        // RandomAccessFile 既能读也能写
        // map() 方法产生 MappedByteBuffer,特殊类型的直接缓冲器。
        // 必须指定映射文件的初始位置和映射区域的大小,意味着可以映射某个大文件的较小的部分。
        MappedByteBuffer out =
                new RandomAccessFile("test.dat", "rw").getChannel()
                        .map(FileChannel.MapMode.READ_WRITE, 0, length);
        for (int i = 0; i < length; i++) {
            out.put((byte) 'x');
        }
        System.out.println("Finished writing");
        for (int i = length / 2; i < length / 2 + 6; i++) {
            System.out.println((char) out.get(i));
        }
    }
}

文件加锁

上述映射文件一般用于极大的文件,对这种巨大的文件进行部分加锁,可以让其他线程修改文件中未被加锁的部分。

public class LockingMappedFiles {
    static final int LENGTH = 0x8FFFFFF; // 128 MB
    static FileChannel fc;

    public static void main(String[] args) throws Exception {
        fc = new RandomAccessFile("test.dat", "rw").getChannel();

        MappedByteBuffer out = fc.map(FileChannel.MapMode.READ_WRITE, 0, LENGTH);

        for (int i = 0; i < LENGTH; i++) {
            out.put((byte) 'x');
        }
        new LockAndModify(out, 0, 0 + LENGTH / 3);
        new LockAndModify(out, 0, 0 + LENGTH / 3);
        new LockAndModify(out, LENGTH / 2, LENGTH / 2 + LENGTH / 4);
    }

    private static class LockAndModify extends Thread {
        private ByteBuffer buff;
        private int start, end;

        LockAndModify(ByteBuffer mbb, int start, int end) {
            this.start = start;
            this.end = end;
            mbb.limit(end);
            mbb.position(start);
            buff = mbb.slice();
            start();
        }

        @Override
        public void run() {

            Thread curr = Thread.currentThread();

            FileLock lock = null;

            try {
                while (true) {
                    try {
                        // lock = fc.lock();
                        lock = fc.lock(start, end, false);
                        break;
                    } catch (OverlappingFileLockException e) {
                        Thread.sleep(1 * 1000);
                    }
                }

                System.out.println("Locked: " + start + " to " + end);
                // Perform modification:
                while (buff.position() < buff.limit() - 1) {
                    buff.put((byte) (buff.get() + 1));
                }
                lock.release();
                System.out.println("Released: " + start + " to " + end);

            }  catch (IOException | InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

调用 lock 对文件加锁时,如果该部分已经加锁,会抛出异常 OverlappingFileLockException,所以我们可以循环等待一段时间再去获取锁。

lock 和tryLock这里感觉不到差异啊?