java处理大文件,一般用BufferedReader,BufferedInputStream这类带缓冲的Io类,不过如果文件超大的话,更快的方式是采用MappedByteBuffer

MappedByteBuffer是java nio引入的文件内存映射方案,读写性能极高。NIO最主要的就是实现了对异步操作的支持。其中一种通过把一个套接字通道(SocketChannel)注册到一个选择器(Selector)中,不时调用后者的选择(select)方法就能返回满足的选择键(SelectionKey),键中包含了SOCKET事件信息。这就是select模型。
    SocketChannel的读写是通过一个类叫ByteBuffer(java.nio.ByteBuffer)来操作的.这个类本身的设计是不错的,比直接操作byte[]方便多了. ByteBuffer有两种模式:直接/间接.间接模式最典型(也只有这么一种)的就是HeapByteBuffer,即操作堆内存 (byte[]).但是内存毕竟有限,如果我要发送一个1G的文件怎么办?不可能真的去分配1G的内存.这时就必须使用"直接"模式,即 MappedByteBuffer,文件映射.
     先中断一下,谈谈操作系统的内存管理.一般操作系统的内存分两部分:物理内存;虚拟内存.虚拟内存一般使用的是页面映像文件,即硬盘中的某个(某些)特殊的文件.操作系统负责页面文件内容的读写,这个过程叫"页面中断/切换". MappedByteBuffer也是类似的,你可以把整个文件(不管文件有多大)看成是一个ByteBuffer.MappedByteBuffer 只是一种特殊的 ByteBuffer ,即是ByteBuffer的子类。 MappedByteBuffer 将文件直接映射到内存(这里的内存指的是虚拟内存,并不是物理内存)。通常,可以映射整个文件,如果文件比较大的话可以分段进行映射,只要指定文件的那个部分就可以。

三种方式:
              FileChannel提供了map方法来把文件影射为内存映像文件: MappedByteBuffer map(int mode,long position,long size); 可以把文件的从position开始的size大小的区域映射为内存映像文件,mode指出了 可访问该内存映像文件的方式:READ_ONLY,READ_WRITE,PRIVATE.                    
a. READ_ONLY,(只读): 试图修改得到的缓冲区将导致抛出 ReadOnlyBufferException.(MapMode.READ_ONLY)
 b. READ_WRITE(读/写): 对得到的缓冲区的更改最终将传播到文件;该更改对映射到同一文件的其他程序不一定是可见的。 (MapMode.READ_WRITE)
c. PRIVATE(专用): 对得到的缓冲区的更改不会传播到文件,并且该更改对映射到同一文件的其他程序也不是可见的;相反,会创建缓冲区已修改部分的专用副本。 (MapMode.PRIVATE)

三个方法:

a. fore();缓冲区是READ_WRITE模式下,此方法对缓冲区内容的修改强行写入文件
b. load()将缓冲区的内容载入内存,并返回该缓冲区的引用
c. isLoaded()如果缓冲区的内容在物理内存中,则返回真,否则返回假

三个特性:

调用信道的map()方法后,即可将文件的某一部分或全部映射到内存中,映射内存缓冲区是个直接缓冲区,继承自ByteBuffer,但相对于ByteBuffer,它有更多的优点:

a. 读取快
b. 写入快
c. 随时随地写入

下面来看代码:

  1. package study;
  2. import java.io.FileInputStream;
  3. import java.io.FileOutputStream;
  4. import java.nio.ByteBuffer;
  5. import java.nio.MappedByteBuffer;
  6. import java.nio.channels.FileChannel;
  7. public class MapMemeryBuffer {
  8. public static void main(String[] args) throws Exception {
  9. ByteBuffer byteBuf = ByteBuffer.allocate(1024 * 14 * 1024);
  10. byte[] bbb = new byte[14 * 1024 * 1024];
  11. FileInputStream fis = new FileInputStream("e://data/other/UltraEdit_17.00.0.1035_SC.exe");
  12. FileOutputStream fos = new FileOutputStream("e://data/other/outFile.txt");
  13. FileChannel fc = fis.getChannel();
  14. long timeStar = System.currentTimeMillis();// 得到当前的时间
  15. fc.read(byteBuf);// 1 读取
  16. //MappedByteBuffer mbb = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size());
  17. System.out.println(fc.size()/1024);
  18. long timeEnd = System.currentTimeMillis();// 得到当前的时间
  19. System.out.println("Read time :" + (timeEnd - timeStar) + "ms");
  20. timeStar = System.currentTimeMillis();
  21. fos.write(bbb);//2.写入
  22. //mbb.flip();
  23. timeEnd = System.currentTimeMillis();
  24. System.out.println("Write time :" + (timeEnd - timeStar) + "ms");
  25. fos.flush();
  26. fc.close();
  27. fis.close();
  28. }
  29. }
  30. 运行结果:
  31. 14235
  32. Read time :24ms
  33. Write time :21ms
  34. 我们把标注1和2语句注释掉,换成它们下面的被注释的那条语句,再来看运行效果。14235
  35. Read time :2ms
  36. Write time :0ms

可以看出速度有了很大的提升。MappedByteBuffer的确快,但也存在一些问题,主要就是内存占用和文件关闭等不确定问题。被MappedByteBuffer打开的文件只有在垃圾收集时才会被关闭,而这个点是不确定的。在javadoc里是这么说的:A mapped byte buffer and the file mapping that it represents remain valid until the buffer itself  is garbage-collected.
这里提供一种解决方案:

  1. AccessController.doPrivileged(new PrivilegedAction() {
  2. public Object run() {
  3. try {
  4. Method getCleanerMethod = buffer.getClass().getMethod("cleaner", new Class[0]);
  5. getCleanerMethod.setAccessible(true);
  6. sun.misc.Cleaner cleaner = (sun.misc.Cleaner)
  7. getCleanerMethod.invoke(byteBuffer, new Object[0]);
  8. cleaner.clean();
  9. } catch (Exception e) {
  10. e.printStackTrace();
  11. }
  12. return null;
  13. }
  14. });

关于MappedByteBuffer资源释放问题

JDK1.4中加入了一个新的包:NIO(java.nio.*)。这个库最大的功能(我认为)就是增加了对异步套接字的支持。其实在 其他语言中,包括在最原始的SOCKET实现(BSD SOCKET),这是一个早有的功能:异步回调读/写事件,通过选择器动态选择感兴趣的事件,等等。
先谈谈操作系统的内存管理。一般操作系统的内存分两部分:物理内存;虚拟内存。虚拟内存一般使用的是页面映像文件,即硬盘中的某个(某些)特殊的文件.操作系统负责页面文件内容的读写,这个过程叫"页面中断/切换"。
MappedByteBuffer也是类似的,你可以把整个文件(不管文件有多大)看成是一个ByteBuffer。这是一个很好的设计,除了令人头疼的一点在后面会讲到。
java.lang.Object
   java.nio.Buffer
      java.nio.ByteBuffer
          java.nio.MappedByteBuffer
MappedByteBuffer是一个比较方便使用的类。其内容是文件的内存映射区域。映射的字节缓冲区是通过FileChannel.map 方法创建的。映射的字节缓冲区和它所表示的文件映射关系在该缓冲区本身成为垃圾回收缓冲区之前一直保持有效。此类用特定于内存映射文件区域的操作扩展 ByteBuffer 类。 这个类本身的设计是不错的,比直接操作byte[]方便多了。
ByteBuffer有两种模式:直接/间接。间接模式最典型(也只有这么一种)的就是HeapByteBuffer,即操作堆内存(byte [])。但是内存毕竟有限,如果我要发送一个1G的文件怎么办?不可能真的去分配1G的内存.这时就必须使用"直接"模式,即 MappedByteBuffer,文件映射。
在JDK API文档中这样描述的:
全部或部分映射的字节缓冲区可能随时成为不可访问的,例如,如果我们截取映射的文件。试图访问映射的字节缓冲区的不可访问区域将不会更改缓冲区 的内容,并导致在访问时或访问后的某个时刻抛出未指定的异常。因此强烈推荐采取适当的预防措施,以避免此程序或另一个同时运行的程序对映射的文件执行操作 (读写文件内容除外)。
MappedByteBuffer只能通过调用FileChannel的map()取得,再没有其他方式.但是令人奇怪的是,SUN提供了map()却没有提供unmap().这样会导致什么后果呢?
这样,问题就出现了。通过MappedByteBuffer实现文件复制功能非常容易,可以用以下方法来实现。
   //文件复制
   public void copyFile(String filename,String srcpath,String destpath)throws IOException {
    File source = new File(srcpath+"/"+filename);
    File dest = new File(destpath+"/"+filename);
     FileChannel in = null, out = null;
     try { 
      in = new FileInputStream(source).getChannel();
      out = new FileOutputStream(dest).getChannel();
      long size = in.size();
      MappedByteBuffer buf = in.map(FileChannel.MapMode.READ_ONLY, 0, size);
      out.write(buf);
      in.close();
      out.close();
      source.delete();//文件复制完成后,删除源文件
     }catch(Exception e){
      e.printStackTrace();
     } finally {
      in.close();
      out.close();
     }
   }
但是如果要实现文件文件复制完成后,删除源文件,以上方法就有问题。因为在source.delete()时,会返回false,删除失败,主 要原因是变量buf仍然有源文件的句柄,文件处于不可删除状态。既然MappedByteBuffer是从FileChannel中map()出来的,为 什么它又不提供unmap()呢?SUN自己也没有讲清楚为什么。O'Reilly的<<Java NIO>>中说是因为"安全"的原因,但是到底unmap()会怎么不安全,作者也没有讲清楚。
在sun网站也有相应的BUG报告:bug id:4724038链接为http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4724038,但是sun自己不认为是BUG,而只是一个RFE(Request For Enhancement),有待改进。
好在有个叫bellomi的网友提出了一个解决方法,我也测试过,可以实现期望的功能。具体实现代码如下:
   public static void clean(final Object buffer) throws Exception {
         AccessController.doPrivileged(new PrivilegedAction() {
             public Object run() {
             try {
                Method getCleanerMethod = buffer.getClass().getMethod("cleaner",new Class[0]);
                getCleanerMethod.setAccessible(true);
                sun.misc.Cleaner cleaner =(sun.misc.Cleaner)getCleanerMethod.invoke(buffer,new Object[0]);
                cleaner.clean();
             } catch(Exception e) {
                e.printStackTrace();
             }
                return null;}});
         
}
不知道为什么SUN不提供ByteBuffer的派生。毕竟这是一个很实用的类,如果允许派生,那么我就可以操作的就不仅仅限于堆内存和文件了,我可以扩展到任何存储设备。

java大文件读写操作,java nio 之MappedByteBuffer,高效文件/内存映射的更多相关文章

  1. Java 字节流实现文件读写操作(InputStream-OutputStream)

    Java 字节流实现文件读写操作(InputStream-OutputStream) 备注:字节流比字符流底层,但是效率底下. 字符流地址:http://pengyan5945.iteye.com/b ...

  2. Java 字符流实现文件读写操作(FileReader-FileWriter)

    Java 字符流实现文件读写操作(FileReader-FileWriter) 备注:字符流效率高,但是没有字节流底层 字节流地址:http://pengyan5945.iteye.com/blog/ ...

  3. java文件读写操作类

    借鉴了项目以前的文件写入功能,实现了对文件读写操作的封装 仅仅需要在读写方法传入路径即可(可以是绝对或相对路径) 以后使用时,可以在此基础上改进,比如: 写操作: 1,对java GUI中文本框中的内 ...

  4. Golang: 常用的文件读写操作

    Go 语言提供了很多文件操作的支持,在不同场景下,有对应的处理方式,今天就来系统地梳理一下,几种常用的文件读写的形式. 一.读取文件内容 1.按字节读取文件 这种方式是以字节为单位来读取,相对底层一些 ...

  5. [转]Android - 文件读写操作 总结

     转自:http://blog.csdn.net/ztp800201/article/details/7322110 Android - 文件读写操作 总结 分类: Android2012-03-05 ...

  6. Kotlin入门(27)文件读写操作

    Java的文件处理用到了io库java.io,该库虽然功能强大,但是与文件内容的交互还得通过输入输出流中转,致使文件读写操作颇为繁琐.因此,开发者通常得自己重新封装一个文件存取的工具类,以便在日常开发 ...

  7. Kotlin入门-文件读写操作

    转 https://blog.csdn.net/aqi00/article/details/83241762 Java的文件处理用到了io库java.io,该库虽然功能强大,但是与文件内容的交互还得通 ...

  8. python里文件读写操作

    文件读写操作一种基本操作,但是里面也存在很多需要注意的问题,例如字符编码.内存缓冲.指针位置等等.如果忽视这些问题就会引起很多不必要的麻烦.简单来说,文件的读写分为几个过程: 打开文件,并定义操作文件 ...

  9. loadrunner 脚本开发-文件读写操作

    脚本开发-文件读写操作 by:授客 QQ:1033553122 函数说明 函数原型: size_t fwrite( const void *buffer, size_t size, size_t co ...

  10. loadrunder脚本篇——文件读写操作

     函数说明 函数原型: size_t fwrite( const void *buffer, size_t size, size_t count, FILE *file_pointer ); 参数说明 ...

随机推荐

  1. hdu 1166 敌兵布阵 解题报告

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1166 题目意思:给出 N 个数你,通过对某些数进行更改(或者 + 或者 -),当输入的是 Query ...

  2. fuse的write过程到底是怎么样的,128KB的buffer怎么用?

    1. 在fuse/lib/fuse_kern_chan.c中有一个buffer设置 #define MIN_BUFSIZE 0x21000 //十进制132×1024  //为何不是128? 下面有一 ...

  3. 「LuoguP4752」牧 Divided Prime(判质数

    Description 给定一个数字 A,这个 A 由 a1,a2,⋯,aN相乘得到. 给定一个数字 B,这个 B 由 b1,b2,⋯,bM相乘得到. 如果 A/B 是一个质数,请输出YES,否则输出 ...

  4. 「LuoguP2170」 选学霸(01背包

    Description 老师想从N名学生中选M人当学霸,但有K对人实力相当,如果实力相当的人中,一部分被选上,另一部分没有,同学们就会抗议.所以老师想请你帮他求出他该选多少学霸,才能既不让同学们抗议, ...

  5. Spss22安装与破解教程

    Spss22安装与破解教程 Spss22安装与破解教程 1.下载安装包 可以去IBM官网.人大论坛等网站下载,全部文件应包括spss22安装包(含32位及64位)和破解文件,这里提供一个64位的百度网 ...

  6. Excel: 使用Countif函数来去掉Excel中重复的数据

    如果使用Ruby脚本,uniq函数就能搞定一切.不过我们现在还是讨论怎么用excel的Countif函数吧. 假设Excel中有一列数据:

  7. 洛谷P3195 [HNOI2008]玩具装箱TOY——斜率优化DP

    题目:https://www.luogu.org/problemnew/show/P3195 第一次用斜率优化...其实还是有点云里雾里的: 网上的题解都很详细,我的理解就是通过把式子变形,假定一个最 ...

  8. 如何让虚拟机的Ubuntu上网?

    先声明 本文使用的虚拟机: VMware Workstation 14 Pro 本文使用的Ubuntu : ARM裸机1期加强版配套的Ubuntu16.04 特别注意:如果你使用的虚拟机和Ubuntu ...

  9. springboot读写分离--temp

    我最初的想法是: 读方法走读库,写方法走写库(一般是主库),保证在Spring提交事务之前确定数据源. 保证在Spring提交事务之前确定数据源,这个简单,利用AOP写个切换数据源的切面,让他的优先级 ...

  10. 关于HibernateDaoSupport中的getHibernateTemplate().execute及executeFind方法

    转自:https://blog.csdn.net/angus_17/article/details/8501668 1. 这两个方法都是为了Spring在接管Hibernate之后,可以对Hibern ...