如何读取特定格式的内存映射文件?

时间:2014-12-28 07:16:16

标签: java bytearray nio memory-mapped-files bytebuffer

我正在使用Java中的Memory Mapped文件。我有一个特定的用户ID数据以Avro二进制编码格式存储在内存映射文件中。

内存映射文件包含两个主要部分: - 标题,用作完整文件内容的索引,专门回答文件的问题,并为每个用户的数据提供偏移量。 - 一个正文,后跟给定偏移量的文件中每个用户的数据。

标题

version                     4 bytes
last_modified_date          8 bytes
users                       4 bytes
shards                      4 bytes
the shards                  N * 4 bytes
num_hash_index              4 bytes
num_chain_slots             4 bytes
user offset/size index      num_hash_index * num_chain_slots * (8 bytes + 8 bytes + 4 bytes)

现在标题后面是正文,如下所示。

车身

number of records                   2 bytes         how many records does this user have?
a repeated sequence of records      variable size   as described below

所有记录都遵循此规范:

attribute key                       X bytes     a string of the users key.
key delimiter                       1 bytes     '\0'
client id                           2 bytes     some client id
last modified time (in ms)          8 bytes     This is the last modified time for this attribute in ms.
length of the avro binary data      2 bytes     actual length of avro binary data
the binary avro data or text        Y bytes     Length given by the previous field.

现在我已经使用上述格式生成了很多文件。我需要从Java程序中读取此文件。在Java中执行此操作的最佳方法是什么?这是我第一次使用Memory Mapped文件,所以试着理解我该如何处理这个?

FileChannel fc = new RandomAccessFile(new File("c:/tmp/file.txt"), "rw").getChannel();

现在我不知道该怎么办?任何例子都会帮助我更好地理解。

1 个答案:

答案 0 :(得分:1)

这应该这样做。关键是DataInputStream中读取和转换字节的方法。我认为字节顺序是合适的。

 ByteBuffer buf = ByteBuffer.allocate( 9999 ); // capacity
 int nRead = fc.read( buf );
 InputStream is = new ByteArrayInputStream( buf.array() );
 DataInputStream dis = new DataInputStream( is );
 int version = dis.readInt(); //                   4 bytes
 long timestamp = dis.readLong();  //                 8 bytes
 int numUsers = dis.readInt(); //                   4 bytes

等等。

关于身体的更多细节

不需要存储密钥分隔符('\ 0')和avro数据的长度,这由字节数组的长度表示。我使用int来存储短整数,只是为了安全起见(Java中没有unsigned short),

public class UserAttribute {
  private final String attributeKey;
  private final int schemaId;               // unsigned short
  private final long lastModifiedDate;
  private final byte[] avroBinaryData;      // preceded by length: unsigned short
  // constructor and getters here

}

int numberOfAttributes = dis.readShort();
List<UserAttribute> ual = new ArrayList<>( numberOfAttributes );
for( int iAttr = 0; iAttr < numberOfAttributes; ++iAttr ){
    // read values for one attribute, create UserAttribute  object
    UserAttribute ua = new UserAttribute();
    StringBuilder sb = new StringBuilder();
    for(;;){
        int ub = dis.readUnsignedByte(); // can this be in ISO-8859-1 > 0x80?
        if( ub == 0 ) break;
        sb.append( (char)ub );
    }
    ua.setAttributeKey( sb.toString() );
    ua.setSchemaId( dis.readUnsignedShort() );
    ua.setLastModifiedDate( dis.readLong() );
    int loabd = dis.readUnsignedShort();
    byte[] abd = new byte[loabd];
    for( int ib = 0; ib < loabd; ++ib ){
        abd[ib] = dis.readByte();
    }
    ua.setAvroBinaryData();
    ual.add( ua );
}

另外,我认为阅读分片应该是

int numShards = dis.readInt(); // 4 bytes 1..101
int[] shards = new int[numShards];
for( il = 0; il < numShards; ++il ){
    shards[il] = dis.readInt(); //  N * 4 bytes     Where N is the number of shards
}

甚至更晚内存映射

int read = ...;
FileChannel fc = new RandomAccessFile(file, "rw").getChannel();
ByteBuffer buffer = fc.map(FileChannel.MapMode.READ_ONLY, 0, read );
buffer.order(ByteOrder.BIG_ENDIAN);

这导致包含文件数据的给定长度的ByteBuffer。如果文件大于0x7fffffff,则必须以块的形式映射,这可以使用相同的FileChannel方法,即map。