C 最适合基于前缀的搜索的数据结构
我必须维护一个键值对的内存数据结构。我有以下限制:C 最适合基于前缀的搜索的数据结构,c,regex,algorithm,data-structures,hash,C,Regex,Algorithm,Data Structures,Hash,我必须维护一个键值对的内存数据结构。我有以下限制: 键和值都是长度为256和1024的文本字符串 分别地任何键通常看起来像k1k2k3k4k5,每个k(i)本身就是4-8字节的字符串 内存中的数据结构应尽可能具有连续内存。我有400 MB的键值对,允许分配120%的值。(元数据额外增加20%,仅在需要时。) DS将进行以下操作: 添加[不频繁操作]:典型的签名看起来像void Add_kv(void*ds,char*key,char*value) 删除[不频繁操作]:典型的签名看起来像void
void Add_kv(void*ds,char*key,char*value)代码>
void del_kv(void*ds,char*key)代码>
char*LookUp(void*ds,char*key)代码>
void*iterate(void*ds,char*prefix\u key)代码>
我已经考虑过了。添加/删除/查找可以在o(1)中完成,因为我有足够的内存,但它不是迭代的最佳选择。散列的散列(先在k1上散列,然后在k2上散列,然后在k3上散列…)或散列数组可以完成,但它违反了项目符号2。我还有什么其他选择?我可能会使用类似于B+树的东西来实现这一点: 由于内存效率对您很重要,当一个叶块满时,您应该尽可能在几个块之间重新分配键,以确保块始终>=85%满。块大小应足够大,以便内部节点的开销仅为几% 您还可以优化叶块中的存储,因为块中的大多数键都有一个很长的公共前缀,您可以从更高级别的块中找出它。因此,您可以从叶块中的键中删除公共前缀的所有副本,400MB的键值对占用的RAM将大大少于400MB。这将使插入过程稍微复杂化
为了进一步压缩此结构,您还可以做其他事情,但这会很快变得复杂,而且听起来您并不需要它。我会将此实现为一个哈希表用于查找,并为您的迭代实现一个单独的哈希表。我认为尝试将这些独立的键段转换成整数,就像你要求的那样,是一堆不必要的工作 已经有很多很好的C哈希表实现可用,所以我就不赘述了 要为迭代创建反向索引,请创建N个哈希表,其中N是键段数。然后,对于每个键,将其分解为单独的段,并将该值的条目添加到相应的哈希表中。因此,如果您有“abcxyzqgx”键,其中: 然后在k1哈希表中添加一个条目“abc=abcxyzqgx”。在k2哈希表中添加一个条目“xyz=abcxyzqgx”。在k3哈希表中添加“qgx=abcxyzqgx”。(当然,这些值不是字符串键本身,而是对字符串键的引用。否则,您将有O(nk)256个字符串。) 完成后,哈希表中的每个表都有唯一的段值作为键,这些值是这些段所在的键列表 如果要查找所有具有k1=abc和k3=qgx的键,请查询k1哈希表以获取包含abc的键列表,查询k3哈希表以获取包含qgx的键列表。然后将这两个列表相交以获得结果 构建各个散列表是O(nk)的一次性成本,其中n是键的总数,k是键段的数目。内存需求也是O(nk)。当然,这有点贵,但你说的总共只有160万把钥匙 迭代的情况是O(m*x),其中m是单个键段引用的键的平均数,x是查询中的键段数 一个明显的优化是在查找之前放置一个LRU缓存,以便从缓存中提供频繁的查询
另一种可能的优化是创建组合键的附加索引。例如,如果查询经常要求k1和k2,并且可能的组合相当小,那么使用组合的k1k2缓存是有意义的。因此,如果有人搜索k1=abc和k2=xyz,那么您有一个包含“abcxyz=[密钥列表]”的k1k2缓存。我将使用五个并行哈希表,对应于可能搜索的五个前缀。每个哈希表槽将包含零个或多个引用,每个引用包含该特定键值对的前缀长度、该键值前缀的哈希以及指向实际键和数据结构的指针 对于删除,实际的键和数据结构将包含所有五个前缀长度和相应的哈希,以及键和值的字符数据 例如:
#define PARTS 5
struct hashitem {
size_t hash[PARTS];
size_t hlen[PARTS];
char *data;
char key[];
};
struct hashref {
size_t hash;
size_t hlen;
struct hashitem *item;
};
struct hashrefs {
size_t size;
size_t used;
struct hashref ref[];
};
struct hashtable {
size_t size[PARTS];
struct hashrefs **slot[PARTS];
};
在struct hashitem
中,如果key
是k1k3k4k5
,那么hlen[0]=2
,hash[0]=hash(“k1”)
,hlen[1]=4
,hash[1]=hash(“k1k2k2”)
,等等,直到hlen[4]=10
,hash[4]=hash(“k3k5”)
插入新的键值对时,首先要找出前缀长度(hlen[]
)
#define PARTS 5
struct hashitem {
size_t hash[PARTS];
size_t hlen[PARTS];
char *data;
char key[];
};
struct hashref {
size_t hash;
size_t hlen;
struct hashitem *item;
};
struct hashrefs {
size_t size;
size_t used;
struct hashref ref[];
};
struct hashtable {
size_t size[PARTS];
struct hashrefs **slot[PARTS];
};
static int insert_pair(struct hashtable *ht,
const char *key,
const size_t hash[PARTS],
const size_t hlen[PARTS],
const char *data,
const size_t datalen)
{
struct hashitem *item;
size_t p, i;
/* Verify the key is not already in the
hash table. */
/* Allocate 'item', and copy 'key', 'data',
'hash', and 'hlen' to it. */
for (p = 0; p < PARTS; p++) {
i = hash[p] % ht->size[p];
if (!ht->entry[i]) {
/* Allocate a new hashrefs array,
with size=1 or greater, initialize
used=0 */
} else
if (ht->entry[i].used >= ht->entry[i].size) {
/* Reallocate ht->entry[i] with
size=used+1 or greater */
}
ht->entry[i].ref[ht->entry[i].used].hash = hash[p];
ht->entry[i].ref[ht->entry[i].used].hlen = plen[p];
ht->entry[i].ref[ht->entry[i].used].item = item;
ht->entry[i].used++;
}
return 0; /* Success, no errors */
}
int lookup_filter(struct hashtable *ht,
const size_t hash,
const size_t hashlen,
const size_t parts, /* 0 to PARTS-1 */
const char *key,
int (*func)(struct hashitem *, void *),
void *custom)
{
const struct hashrefs *refs = ht->entry[parts][hash % ht->size[parts]];
int retval = -1; /* None found */
size_t i;
if (!refs)
return retval;
for (i = 0; i < refs->used; i++)
if (refs->ref[i].hash == hash &&
refs->ref[i].hlen == hashlen &&
!strncmp(refs->ref[i].item->key, key, hashlen)) {
if (func) {
retval = func(refs->ref[i].item, custom);
if (retval)
return retval;
} else
retval = 0;
}
return retval;
}
struct hashitem *lookup(struct hashtable *ht,
const size_t hash,
const size_t hashlen,
const char *key)
{
const struct hashrefs *refs = ht->entry[PARTS-1][hash % ht->size[PARTS-1]];
size_t i;
if (!refs)
return NULL;
for (i = 0; i < refs->used; i++)
if (refs->ref[i].hash == hash &&
refs->ref[i].hlen == hashlen &&
!strncmp(refs->ref[i].item->key, key, hashlen))
return refs->ref[i].item;
return NULL;
}