C++ 在文件中存储大量由分隔符分隔的整数键值对的最有效方法

C++ 在文件中存储大量由分隔符分隔的整数键值对的最有效方法,c++,file,file-io,integer,mmap,C++,File,File Io,Integer,Mmap,我有一个系统,其中两个程序同时读写一个包含大量无符号整数对的文件。该键可以有一个值[0,2^16],该值可以有一个值[0,2^32]。我目前正在将这些整数作为字符存储在文件中,其中每对都在自己的行上,键和值由一个空格分隔,所有值都有10个字符 这10个字符的原因有两个:1.最大的无符号整数(解释为字符)长度为10个字符;2.我正在使用mmap将文件映射到两个程序的内存中,使用map_SHARED标志。当写入文件的程序第一次运行时,它将线性扫描文件并构造一个哈希映射映射指向值的指针的键。当编写器希

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

这10个字符的原因有两个:1.最大的无符号整数(解释为字符)长度为10个字符;2.我正在使用mmap将文件映射到两个程序的内存中,使用map_SHARED标志。当写入文件的程序第一次运行时,它将线性扫描文件并构造一个哈希映射映射指向值的指针的键。当编写器希望将新的键-值对放入文件时,它将在哈希映射中搜索该键,如果该键存在,它将覆盖存储在与该键关联的地址中的值。我确保所有值在文件中存储为10个字符,较小的值将填充到该文件中ht使用空格,这样编写器就可以始终简单地覆盖该地址的值,而不必再次对内存、munmap和mmap进行疯狂的洗牌

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

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

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

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


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

    数据存储: 我建议您以二进制形式存储数据,这将在一定程度上减少文件大小

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


    搜索:由于
    mmap()
    ing将提供一些指针来处理数据,因此您应该能够将其作为原始C样式数组使用,或者将其包装在中并与标题功能一起使用。

    我最终实现了一个我正在寻找的解决方案,该解决方案具有以下功能:

  • 密钥-值对以二进制格式存储在文件中,密钥为4字节,值为4字节

  • 该文件只包含键值对,因此该文件只是一个没有分隔符或额外绒毛的键值对流

  • 读写程序都可以搜索一个键并在日志(n)时间内检索相应的值。这是通过将二进制文件重新解释为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);
    
        ...
    
    }
    
    struct Pair{uint32_t index[];};
    结构PairArray{uint64_t index[];};
    大小\u t getFilesize(常量字符*文件名){
    结构统计;
    stat(文件名,&st);
    返回st.st_大小;
    }
    无效二进制搜索(常量PairArray*常量PairArray,
    uint16元素、uint32键、uint32键*&值){
    int mid=numElements/2;
    如果(numElements==0)返回;
    //将该对解释为4字节密钥和值的数组
    const Pair*Pair=reinterpret_cast(&(pairArray->index[mid]);
    //传递到递归调用的新指针
    const PairArray*const newPairArray=重新解释(
    &(pairArray->index[mid+1]);
    //若找到键,则通过引用传递给值的点指针
    如果(键==对->索引[0]){
    value=const_cast(&pair->index[1]);
    返回;
    } 
    //若搜索键小于当前键,则在左侧子数组上进行二进制搜索
    else if(键index[0]){
    二进制搜索(pairArray、mid、key、value);
    } 
    //否则,在右侧子阵列上进行二进制搜索
    其他(数值%2==0)
    ?二进制搜索(newPairArray,mid-1,键,值)
    :binarySearch(newPairArray、mid、键、值);
    }
    int main(int argc,字符**argv){
    ...
    //获取文件的大小
    size_t filesize=getFilesize(argv[1]);
    //打开文件
    int fd=打开(argv[1],O_RDWR,0);
    如果(fd<0){
    
    std::cerr您能将当前的实现与需求分开一点吗?例如,列出您希望支持的操作类型?@KarolyHorvath现在,编写程序只需要设置一个键值对,而读取程序只需要获得一个与键值关联的值。但我认为允许wr是很好的正在使用程序删除一对。为什么文件的大小很重要?您是在磁盘空间严重受限的系统上,还是在内存非常有限的系统上?您是否在2^32个可能的值中有一个值,可以指定为
    NULL
    ?如果内存足够,2^16个密钥可以存储在一个数组中。谢谢您的建议。我有实现了一个解决方案,并回答了这个问题!