【问题标题】:Most efficient way to store a large number of key-value pairs of integers, separated by a delimiter, in a file在文件中存储大量键值对整数的最有效方法,由分隔符分隔
【发布时间】:2015-10-17 17:10:15
【问题描述】:

我有一个系统,其中两个程序同时读取和写入一个包含大量无符号整数对的文件。键的值可以是 [0,2^16),而值的值可以是 [0,2^32)。我目前将这些整数作为字符存储在文件中,其中每一对都在自己的行上,键和值由一个空格分隔,并且所有值都有 10 个字符。

10 个字符的原因有两个:1. 解释为字符的最大无符号整数是 10 个字符长,以及 2. 我正在使用 mmap 将文件映射到两个程序的内存中,使用 MAP_SHARED旗帜。当写入文件的程序第一次运行时,它将线性扫描文件并构造一个哈希映射,将键映射到指向值的指针。当 writer 想要将一个新的 key-value 对放入文件中时,它将在 hash map 中搜索 key,如果该 key 存在,它将覆盖与该 key 关联的地址中存储的 value。我确保所有值都在文件中存储为 10 个字符,其中较小的值在右侧用空格填充,以便编写者始终可以简单地覆盖该地址处的值,而不必对内存和 munmap 进行一些疯狂的洗牌然后再次映射。

但是,将整数作为字符串存储在文件中效率极低,我想知道如何更有效地做三件事:

  1. 将整数对存储为整数对而不是字符串。我希望文件尽可能小。

  2. 不必在编写程序中使用额外的大型数据结构,如哈希映射。

  3. 允许读取程序在 log(n) 时间内搜索和检索键值对,而不是现在执行的操作,即线性读取文件以获取匹配的字符串。

一般来说,文件的结构可以像数据结构一样可以通过二进制搜索等方式进行排序,如果可以,我将如何去做?

【问题讨论】:

  • 您能否将当前的实现与需求分开一点?例如。列出你想支持什么样的操作?
  • @KarolyHorvath 现在,写程序只需要设置一个键值对,读程序只需要获取一个键关联的值。但我想如果允许编写程序也删除一对会很好。
  • 为什么文件的大小很关键?您是在磁盘空间严重受限或内存非常有限的系统上使用的吗?
  • 在 2^32 个可能的值中是否有一个值可以指定为 NULL
  • 如果有足够的内存,可以在一个数组中存储 2^16 个键。

标签: c++ file file-io integer mmap


【解决方案1】:

数据存储: 我建议您以二进制形式存储数据,这会在一定程度上减小文件大小。要保留platform-independence,您应该使用msgpack 之类的内容。

数据访问:我认为mmap() 是个好主意(特别是如果您使用像boost memory-mapped file 这样的跨平台解决方案)

搜索:由于 mmap()ing 将提供一些指针来处理您的数据,您应该能够将其用作原始 C 样式数组或包装它在 array_view 中并将其与 <algorithm> 标头设施一起使用。

【讨论】:

  • 感谢您的建议。我已经实施了一个解决方案,并用它回答了这个问题!
【解决方案2】:

我最终实现了一个我正在寻找的解决方案,它执行以下操作:

  1. 键值对以二进制格式存储在文件中,4 个字节为键,4 个字节为值。

  2. 该文件仅包含键值对,因此该文件只是一个键值对流,没有分隔符或多余的绒毛。

  3. 读写程序都可以在log(n)时间内搜索一个key并获取对应的值。这是通过将二进制文件重新解释为 8 字节块的数组,将每个 8 字节块重新解释为两个 4 字节块(键和值),并对映射文件执行二进制搜索来实现的。

我想出的代码如下:

struct Pair { uint32_t index[]; };
struct PairArray { uint64_t index[]; };

size_t getFilesize(const char* filename) {
    struct stat st;
    stat(filename, &st);
    return st.st_size;   
}

void binarySearch(const PairArray* const pairArray, 
    uint16_t numElements, uint32_t key, uint32_t*& value) {

    int mid = numElements/2;

    if (numElements == 0) return;

    // interpret the pair as an array of 4 byte key and value
    const Pair* pair = reinterpret_cast<const Pair*>(&(pairArray->index[mid]));

    // new pointer to pass into recursive call
    const PairArray* const newPairArray = reinterpret_cast<const PairArray* const>(
        &(pairArray->index[mid + 1]));

    // if key is found, point pointer passed by reference to value
    if (key == pair->index[0]) {
        value = const_cast<uint32_t*>(&pair->index[1]);
        return;
    } 

    // if search key is less than current key, binary search on left subarray
    else if (key < pair->index[0]) {
        binarySearch(pairArray, mid, key, value);
    } 

    // otherwise, binary search on right subarray
    else (numElements%2 == 0) 
            ? binarySearch(newPairArray, mid - 1, key, value)
            : binarySearch(newPairArray, mid, key, value);
}

int main(int argc, char** argv) {

    ...

    // get size of the file
    size_t filesize = getFilesize(argv[1]);

    // open file
    int fd = open(argv[1], O_RDWR, 0);
    if (fd < 0) {
        std::cerr << "error: file could not be opened" << std::endl;
        exit(EXIT_FAILURE);
    }   

    // execute mmap:
    char* mmappedData = static_cast<char*>(
        mmap(NULL, filesize, PROT_WRITE|PROT_READ, MAP_SHARED, fd, 0));
    if (mmappedData == NULL) {
        std::cerr << "error: could not memory map file" << std::endl;
        exit(EXIT_FAILURE);
    }

    // interpret the memory mapped file as an array of 8 byte pairs
    const PairArray* const pairArray = reinterpret_cast<PairArray*>(mmappedData);

    // spin until file is unlocked, and take lock for yourself
    while(true) {
        int gotLock = flock(fd, LOCK_SH);
        if (gotLock == 0) break;
    }

    // binary search for key value pair
    uint32_t* value = nullptr;
    binarySearch(pairArray, filesize/8, key, value);
    (value == nullptr)
        ? std::cout << "null" << std::endl 
        : std::cout << *value << std::endl;

    // release lock
    flock(fd, LOCK_UN);

    ...

}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-04-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-06-16
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多