我遇到了一个问题:我需要根据文件路径前缀节省空间的文件系统数据查找。换句话说,前缀搜索已排序的文本。你说,使用trie,我也想到了同样的事情。麻烦的是,尝试不够节省空间,没有其他技巧。
我有相当数量的数据:
我不想在内存中接近450M的任何地方吃东西。在这一点上,我很乐意在大约100M左右使用,因为前缀形式有很多冗余。
我正在使用C#来完成这项工作,并且直接实现trie仍然需要为文件中的每一行提供一个叶子节点。假定每个叶子节点都需要对最后一个文本块进行某种引用(32位,比如指向一个字符串数据数组的索引以最小化字符串重复),并且CLR对象开销是8个字节(使用windbg / SOS验证) ,我将在结构开销上花费> 96,000,000字节而根本没有文本存储。
让我们看一下数据的一些统计属性。当塞进一个特里:
叶片产生的过剩率约为15%,过多的内部节点创造率为22% - 过度创造,我的意思是在构造期间创建的叶子和内部节点,但不是最终的trie作为最终节点数量的比例每种类型。
这是来自SOS的堆分析,指出了大部分内存的使用位置:
[MT ]--[Count]----[ Size]-[Class ]
03563150 11 1584 System.Collections.Hashtable+bucket[]
03561630 24 4636 System.Char[]
03563470 8 6000 System.Byte[]
00193558 425 74788 Free
00984ac8 14457 462624 MiniList`1+<GetEnumerator>d__0[[StringTrie+Node]]
03562b9c 6 11573372 System.Int32[]
*009835a0 1456066 23297056 StringTrie+InteriorNode
035576dc 1 46292000 Dictionary`2+Entry[[String],[Int32]][]
*035341d0 1456085 69730164 System.Object[]
*03560a00 1747257 80435032 System.String
*00983a54 8052746 96632952 StringTrie+LeafNode
Dictionary<string,int>
用于将字符串块映射到索引到List<string>
,并且可以在构造之后丢弃,尽管GC似乎没有删除它(一些显式集合)在此转储之前完成了 - 在SOS中!gcroot
没有表明任何根,但我预计后来的GC会释放它。
MiniList<T>
使用精确大小(即线性增长,List<T>
添加性能)O(n^2)
替换T[]
以避免空间浪费;它是一种值类型,InteriorNode
用于跟踪子项。此T[]
已添加到System.Object[]
堆中。
所以,如果我把“有趣”的项目(用*
标记),我得到大约270M,这比磁盘上的原始文本更好,但仍然不够接近我的目标。我认为.NET对象开销过多,并创建了一个新的“苗条”trie,只使用值类型数组来存储数据:
class SlimTrie
{
byte[] _stringData; // UTF8-encoded, 7-bit-encoded-length prefixed string data
// indexed by _interiorChildIndex[n].._interiorChildIndex[n]+_interiorChildCount[n]
// Indexes interior_node_index if negative (bitwise complement),
// leaf_node_group if positive.
int[] _interiorChildren;
// The interior_node_index group - all arrays use same index.
byte[] _interiorChildCount;
int[] _interiorChildIndex; // indexes _interiorChildren
int[] _interiorChunk; // indexes _stringData
// The leaf_node_index group.
int[] _leafNodes; // indexes _stringData
// ...
}
这种结构将数据量降低到139M,并且仍然是只读操作的高效可遍历的trie。而且因为它非常简单,我可以将它简单地保存到磁盘并恢复它,以避免每次都重新创建trie的成本。
那么,对于前缀搜索比trie更有效的结构的任何建议?我应该考虑的替代方法?
答案 0 :(得分:2)
由于只有110万个块,您可以使用24位而不是32位来索引块,并在那里节省空间。
你也可以压缩块。也许Huffman coding是个不错的选择。我还会尝试以下策略:您应该编码字符转换,而不是使用字符作为符号进行编码。因此,不要查看出现角色的概率,而应查看状态为当前角色的Markov chain中的过渡概率。
答案 1 :(得分:1)
您可以找到与您的问题相关的科学论文here(作者引用:“实验表明,我们的索引支持空间占用内的快速查询,该查询通过压缩字符串字典来实现gzip,bzip或ppmdi。“ - 但不幸的是这篇论文只是付款。我不确定这些想法有多难实现。本文的作者有一个website,您可以在其中找到各种压缩索引算法的实现(在“索引集合”下)。
如果您想继续使用自己的方法,请务必查看有关Crit-bit trees和Radix tree的网站。
答案 2 :(得分:0)
离题的想法:而不是一个哈希表。你在内存中只有哈希和字符串数据,可能是压缩的。
或者你能买得起一页吗?只有内存中的散列和文件位置,使用与该散列匹配的行检索“页面”,可能是有序行数较少,因此在发生冲突时可以非常快速地进行搜索。