`
langgufu
  • 浏览: 2306899 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

java大文件读写操作,java nio 之MappedByteBuffer,高效文件/内存映射

阅读更多

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. 随时随地写入

下面来看代码:

 

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

[java] view plaincopy
  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 nio 包读取超大数据文件

    内存映射文件是一种将文件内容直接映射到内存中的技术,使得对文件的操作如同操作内存一样简单高效。下面结合给定的部分代码片段,详细解释如何使用Java NIO读取超大数据文件: 1. **初始化配置** - **定义文件...

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

    在Java NIO中,内存映射文件(MappedByteBuffer)是一个重要的特性,它允许将文件直接映射到内存中,以便于快速访问和修改文件内容。这一特性不仅提高了读写效率,而且还能用于进程间通信(IPC)。 内存映射文件的...

    java NIO.zip

    NIO允许将文件直接映射到内存,使得读写文件就像操作普通缓冲区一样快速。通过MappedByteBuffer类,可以直接在内存中对文件进行操作,提高大文件处理的性能。 7. **管道(Pipes)**: 管道用于在两个线程间创建...

    java NIO 写文件

    4. **内存映射文件**:`MappedByteBuffer`允许将文件直接映射到内存,提供了极高的读写速度。 然而,尽管Java NIO提供了很多优势,但在实际使用中,开发者还需要注意一些潜在的问题,如内存管理(缓冲区过大可能...

    commons-mmf.rar_java nio_java共享内存_共享内存

    Java NIO(New Input/Output)是Java标准库中提供的一种替代传统IO的高效I/O模型,它引入了通道(Channel)和缓冲区(Buffer)的概念,极大地优化了数据读写操作。在处理大流量实时业务系统时,NIO的优势尤为突出,...

    Java I/O, NIO and NIO.2

    FileChannel新增了映射内存到文件(MappedByteBuffer)的功能,使得大文件操作更加高效。 在实际应用中,Java I/O适用于简单的文件读写和网络通信,而NIO和NIO.2更适合于需要高并发和低延迟的场景,如服务器开发。...

    java nio入门学习,两个pdf

    4. **文件系统(File Systems)**:NIO提供FileChannel和FileLock用于处理文件系统操作,支持随机访问、映射到内存(MappedByteBuffer)等功能。 三、Java NIO的工作流程 1. **打开通道**:首先,我们需要创建一个或多...

    The_Study_about_Java.nio.rar_java nio

    4. **文件系统操作**:Java NIO提供了更灵活的文件操作,如文件映射(MappedByteBuffer)。 四、Java NIO的应用场景 1. **网络编程**:Java NIO在服务器端开发中,特别是高并发的TCP连接处理,如聊天服务器、游戏...

    Java NIO测试示例

    使用MappedByteBuffer,NIO可以将文件映射到内存,使得文件操作如同操作内存一样快速,特别适合大数据处理。 在实际应用中,Java NIO通常用于高性能的服务器编程,例如在开发聊天服务器、Web服务器或游戏服务器时...

    Java NIO 中英文版 + Pro Java 7 NIO.2

    5. **文件系统操作**:NIO提供FileChannel,可以高效地进行文件读写操作,包括映射文件到内存(MappedByteBuffer)。 6. **管道(Pipes)**:管道是两个线程之间进行单向数据传输的通道,用于线程间通信。 7. **...

    【IT十八掌徐培成】Java基础第27天-01.MappedMemoryBuffer-文件内存映射缓冲区.zip

    Java中的MappedByteBuffer是Java NIO(New Input/Output)库的一部分,它提供了一种高效的方式来访问和操作大文件。在标题和描述中提到的“文件内存映射缓冲区”是Java处理大文件的一种高级技术,它允许将文件直接...

    Java nio源码

    Java NIO还提供了内存映射文件,允许将文件直接映射到内存,从而提高文件读写的性能。这种方式下,操作系统负责文件和内存之间的数据传输,减少了Java虚拟机的干预。 6. **文件系统操作** Java NIO提供了...

    Java NIO 中英文版

    - Java NIO提供了一组文件系统操作API,例如FileChannel用于读写文件,MappedByteBuffer实现了内存映射文件,可以直接通过内存访问文件内容,提高了读写速度。 4. **缓冲区的分类** - **ByteBuffer**:用于处理...

    java读取大文件

    `MappedByteBuffer`是Java NIO(New I/O)的一部分,它允许将文件的一部分映射到内存中,使得可以直接通过内存访问文件数据,而无需通过I/O通道。在代码中,`MappedByteBuffer`被用来按块读取大文件,减少了内存...

    java的大数据读写

    因此,了解如何有效地使用`java.nio`包中的缓冲通道或流,或者使用内存映射文件(`MappedByteBuffer`)可以优化内存使用。 5. **分块读写**:面对GB级别的文件,一次性加载到内存是不现实的。因此,通常会将大文件...

    文件高速传输读写 客户端服务端

    本主题聚焦于Java平台下如何实现高效的文件操作,特别是利用内存映射(MappedByteBuffer)进行读写和通过网络进行文件传输。以下是相关的知识点详解: 1. **内存映射文件(MappedByteBuffer)**: 内存映射文件是...

    Java.nio

    例如,可以使用MappedByteBuffer进行内存映射文件操作,将文件内容直接映射到内存,提高大文件读写的效率。 另外,Java.nio还包含网络通信相关的类,如SocketChannel和ServerSocketChannel,它们支持TCP和UDP协议,...

    java-instantcode-developing.rar_java nio

    4. **内存映射文件(MappedByteBuffer)**:Java NIO提供了一种高效访问大文件的方式,即内存映射文件。通过映射文件到内存,可以直接通过内存操作文件,减少了磁盘I/O的开销。 5. **非阻塞模式**:与传统的阻塞IO...

    Java.nio 与Java.io比较

    3. **文件映射缓冲区**:对于大文件的处理,Java.nio提供了一个名为`MappedByteBuffer`的类,可以直接将文件映射到内存,避免了多次读写操作,极大地提高了效率。 4. **选择器(Selector)**:选择器允许程序同时...

Global site tag (gtag.js) - Google Analytics