`

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

    博客分类:
  • NIO
阅读更多

一看到 Java NIO 的内存映射文件(MappedByteBuffer),让我立即就联想到 Windows 系统的内存映射文件。Windows 系统的内存映射文件能用来在多个进程间共享数据,即进程间的共享内存,是通过把同一块内存区域映射到不同进程的地址空间中,从而达到共享内存。

Java NIO 的内存映射文件和 Windows 系统下的一样,都能把物理文件的内容映射到内存中,那么 MappedByteBuffer 是否能用来在不同 Java 进程(JVM) 间共享数据呢?答案是肯定的,这样在通常的 Socket 方式来实现 Java 进程间通信之上又多了一种方法。

在 Windows 中内存映射文件可以是脱离物理文件而存在的一块命名的内存区域,使用相同的内存映射名就能在不同的进程中共享同一片内存。然后,Java 的 MappedByteBuffer 总是与某个物理文件相关的,因为不管你是从 FileInputStream、FileOutputStream 还是 RandomAccessFile 得来的 FileChannel,再 map() 得到的内存映射文件 MappedByteBuffer,如果在构造 FileInputStream、FileOutputStream、RandomAccessFile 对象时不指定物理文件便会有 FileNotFoundException 异常。

所以 Java NIO 来实现共享内存的办法就是让不同进程的内存映射文件关联到同一个物理文件,因为 MappedByteBuffer 能让内存与文件即时的同步内容。严格说来,称之为内存共享是不准确的,其实就是两个 Java 进程通过中间文件来交换数据,用中间文件使得两个进程的两块内存区域的内容得到及时的同步。

用图来理解 Java NIO 的“共享内存”的实现原理:

知道了实现原理之后,下面用代码来演示两个进程间用内存映射文件来进行数据通信。代码 WriteShareMemory.java 往映射文件中依次写入 A、B、C ... Z,ReadShareMemory.java 逐个读出来,打印到屏幕上。代码对交换文件 swap.mm 的第一个字节作了读写标志,分别是 0-可读,1-正在写,2-可读。RandomAccessFile 得到的 Channel 能够灵活的进行读或写,并且不会破坏原有文件内容,而 FileInputStream 或 FileOutputStream 取得的 Channel 则很难达到这一功效,所以使用了 RandomAccessFile 来获得 FileChannel。

WriteShareMemory.java

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package com.unmi;
 
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileChannel.MapMode;
 
/**
  * 往 "共享内存" 写入数据
  * @author Unmi
  */
public class WriteShareMemory {
 
     /**
      * @param args
      * @throws Exception
      */
     public static void main(String[] args) throws Exception {
         RandomAccessFile raf = new RandomAccessFile( "c:/swap.mm" , "rw" );
         FileChannel fc = raf.getChannel();
         MappedByteBuffer mbb = fc.map(MapMode.READ_WRITE, 0 , 1024 );
 
         //清除文件内容
         for ( int i= 0 ;i< 1024 ;i++){
             mbb.put(i,( byte ) 0 );
         }
 
         //从文件的第二个字节开始,依次写入 A-Z 字母,第一个字节指明了当前操作的位置
         for ( int i= 65 ;i< 91 ;i++){
             int index = i- 63 ;
             int flag = mbb.get( 0 ); //可读标置第一个字节为 0
             if (flag != 0 ){ //不是可写标示 0,则重复循环,等待
                 i --;
                 continue ;
             }
             mbb.put( 0 ,( byte ) 1 ); //正在写数据,标志第一个字节为 1
             mbb.put( 1 ,( byte )(index)); //写数据的位置
 
             System.out.println( "程序 WriteShareMemory:" +System.currentTimeMillis() +
                     ":位置:" + index + " 写入数据:" + ( char )i);
 
             mbb.put(index,( byte )i); //index 位置写入数据
             mbb.put( 0 ,( byte ) 2 ); //置可读数据标志第一个字节为 2
             Thread.sleep( 513 );
         }
     }
}

ReadShareMemory.java

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package com.unmi;
 
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileChannel.MapMode;
 
/**
  * 从 "共享内存" 读出数据
  * @author Unmi
  */
public class ReadShareMemory {
 
     /**
      * @param args
      * @throws Exception
      */
     public static void main(String[] args) throws Exception {
         RandomAccessFile raf = new RandomAccessFile( "c:/swap.mm" , "rw" );
         FileChannel fc = raf.getChannel();
         MappedByteBuffer mbb = fc.map(MapMode.READ_WRITE, 0 , 1024 );
         int lastIndex = 0 ;
 
         for ( int i= 1 ;i< 27 ;i++){
             int flag = mbb.get( 0 ); //取读写数据的标志
             int index = mbb.get( 1 ); //读取数据的位置,2 为可读
 
             if (flag != 2 || index == lastIndex){ //假如不可读,或未写入新数据时重复循环
                 i--;
                 continue ;
             }
 
             lastIndex = index;
             System.out.println( "程序 ReadShareMemory:" + System.currentTimeMillis() +
                     ":位置:" + index + " 读出数据:" + ( char )mbb.get(index));
 
             mbb.put( 0 ,( byte ) 0 ); //置第一个字节为可读标志为 0
 
             if (index == 27 ){ //读完数据后退出
                 break ;
             }
         }
     }
}

在 Eclipse 中运行 WriteShareMemory,然后到命令行下运行 ReadShareMemory,你将会看到 WriteShareMemory 写一个字符,ReadShareMemory 读一个。

代码中使用了读写标志位,和写入的索引位置,所以在 WriteShareMemory 写入一个字符后,只有等待 ReadShareMemory 读出刚写入的字符后才会写入第二个字符。实际应用中可以加入更好的通知方式,如文件锁等。

你也可以查看执行时 c:\swap.mm 文件的内容来验证这一过程,因为 MappedByteBuffer 在运行时是一种 DirectByteBuffer,所以它能与文件即时的同步内容,无须通过 FileChannel 来 write(buffer) 往文件中手工写入数据,或 read(buffer) 手工读数据到内存中。

参考:1. 共享内存在Java中实现和应用

本文链接 http://unmi.cc/java-nio-memory-mapping-communicate , 来自 隔叶黄莺 Unmi Blog

分享到:
评论

相关推荐

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

    需要注意的是,使用内存映射文件进行进程间通信需要谨慎管理内存,确保在写入完成后正确地刷新缓冲区到文件,避免数据丢失。此外,由于文件被多个进程共享,必须同步对文件的访问,防止数据冲突。 总之,Java NIO的...

    java NIO实例

    6. **Pipe**:在本地进程间通信(IPC)中使用,提供单向数据流。 7. **CharSet和CharsetDecoder/Encoder**:用于字符编码和解码,支持多种字符集转换。 在`NIOClient.java`文件中,可能会创建SocketChannel连接...

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

    在Java中,可以通过`java.nio.MappedByteBuffer`类来实现共享内存功能,这被称为内存映射文件(Memory-Mapped File,MMF)。 `MappedByteBuffer`是NIO中的一种特殊缓冲区,它将文件的一部分映射到内存中,使得文件...

    JAVA-NIO程序设计完整实例

    - **Pipe.SinkChannel** 和 **Pipe.SourceChannel**: 提供进程间的单向数据流。 ### 3. NIO操作流程 1. **创建通道**: 根据需求创建FileChannel、SocketChannel或ServerSocketChannel等。 2. **创建缓冲区**: ...

    java nio 尚硅谷 12讲 new

    - Java NIO提供了FileChannel,可以进行文件的读写操作,支持随机访问和映射到内存(Memory-Mapped Files)。 6. **网络编程** - SocketChannel和ServerSocketChannel分别用于处理TCP连接和监听。DatagramChannel...

    java NIO socket聊天

    此外,Java NIO还引入了`Pipe`和`FileChannel`等特性,使得进程间通信和文件操作也变得更加灵活。`Pipe`允许两个线程之间通过管道进行单向数据传输,而`FileChannel`提供了直接内存映射到文件的能力,提高了大文件...

    java 深入理解内存映射文件原理

    内存映射文件是Java和其他编程语言中的一种高效的数据访问机制,尤其在处理大文件和进行进程间通信时表现优越。本文将深入探讨内存映射文件的原理、与虚拟内存的区别和联系,以及它们的效率差异。 虚拟内存和内存...

    java进程间tx1

    Java进程间通信(IPC,Inter-Process Communication)是多进程应用程序中实现数据交换的重要机制。在Java中,虽然原生的API对进程间通信的支持不如C++或C那样直接,但依然提供了多种方式来实现这一目标。以下是几种...

    Java NIO.pdf

    - **管道(Pipe)**:用于进程间通信。 #### 五、选择器(Selector)详解 1. **选择器概念**:用于监听多个通道的 I/O 事件,可以同时监控多个 Channel 的状态,一旦某个 Channel 准备好进行 I/O 操作,就可以得到...

    IPC.rar_IPC_java i_java ipc_java共享内存_共享内存

    标题“IPC.rar_IPC_java i_java ipc_java共享内存_共享内存”以及描述“IPC共享内存,文件映射编程,实现原理详解”都指向了一个核心主题:Java中的进程间通信(IPC)以及如何利用共享内存进行数据交换。在这个话题中...

    基于Java的实例源码-进程通信.zip

    Java作为一门广泛使用的编程语言,提供了丰富的API和工具来支持进程间的通信。本实例源码主要探讨了如何在Java环境中实现进程通信,让我们深入理解这一主题。 1. **Java进程通信基础** 在Java中,进程通信通常涉及...

    基于java的进程通信.zip

    Java的`java.nio.MappedByteBuffer`允许将文件映射到内存,使得多个进程可以直接访问同一份文件数据,从而实现进程间的通信。 理解并掌握这些进程通信技术对于编写高效、可扩展的Java应用至关重要。开发者需要根据...

    Java NIO 解析

    例如,可以使用FileChannel进行映射内存到文件,实现内存映射文件(Mmapped Files),这种技术可以减少磁盘I/O,提高文件操作速度。 5. **异步I/O**: 虽然Java NIO主要关注的是同步非阻塞I/O,但JDK 7引入了...

    java nio教程

    - **Pipe**:在本地进程内创建一个单向的数据管道,常用于线程间的通信。 - **Selector**:用于多通道选择,通过select()方法获取准备就绪的通道。 - **Buffer**:数据存储区域,具有读写位置、限制和容量等属性...

    java进程间通讯笔记

    Java进程间通信(IPC,Inter-Process Communication)是多进程应用程序中实现不同进程间数据交换的方式。以下是对标题和描述中涉及的知识点的详细说明: 1. **管道(Pipe)**: 管道是一种半双工通信方式,允许...

    MemMapComm:代码存储库,用于使用Java的内存映射文件测试IPC

    内存映射文件(Memory-Mapped File)是一种在Java中实现进程间通信(IPC,Inter-Process Communication)的有效方式。此项目“MemMapComm”提供了一个代码仓库,专门用于通过Java来探索和测试这一技术。在Java中,...

Global site tag (gtag.js) - Google Analytics