JAVA I/O(三)内存映射文件

《Java编程思想》中对内存映射文件有详细的介绍,此处仅做简单记录和总结。内存映射文件允许创建和修改因为太大而不能放入内存的文件。

1. 内存映射文件简单实例

import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;

public class LargeMappedFiles {

    private static int LENGTH = 0x0000FFF;

    public static void main(String[] args) throws IOException{
        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‘);
        }

        for(int i = LENGTH/2; i < LENGTH/2 + 6; i++) {
            System.out.print((char)out.get(i));
        }
    }
}

输出:

xxxxxx
  • 通过RandomAccessFile类获取FileChannel,使其具备读写功能。
  • 通过FileChannel的map方法,获取MappedByteBuffer,该方法包含三个参数,MapMode映射类型、开始位置、映射总数量,意味着可以映射大文件的较小部分。
  • MappedByteBuffer是一个特殊的直接缓冲器,对该缓冲器的修改会反映到对应文件中;另外,其继承ByteBuffer,具有ByteBuffer的所有方法。

本例中首先创建MappedByteBuffer,并设置为读写模式;然后往缓冲器中写入字符x;最后在文件中间开始读取6个字符。

2. 内存映射文件源码

以下是FileChannel.map()方法的解释:

 /**
     * Maps a region of this channel‘s file directly into memory.
     *
     * <p> A region of a file may be mapped into memory in one of three modes:
     * </p>
     *
     * <ul>
     *
     *   <li><p> <i>Read-only:</i> Any attempt to modify the resulting buffer
     *   will cause a {@link java.nio.ReadOnlyBufferException} to be thrown.
     *   ({@link MapMode#READ_ONLY MapMode.READ_ONLY}) </p></li>
     *
     *   <li><p> <i>Read/write:</i> Changes made to the resulting buffer will
     *   eventually be propagated to the file; they may or may not be made
     *   visible to other programs that have mapped the same file.  ({@link
     *   MapMode#READ_WRITE MapMode.READ_WRITE}) </p></li>
     *
     *   <li><p> <i>Private:</i> Changes made to the resulting buffer will not
     *   be propagated to the file and will not be visible to other programs
     *   that have mapped the same file; instead, they will cause private
     *   copies of the modified portions of the buffer to be created.  ({@link
     *   MapMode#PRIVATE MapMode.PRIVATE}) </p></li>
     *
     * </ul>
     *
     * <p> For a read-only mapping, this channel must have been opened for
     * reading; for a read/write or private mapping, this channel must have
     * been opened for both reading and writing.
     *
     * <p> The {@link MappedByteBuffer <i>mapped byte buffer</i>}
     * returned by this method will have a position of zero and a limit and
     * capacity of <tt>size</tt>; its mark will be undefined.  The buffer and
     * the mapping that it represents will remain valid until the buffer itself
     * is garbage-collected.
     *
     * <p> A mapping, once established, is not dependent upon the file channel
     * that was used to create it.  Closing the channel, in particular, has no
     * effect upon the validity of the mapping.
     *
     * <p> Many of the details of memory-mapped files are inherently dependent
     * upon the underlying operating system and are therefore unspecified.  The
     * behavior of this method when the requested region is not completely
     * contained within this channel‘s file is unspecified.  Whether changes
     * made to the content or size of the underlying file, by this program or
     * another, are propagated to the buffer is unspecified.  The rate at which
     * changes to the buffer are propagated to the file is unspecified.
     *
     * <p> For most operating systems, mapping a file into memory is more
     * expensive than reading or writing a few tens of kilobytes of data via
     * the usual {@link #read read} and {@link #write write} methods.  From the
     * standpoint of performance it is generally only worth mapping relatively
     * large files into memory.  </p>
     *
     * @param  mode
     *         One of the constants {@link MapMode#READ_ONLY READ_ONLY}, {@link
     *         MapMode#READ_WRITE READ_WRITE}, or {@link MapMode#PRIVATE
     *         PRIVATE} defined in the {@link MapMode} class, according to
     *         whether the file is to be mapped read-only, read/write, or
     *         privately (copy-on-write), respectively
     *
     * @param  position
     *         The position within the file at which the mapped region
     *         is to start; must be non-negative
     *
     * @param  size
     *         The size of the region to be mapped; must be non-negative and
     *         no greater than {@link java.lang.Integer#MAX_VALUE}
     *
     * @return  The mapped byte buffer
     *
     * @throws NonReadableChannelException
     *         If the <tt>mode</tt> is {@link MapMode#READ_ONLY READ_ONLY} but
     *         this channel was not opened for reading
     *
     * @throws NonWritableChannelException
     *         If the <tt>mode</tt> is {@link MapMode#READ_WRITE READ_WRITE} or
     *         {@link MapMode#PRIVATE PRIVATE} but this channel was not opened
     *         for both reading and writing
     *
     * @throws IllegalArgumentException
     *         If the preconditions on the parameters do not hold
     *
     * @throws IOException
     *         If some other I/O error occurs
     *
     * @see java.nio.channels.FileChannel.MapMode
     * @see java.nio.MappedByteBuffer
     */
    public abstract MappedByteBuffer map(MapMode mode,
                                         long position, long size)
        throws IOException;
  • 该方法直接将通道对应文件的一部分映射到内存,并返回MappedByteBuffer
  • 有3种模式:READ_ONLY(只读)、READ_WRITE(读写)、PRIVATE(私有,用于copy-on-write)
  • MappedByteBuffer一旦建立,就与创建它的通道无关,即通道关闭时,不影响该缓冲器
  • 内存映射需要依赖于底层操作系统;另外,对大部分操作系统,内存映射要比直接读写昂贵,故一般都映射较大的文件。
  • 该方法的参数包括读写模式(由FileChannel内部类MapMode定义,如下)、开始位置position、映射大小size
/**
     * A typesafe enumeration for file-mapping modes.
     *
     * @since 1.4
     *
     * @see java.nio.channels.FileChannel#map
     */
    public static class MapMode {

        /**
         * Mode for a read-only mapping.
         */
        public static final MapMode READ_ONLY
            = new MapMode("READ_ONLY");

        /**
         * Mode for a read/write mapping.
         */
        public static final MapMode READ_WRITE
            = new MapMode("READ_WRITE");

        /**
         * Mode for a private (copy-on-write) mapping.
         */
        public static final MapMode PRIVATE
            = new MapMode("PRIVATE");

        private final String name;

        private MapMode(String name) {
            this.name = name;
        }

        /**
         * Returns a string describing this file-mapping mode.
         *
         * @return  A descriptive string
         */
        public String toString() {
            return name;
        }

    }

3. 文件加锁

JDK1.4引入文件加锁机制,允许同步访问共享资源文件。文件锁对其他操作系统进程是可见的,因为Java的文件加锁直接映射到本地操作系统的加锁工具。

可以通过FileChannel的tryLock()和lock()方法获取整个文件的FileLock。接口如下,tryLock()是非阻塞的,如果不能获取锁,则返回null;lock()是阻塞的,一直等待文件锁。另外,SocketChannel、DatgramChannel、ServerSocketChannel不需要加锁,因为他们是从单进程实体继承而来;并且通常不会在两个进程间共享socket。

public abstract FileLock lock(long position, long size, boolean shared)
        throws IOException;

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

public abstract FileLock tryLock(long position, long size, boolean shared)
        throws IOException;

public final FileLock tryLock() throws IOException {
        return tryLock(0L, Long.MAX_VALUE, false);
    }

FileLock是对文件某区域进行标识的(A token representing a lock on a region of a file.),可以通过FileChannel和AsynchronousFileChannel的加锁方法创建,包含四个成员:

public abstract class FileLock implements AutoCloseable {

    private final Channel channel;
    private final long position;
    private final long size;
    private final boolean shared;

加锁区域由size-position决定,不会根据文件大小变化而变化。shared为共享锁和排它锁标识。

对映射文件的部分加锁

文件映射通常应用于极大的文件,对其一部分进行加锁,其他进程可以对其他部分文件进行操作。数据库就是这样,多个用户可以同时访问。下边用2个线程分别对文件不同部分加锁。

import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileChannel.MapMode;
import java.nio.channels.FileLock;

/**
 * 对映射文件加锁
 * 映射文件:MappedByteBuffer out = fc.map(MapMode.READ_WRITE, 0, LENGTH);
 * 加锁:FileLock fl = fc.lock(start, end, false);    fl.release();
 * @author bob
 *
 */
public class LockingMappedFIles {

    private static final int LENGTH = 0x0000FFF;//128M
    static FileChannel fc;

    public static void main(String[] args) throws IOException{
        //1.获取读写FileChannel
        fc = new RandomAccessFile("data.dat", "rw").getChannel();
        //2.根据FileChannel获取MappedByteBuffer,读写模式、全文件
        MappedByteBuffer out = fc.map(MapMode.READ_WRITE, 0, LENGTH);
        //3.写入字符  x
        for(int i = 0; i < LENGTH; i++) {
            out.put((byte)‘x‘);
        }
        //4.启动线程1,对文件的前1/3加锁,通过缓冲器操作文件
        Thread thread1 = new LockAndModify(out, 0, LENGTH/3);
        thread1.start();
        //5.启动线程2,对文件的后1/3加锁,通过缓冲器操作文件
        Thread thread2 = new LockAndModify(out, LENGTH*2/3, LENGTH);
        thread2.start();

    }

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

        public LockAndModify(ByteBuffer byteBuffer, int start, int end) {
            //记录加锁位置的起始位置
            this.start = start;
            this.end = end;
            /**
             * 1. 设置MappedByteBuffer的position和limit
             * 2. 调slice()方法,创建新ByteBuffer,映射原ByteBuffer;其position为0,limit为缓冲器容量
             *       由slice()方法创建的ByteBuffer是直接的、只可读的
             *       修改会映射到原ByteBuffer中
             * 3. 另外,limit 和 position不可颠倒顺序,否则position可能比limit大,报错
             */
            byteBuffer.limit(end);
            byteBuffer.position(start);
            this.byteBuffer = byteBuffer.slice();
        }

        public void run() {
            try {
                //加排它锁
                FileLock fl = fc.lock(start, end, false);
                System.out.println("Locked: " + start + " to " + end);
                //修改内容
                while(byteBuffer.position() < byteBuffer.limit()+1) {
                    byteBuffer.put(byteBuffer.position(), (byte)(byteBuffer.get()+1));
                }
                fl.release();
                System.out.println("release: " + start + " to " + end);
            } catch (Exception e) {
                // TODO: handle exception
            }
        }
    }
}

运行结果,文件data.bat中前1/3和后1/3字符变为y。

总结

1.内存文件映射主要用于极大文件的访问和操作,可提高性能;

2. 内存映射文件通过通道创建,可设置读写模式和限制映射区域;

3. 对文件某区域加锁可实现多线程或进程对共享资源文件不同区域并发修改;

4. MappedByteBuffer是一种特殊的直接缓冲器,对其修改会反映到文件中。

参考

《Java核心编程》

原文地址:https://www.cnblogs.com/shuimuzhushui/p/10322984.html

时间: 2024-11-07 12:45:22

JAVA I/O(三)内存映射文件的相关文章

Java NIO 应用 -- 使用内存映射文件实现进程间通信

一看到 Java NIO 的内存映射文件(MappedByteBuffer),让我立即就联想到 Windows 系统的内存映射文件.Windows 系统的内存映射文件能用来在多个进程间共享数据,即进程间的共享内存,是通过把同一块内存区域映射到不同进程的地址空间中,从而达到共享内存. Java NIO 的内存映射文件和 Windows 系统下的一样,都能把物理文件的内容映射到内存中,那么 MappedByteBuffer 是否能用来在不同 Java 进程(JVM) 间共享数据呢?答案是肯定的,这样

java 内存映射文件

内存映射文件提供了Java有可能达到的最快IO操作,故高性能Java应用应使用“内存映射文件”来持久化数据,尤其是对于较大文件来说它确实比I/O流要快很多,但是小文件却未必. 内存映射文件可以加载大文件,文件的size上限取决于操作系统(内存的可寻址范围):32位,不能超过4GB,即2^32比特:64位,你几乎可以将任何文件映射到内存中并直接使用Java访问. 内存映射文件允许你使用direct或者non-direct 字节缓存(Byte buffer)来直接读写内存. 内存映射文件的一个关键优

Java内存分配(直接内存、堆内存、Unsafel类、内存映射文件)

1.Java直接内存与堆内存-MarchOn 2.Java内存映射文件-MarchOn 3.Java Unsafe的使用-MarchOn 简单总结: 1.内存映射文件 读文件时候一般要两次复制:从磁盘复制到内核空间再复制到用户空间,内存映射文件避免了第二次复制,且内存分配在内核空间,应用程序访问的就是操作系统的内核内存空间,因此极大提高了读取效率.写文件同理. 2.堆内存分配与直接内存分配: Java申请空间时通常是从JVM堆内存分配的,即 ByteBuffer.allocate(int cap

《Java核心技术卷二》笔记(二)文件操作和内存映射文件

文件操作 上一篇已经总结了流操作,其中也包括文件的读写.文件系统除了读写以为还有很多其他的操作,如复制.移动.删除.目录浏览.属性读写等.在Java7之前,一直使用File类用于文件的操作.Java7提供了Path,Paths,Files,FileSystem等类,使文件操作变得简单和全面.此外还有很多第三方库也提供了文件操作的便捷类如common.io中的FileUtils类,Ant api提供的FileSet等类. 1.File类的使用 Java7之前版本中,File类即代表了路径对象也封装

【JavaNIO的深入研究4】内存映射文件I/O,大文件读写操作,Java nio之MappedByteBuffer,高效文件/内存映射

内存映射文件能让你创建和修改那些因为太大而无法放入内存的文件.有了内存映射文件,你就可以认为文件已经全部读进了内存,然后把它当成一个非常大的数组来访问.这种解决办法能大大简化修改文件的代码.fileChannel.map(FileChannel.MapMode mode, long position, long size)将此通道的文件区域直接映射到内存中.注意,你必须指明,它是从文件的哪个位置开始映射的,映射的范围又有多大:也就是说,它还可以映射一个大文件的某个小片断. MappedByteB

Java利用内存映射文件实现按行读取文件

我们知道内存映射文件读取是各种读取方式中速度最快的,但是内存映射文件读取的API里没有提供按行读取的方法,需要自己实现.下面就是我利用内存映射文件实现按行读取文件的方法,如有错误之处请指出,或者有更好更快的实现方式麻烦也提供一下代码. 代码如下: public class testMemoryMappedFile { public static void main(String[] agrs) throws IOException{ RandomAccessFile memoryMappedFi

JAVA NIO之浅谈内存映射文件原理与DirectMemory

Java类库中的NIO包相对于IO 包来说有一个新功能是内存映射文件,日常编程中并不是经常用到,但是在处理大文件时是比较理想的提高效率的手段.本文我主要想结合操作系统中(OS)相关方面的知识介绍一下原理. 在传统的文件IO操作中,我们都是调用操作系统提供的底层标准IO系统调用函数  read().write() ,此时调用此函数的进程(在JAVA中即java进程)由当前的用户态切换到内核态,然后OS的内核代码负责将相应的文件数据读取到内核的IO缓冲区,然后再把数据从内核IO缓冲区拷贝到进程的私有

Thinking in java(内存映射文件)

在处理大文件时,如果利用普通的FileInputStream 或者FileOutputStream 抑或RandomAccessFile 来进行频繁的读写操作,都将导致进程因频繁读写外存而降低速度.如下为一个对比实验. package test; import java.io.BufferedInputStream; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOExc

随机访问文件RandomAccessFile 与 内存映射文件MappedByteBuffer

一.RandomAccessFile RandomAccessFile是用来访问那些保存数据记录的文件的,你就可以用seek( )方法来访问记录,并进行读写了.这些记录的大小不必相同:但是其大小和位置必须是可知的.但是该类仅限于操作文件. RandomAccessFile不属于InputStream和OutputStream类系的.实际上,除了实现DataInput和DataOutput接口之外(DataInputStream和DataOutputStream也实现了这两个接口),它和这两个类系