javanio-内存映射文件
我最近遇到了这个问题,它为内存映射文件以及如何在两个进程之间共享提供了一个很好的介绍。以下是读取文件的进程的代码:javanio-内存映射文件,java,buffer,nio,channel,memory-mapped-files,Java,Buffer,Nio,Channel,Memory Mapped Files,我最近遇到了这个问题,它为内存映射文件以及如何在两个进程之间共享提供了一个很好的介绍。以下是读取文件的进程的代码: 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;
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;
}
}
}
}
}
}
这将分配8000字节,现在将扩展文件。此返回的缓冲区限制为8000,位置为0,因此,读取器可以继续读取空数据。发生这种情况后,读卡器将停止,因为currentPos==oldSize
假设现在写入程序加入(代码被省略,因为大部分代码都很简单,可以从网站上引用)-它使用相同的缓冲区大小,因此它将先写入8000字节,然后再分配8000字节,扩展文件。现在,如果我们假设这个过程在这一点上暂停,我们回到读卡器,那么读卡器会看到文件的新大小并分配剩余部分(从位置8000到1600),然后再次开始读取,在另一个垃圾中读取
我有点困惑,是否有一个为什么要同步这两个操作。在我看来,对
map
的任何调用都可能会使用一个真正的空缓冲区(填满零)扩展文件,或者编写器可能只是扩展了文件,但还没有写入任何内容…有几种方法
锁。写入所有内容后释放锁。这与在该系统上运行的所有其他应用程序都兼容,但它要求读卡器足够智能,能够在失败的读取上重试,除非您将其与其他方法之一结合使用
MappedByteBuffer bb;
…
// write your data
bb.force();// ensure completion of all writes
bb.put(specialPosition, specialMarkerValue);
bb.force();// ensure visibility of the marker
我为进程间通信使用内存映射文件做了大量工作。我不推荐霍尔格的1号或2号,但他的3号才是我喜欢的。但关键的一点可能是,我只和一个作家合作——如果你有多个作家,事情会变得更复杂 文件的开头是一个标题部分,包含您需要的任何标题变量,最重要的是指向写入数据结尾的指针。写入程序在写入一段数据后应始终更新此标头变量,而读取器的读取不应超过此变量。所有主流CPU都有一种称为“缓存一致性”的东西,可以保证读卡器看到的内存写入顺序与写入顺序相同,因此如果遵循这些规则,读卡器永远不会读取未初始化的内存。(例外情况是读写器位于不同的服务器上-缓存一致性在那里不起作用。不要尝试在不同的服务器上实现共享内存!) 您可以更新文件结尾指针的频率没有限制—它都在内存中,不会涉及任何i/o,因此您可以在写入的每条记录或每条消息中更新它 ByteBuffer有'getInt()'和'putInt()'方法的版本,它们采用绝对字节偏移量,因此我用于读取和写入文件结尾标记…在处理内存映射文件时,我从不使用相对版本
当您已经拥有共享内存时,您不可能使用文件大小或另一种进程间方法来传递文件结束标记,也不需要这样做或从中受益。请查看我的库Mappedbus(),它允许多个Java进程(JVM)为同一个内存映射文件写入记录 以下是Mappedbus如何解决多个写入程序之间的同步问题:
- 文件的前八个字节组成一个名为limit的字段。此字段指定实际写入文件的数据量。读卡器将轮询限制字段(使用volatile)以查看是否有新记录要读取
- 当写入程序希望向文件中添加记录时,它将使用fetch和add指令以原子方式更新limit字段
- 当限制字段增加时,读取器将知道有新数据要读取,但更新限制字段的写入器可能尚未在记录中写入任何数据。为避免此问题,每条记录都包含一个初始字节,该字节构成提交字段
- 写入程序完成记录写入后,将设置提交字段(使用volatile),而读取器仅在看到提交字段已设置后才开始读取记录
(顺便说一句,该解决方案仅在Linux x86和Oracle的JVM上运行。它很可能无法在所有平台上运行)。每当我看到“写入”和“共享数据”时,我认为都需要同步。我不知道“是否有同步的原因”是什么意思,但打开大量内存映射文件,由于垃圾收集的原因,或者多次使用同一个内存是一个非常糟糕的主意,因为没有明确定义的时间来释放相关内存。像8k这样的少量映射没有什么特别的好处:您也可以只使用缓冲流,默认情况下,缓冲流有那么多的缓冲,并且没有任何关于扩展文件时该做什么的废话。映射文件在非常小的数量(例如一个非常大的文件)上使用时效果最好。好的,明白了-打开一个大文件。尽管如此,这是IPC的平均值,所以我想知道如何实现这一点,即一个进程写,另一个进程读,但在某种程度上,我们知道另一个进程实际上在我们读之前写了一些东西。这就是我所说的同步,它是一个文件而不是管道。单独使用mmap()将不允许同步。示例代码进行(丑陋的)繁忙轮询。所谓锁,您指的是
FileLock
MappedByteBuffer bb;
…
// write your data
bb.force();// ensure completion of all writes
bb.put(specialPosition, specialMarkerValue);
bb.force();// ensure visibility of the marker