MappedByteBuffer介绍及详细解读(java操作大文件多种方法)


MappedByteBuffer是ByteBuffer的子类。
以前我们操作大文件都是用BufferedInputStream、BufferedOutputStream等带缓冲的IO流处理。
现在我们讲一下java nio中一种基于MappedByteBuffer操作大文件的方式,读写性能极高。


在讲之前我们先了解一点关于内存的知识:
物理内存: 就是内存条的内存空间。
虛拟内存: 是计算机系统内存管理的一种技术。它使得应用程序认为它拥有连续的可用的内存(一个连续完整的地址空间),而实际上,它通常是被分隔成多个物理内存碎片,还有部分暂时存储在外部磁盘存储器上,在需要时进行数据交换。目前,大多数操作系统都使用了虚拟内存,如 Windows家族的“虚拟内存”; Linux的“交换空间”等。

MappedByteBuffer采用direct buffer的方式读写文件内容,这种方式就是内存映射。这种方式直接调用系统底层的缓存,没有JVM和系统之间的复制操作,所以效率非常高。主要用于操作大文件。

下面我们开始说是如何使用:
MappedByteBuffer没有构造函数(不可new MappedByteBuffer ( )来构造一个MappedByteBuffer( ),我们需要借助FileChannel提供的map方法把文件映射为MappedByteBuffer-->MappedByteBuffer map(int mode, long position, long size);其实就是Map把文件的内容被映像到计算机虚拟内存的一块区域,这样就可以直接操作内存当中的数据而无需操作的时候每次都通过IO去物理硬盘读取文件,所以效率高。

参数 int mode的三种写法:
1、MapMode. READ_ ONLY(只读)
2、MapMode. READ_WRITE(读/写)
3、MapMode PRIVATE
long position和 long size:把文件的从position开始的size大小的区域映射为内存映像文件。

MappedByteBuffer比 ByteBuffer多的三个方法:
1、fore( )缓冲区是READ_WRITE模式下,此方法对缓冲区内容的修改强行写入文件
2、load( )将缓冲区的内容载入内存,并返回该缓冲区的引用
3、isloaded( )如果缓冲区的内容在物理内存中,则返回真,否则返回假。

如果只需要读时可以使用FileInputStream,写映射文件时一定要使用随机( RandomAccessFile)访问文件。

下面的基本使用,创建了一个128Mb的文件,如果一次性读到内存可能导致内存溢出,这里访问好像只是一瞬间的事,这是因为真正调入内存的只是其中的一小部分,其余部分则被放在交换文件上。这样你就很方便地修改超大型的文件了(最大可以到2GB,基本上超过1.5G就可以考恵使用分块操作了)。Java是调用操作系统的“文件映射机制(file- mapping facility)”来提升性能的。如果是操作小文件,就用基本的O就可以了( FileInputStream,FileOutputStream)。

MappedByteBuffer的基本使用:

public class MappedByteBuffer基本应用 {
    static int length = 0x8000000; // 128 Mb 一个bit占1B,0x8000000换成十进制为:134217728
    public static void main(String[] args) throws Exception {
        // 为了以可读可写的方式打开文件,我们使用RandomAccessFile来创建文件
        FileChannel fc = new RandomAccessFile("D:/TEST/test3.txt", "rw").getChannel();
        //文件通道的可读可写要建立在文件流本身可读写的基础之上
        MappedByteBuffer mbb = fc.map(FileChannel.MapMode.READ_WRITE, 0, length);
        //写128M的内容*(a)
        for (int i = 0; i < length; i++) {
            mbb.put((byte) 'a');
        }
        System.out.println("writing end");
        //读取文件中间20个字节内容
        for (int i = length / 2; i < length / 2 + 20; i++) {
            System.out.print((char) mbb.get(i));
        }
        fc.close();
    }
}

MappedByteBuffer与io效率对比:

package com.itheima.springboot_day01_2;

import java.io.*;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;

/**
 * 测试MappedByte和lO的效率对比
 */
public class MappedByteBuffer与io效率对比 {
    static int length = 0x8000000; // 128 Mb 一个bit占1B,0x8000000换成十进制为:134217728

    public static void main(String[] args) throws Exception {
        long start = System.currentTimeMillis();
        // 调用普通io
        testIO();
        // 调用MappedByteBuffer
        testMappedByteBuffer();
        // 调用 testFileChannel()
        testFileChannel();
        // 调用 testFileChannelByteBuffer()
        testFileChannelByteBuffer();
        long end = System.currentTimeMillis();
        System.out.println("耗时=" + (end - start) + "ms");
        /**
         * 调用 testIO()打印内容:耗时=6218ms;
         * 调用 testMappedByteBuffer()打印内容:耗时=2132ms
         * 调用 testFileChannel()打印内容:耗时=703ms
         * 调用 testFileChannelByteBuffer()打印内容:耗时=819ms
         */
    }

    /**
     * 测试0.9G文件,IO的效率
     *
     * @throws IOException
     */
    private static void testIO() throws IOException {
        File sourceFile = new File("D:/TEST/testFile0.9G文件.zip");
        byte[] bytes = new byte[1024];  // 和下面方式创建byte[]效率基本一样
//        byte[] bytes = new byte[(int) sourceFile.length()];
        FileInputStream fis = new FileInputStream(sourceFile);
        FileOutputStream fos = new FileOutputStream("D:/TEST/0.9G文件.zip");
        int len = -1;
        while ((len = fis.read(bytes)) != -1) {
            fos.write(bytes, 0, len); // 写入数据
        }
        fis.close();
        fos.close();
    }

    /**
     * 测试0.9G文件,MappedByteBuffer的效率
     *
     * @throws IOException
     */
    private static void testMappedByteBuffer() throws IOException {
        File sourceFile = new File("D:/TEST/testFile0.9G文件.zip");
//        byte[] bytes = new byte[1024];  // 和下面方式创建byte[]效率基本一样
        byte[] bytes = new byte[(int) sourceFile.length()];
        RandomAccessFile ra_read = new RandomAccessFile(sourceFile, "r");
        FileChannel fc = new RandomAccessFile("D:/TEST/0.9G文件.zip", "rw").getChannel();
        MappedByteBuffer map = fc.map(FileChannel.MapMode.READ_WRITE, 0, sourceFile.length());
        int len = -1;
        while ((len = ra_read.read(bytes)) != -1) {
            map.put(bytes, 0, len); // 写入数据
        }
        ra_read.close();
        fc.close();
    }

    /**
     * 测试0.9G文件,FileChannel的效率
     *
     * @throws IOException
     */
    private static void testFileChannel() throws IOException {
        File sourceFile = new File("D:/TEST/testFile0.9G文件.zip");
        FileInputStream fis = new FileInputStream(sourceFile);
        FileChannel fisChannel = fis.getChannel();
        FileOutputStream fos = new FileOutputStream("D:/TEST/0.9G文件.zip");
        FileChannel fosChannel = fos.getChannel();
        fisChannel.transferTo(0, fisChannel.size(), fosChannel);
        fis.close();
        fos.close();
    }

    /**
     * 测试0.9G文件,FileChannel的效率
     *
     * @throws IOException
     */
    private static void testFileChannelByteBuffer() throws IOException {
        try (FileChannel from = new FileInputStream("D:/TEST/testFile0.9G文件.zip").getChannel();
             FileChannel to = new FileOutputStream("D:/TEST/0.9G文件.zip").getChannel();
        ) {
            ByteBuffer bb = ByteBuffer.allocateDirect(1024 * 1024);
            while (true) {
                int len = from.read(bb);
                if (len == -1) {
                    break;
                }
                bb.flip();  // 调用flip之后,读写指针指到缓存头部,切换成读模式
                to.write(bb);
                bb.clear();  // 切换成写模式
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}

 调用 testIO( )打印内容:耗时=6218ms;
 调用 testMappedByteBuffer( )打印内容:耗时=2132ms
总结:利用 FileChannel获取MappedByteBuffer操作大文件效率明显高于普通IO流。文件过大时会报错( Exception in threadmainjava. lang Negative Array Size Exception),遇到报错就要做文件分片、分块了。

注意:要先分块才能使用MappedByteBuffer写操作。MappedByteBuffer其实就是文件映射,不能把一个大文件用MappedByteBuffer进行分块。

  • 13
    点赞
  • 42
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
你可以使用`RandomAccessFile`类以及`MappedByteBuffer`来指定位置写入文件。首先,你需要创建一个`RandomAccessFile`对象,并使用`"rw"`模式打开文件。然后,调用`getChannel()`方法获取文件的`FileChannel`对象。接下来,你可以使用`FileChannel`的`map()`方法创建一个`MappedByteBuffer`,并指定要写入的位置和长度。 下面是一个示例代码: ```java import java.io.RandomAccessFile; import java.nio.MappedByteBuffer; import java.nio.channels.FileChannel; import java.nio.charset.StandardCharsets; public class Main { public static void main(String[] args) { try { RandomAccessFile file = new RandomAccessFile("example.txt", "rw"); FileChannel channel = file.getChannel(); int position = 10; // 指定写入的位置 int size = 15; // 写入的字节长度 MappedByteBuffer buffer = channel.map(FileChannel.MapMode.READ_WRITE, position, size); // 写入数据到MappedByteBuffer String data = "Hello, World!"; buffer.put(data.getBytes(StandardCharsets.UTF_8)); // 关闭文件和通道 channel.close(); file.close(); } catch (Exception e) { e.printStackTrace(); } } } ``` 在上述示例中,我们打开了一个名为`example.txt`的文件,并将文件的第10个字节开始的15个字节映射到`MappedByteBuffer`。然后,我们使用`put()`方法将字符串"Hello, World!"写入到`MappedByteBuffer`中。 请注意,使用`MappedByteBuffer`时需要保证指定的位置和长度不超过文件的大小,否则可能会抛出`IndexOutOfBoundsException`异常。另外,一旦将数据写入到`MappedByteBuffer`中,它会自动反映到文件中,你不需要手动执行写入操作。 希望这可以帮助到你!如果有任何其他问题,请随时提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值