簡體   English   中英

Java NIO - 內存映射文件

[英]Java NIO - 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;

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調用都可能會擴展文件,實際上是一個空緩沖區(用零填充)或者編寫器可能剛剛擴展了文件,但還沒有寫入任何東西......

我使用內存映射文件進行大量工作以進行進程間通信。 不會推薦Holger的#1或#2,但他的#3就是我做的。 但關鍵的一點是,我可能只與一位作家合作 - 如果你有多個作家,事情會變得更復雜。

文件的開頭是一個標題部分,包含您需要的任何標題變量,最重要的是指向寫入數據末尾的指針。 編寫者應該寫完一段數據更新這個標題變量,讀者永遠不應該超越這個變量。 所有主流CPU都稱之為“緩存一致性”的東西將保證讀者將按照它們所寫的相同順序看到內存寫入,因此如果遵循這些規則,讀者將永遠不會讀取未初始化的內存。 (例外情況是讀者和作者在不同的服務器上 - 緩存一致性在那里不起作用。不要試圖在不同的服務器上實現共享內存!)

更新文件結束指針的頻率沒有限制 - 它全部在內存中,並且不會涉及任何i / o,因此您可以更新每條記錄或您編寫的每條消息。

ByteBuffer有'getInt()'和'putInt()'方法的版本,它們采用絕對字節偏移量,這就是我用來讀取和寫入文件結束標記的...我工作時從不使用相對版本內存映射文件。

你不應該使用文件大小或另一個進程間方法來傳遞文件結束標記,並且當你已經有共享內存時沒有必要或好處。

查看我的庫Mappedbus( http://github.com/caplogic/mappedbus ),它允許多個Java進程(JVM)寫入記錄,以便存儲到同一個內存映射文件中。

以下是Mappedbus如何解決多個編寫器之間的同步問題:

  • 該文件的前八個字節組成一個稱為限制的字段。 此字段指定實際已將多少數據寫入文件。 讀者將輪詢限制字段(使用volatile)以查看是否有要讀取的新記錄。

  • 當編寫者想要向文件添加記錄時,它將使用fetch-and-add指令以原子方式更新限制字段。

  • 當限制字段增加時,讀者將知道要讀取的新數據,但更新限制字段的編寫者可能尚未在記錄中寫入任何數據。 為避免此問題,每條記錄都包含一個構成提交字段的初始字節。

  • 當編寫器完成寫入記錄時,它將設置提交字段(使用volatile),並且只有在看到已設置提交字段后,閱讀器才會開始讀取記錄。

(順便說一下,該解決方案僅經過驗證,可以在Linux x86上使用Oracle的JVM。它很可能無法在所有平台上運行)。

有幾種方法。

  1. 讓作者獲得尚未編寫的區域的獨家Lock 在寫完所有內容后釋放鎖定。 這與在該系統上運行的每個其他應用程序兼容,但它要求讀者足夠聰明以重試失敗的讀取,除非您將其與其他方法之一結合使用

  2. 使用另一個通信通道,例如管道或套接字或文件的元數據通道,讓作者告訴讀者完成的寫入。

  3. 在文件中的一個位置寫一個特殊的標記(作為協議的一部分),告訴寫入的數據,例如

     MappedByteBuffer bb; … // write your data bb.force();// ensure completion of all writes bb.put(specialPosition, specialMarkerValue); bb.force();// ensure visibility of the marker 

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM