我最近遇到了这个article,它为内存映射文件提供了一个很好的介绍,以及它如何在两个进程之间共享。以下是读取文件的进程的代码:
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
public class MemoryMapReader {
/**
* @param args
* @throws IOException
* @throws FileNotFoundException
* @throws InterruptedException
*/
public static void main(String[] args) throws FileNotFoundException, IOException, InterruptedException {
FileChannel fc = new RandomAccessFile(new File("c:/tmp/mapped.txt"), "rw").getChannel();
long bufferSize=8*1000;
MappedByteBuffer mem = fc.map(FileChannel.MapMode.READ_ONLY, 0, bufferSize);
long oldSize=fc.size();
long currentPos = 0;
long xx=currentPos;
long startTime = System.currentTimeMillis();
long lastValue=-1;
for(;;)
{
while(mem.hasRemaining())
{
lastValue=mem.getLong();
currentPos +=8;
}
if(currentPos < oldSize)
{
xx = xx + mem.position();
mem = fc.map(FileChannel.MapMode.READ_ONLY,xx, bufferSize);
continue;
}
else
{
long end = System.currentTimeMillis();
long tot = end-startTime;
System.out.println(String.format("Last Value Read %s , Time(ms) %s ",lastValue, tot));
System.out.println("Waiting for message");
while(true)
{
long newSize=fc.size();
if(newSize>oldSize)
{
oldSize = newSize;
xx = xx + mem.position();
mem = fc.map(FileChannel.MapMode.READ_ONLY,xx , oldSize-xx);
System.out.println("Got some data");
break;
}
}
}
}
}
}
然而,我对这种方法有一些意见/问题:
如果我们只在空文件上执行阅读器,即运行
long bufferSize=8*1000;
MappedByteBuffer mem = fc.map(FileChannel.MapMode.READ_ONLY, 0, bufferSize);
long oldSize=fc.size();
这将分配8000个字节,现在将扩展该文件。返回的缓冲区的限制为8000,位置为0,因此,读者可以继续读取空数据。发生这种情况后,读者将停止,currentPos == oldSize
。
据说现在作者进来了(代码被省略了,因为大部分都是直截了当的,可以从网站上引用) - 它使用相同的缓冲区大小,因此它将先写入8000个字节,然后分配另外8000个,扩展文件。现在,如果我们假设这个过程暂停,然后我们回到阅读器,那么读者会看到文件的新大小并分配剩余部分(从位置8000到1600)并再次开始阅读,阅读另一个垃圾...
我有点困惑是否有同步这两个操作的原因。对我来说,任何对map
的调用都可能会扩展文件,实际上是一个空缓冲区(用零填充),或者编写器可能只是扩展了文件,但还没有写入任何内容......
答案 0 :(得分:12)
我为内存映射文件做了很多工作,用于进程间通信。我会 不 推荐Holger的#1或#2,但他的#3就是我的工作。但关键的一点是,我可能只与一位作家合作 - 如果你有多位作家,事情会变得更复杂。
文件的开头是一个标题部分,包含您需要的任何标题变量,最重要的是指向写入数据末尾的指针。在编写一段数据之后,编写者应该始终更新此头文件变量,读者不应该读取此变量。一个名为&#34;缓存一致性&#34;所有主流CPU都会保证读者会按照它们写入的相同顺序看到内存写入,因此如果您遵循这些规则,读者将永远不会读取未初始化的内存。 (例外情况是读者和作者在不同的服务器上 - 缓存一致性在那里不起作用。不要尝试在不同的服务器上实现共享内存!)
更新文件结束指针的频率没有限制 - 它全部在内存中并且不会涉及任何i / o,因此您可以更新每条记录或者你写的每条消息。
ByteBuffer拥有&#39; getInt()&#39;的版本和&#39; putInt()&#39;采用绝对字节偏移的方法,以便我用于读取和读取的内容。编写文件结束标记...在处理内存映射文件时,我从不使用相对版本。
你不应该使用文件大小或另一种进程间方法来传递文件结束标记,并且当你已经有共享内存时没有必要或好处。
答案 1 :(得分:5)
查看我的库Mappedbus(http://github.com/caplogic/mappedbus),它允许多个Java进程(JVM)为同一个内存映射文件写入记录。
以下是Mappedbus如何解决多个编写器之间的同步问题:
文件的前八个字节组成一个名为limit的字段。此字段指定实际已将多少数据写入文件。读者将轮询限制字段(使用volatile)以查看是否有新记录要读取。
当作者想要向文件添加记录时,它将使用fetch-and-add指令以原子方式更新限制字段。
当限制字段增加时,读者将知道要读取的新数据,但更新限制字段的编写者可能尚未在记录中写入任何数据。为了避免这个问题,每个记录都包含一个构成提交字段的初始字节。
当一个编写器写完一条记录时,它会设置一个提交字段(使用volatile),一旦看到提交字段被设置,读者只会开始读取一条记录。
< / LI>(顺便说一句,该解决方案仅经过验证,可以在Linux x86上使用Oracle的JVM。很可能无法在所有平台上运行。)
答案 2 :(得分:3)
有几种方法。
让作者在尚未编写的区域获得独占Lock
。在写完所有内容后释放锁定。这与在该系统上运行的每个其他应用程序兼容,但它要求读者足够聪明以重试失败的读取,除非您将其与其他方法之一结合使用
使用其他通讯渠道,例如管道或套接字或文件的元数据通道,让作者告诉读者完成的写入。
在文件中的一个位置写一个特殊的标记(作为协议的一部分),告诉写入的数据,例如
MappedByteBuffer bb;
…
// write your data
bb.force();// ensure completion of all writes
bb.put(specialPosition, specialMarkerValue);
bb.force();// ensure visibility of the marker