我有一个在磁盘阵列的字符串创建索引的非常普遍的问题。 总之,我需要存储在中盘表示每个字符串的位置。 例如,如下一个非常幼稚的解决办法是索引阵列:
UINT64 IDX [] = {0,20,500,1024,...,103434};
它说的是,第一字符串是在位置0,第二个20位,第三,在500位置和103434位置的第n个。
位置总是按顺序非负64位整数。 虽然数字可以由任何差异而变化,但在实践我期望典型差为该范围的内部从2 ^ 8至2 ^ 20。 我期望这个索引来在存储器中mmap'ed,和位置将被随机访问(假定均匀分布)。
我在想我自己写的代码做某种块增量编码或其它更复杂的编码,但也有编码/解码速度和空间之间这么多不同的取舍,我宁愿得到一个工作库为起点甚至可能解决的东西,没有任何自定义。
任何提示? C库将是理想的,但C ++的一个也可以让我运行一些初始的基准。
如果你还在下面的详细原因。 这将被用来建立类似于CDB库( http://cr.yp.to/cdb/cdbmake.html顶部的库CMPH() http://cmph.sf.net )。 总之,它是基于一个大的磁盘读取只在内存小的指数关联地图。
既然是一个图书馆,我没有在输入控件,但我要优化典型的用例有数亿值的,在几KB平均值尺寸范围在2 ^ 31最大值。
为了记录在案,如果我没有找到现成使用图书馆,我打算实施的64个整数与指定块到目前为止抵消初始字节的块增量编码。 该块本身将与树进行索引,让我O(日志(N / 64))的访问时间。 有太多的其他选择,我宁愿不讨论这些问题。 我真的很期待准备使用的代码,而不是如何实现编码的想法。 我会很高兴和大家一起分享我做什么,一旦我有工作。
我感谢您的帮助,让我知道,如果您有任何疑问。
我用fastbit (咳声吴LBL.GOV),看来你需要的东西好,速度快而现在,所以fastbit是Oracle的BBC(字节对齐位码,BerkeleyDB的)高度competient改善。 这很容易安装和非常好的gernally。
然而,考虑到更多的时间,你可能想看看在格雷码的解决方案,它似乎是最适合你的目的。
丹尼尔·勒迈尔有一些关于发布了C / ++ / Java库code.google ,我读了他的一些论文和他们在fastbit相当不错,几个进步和替代方法列重新排序与置换的灰色代码的。
差点忘了,我也碰到东京内阁 ,但我不认为这将是非常适合我目前的项目,我可能更多的考虑,如果我以前知道这件事),它有一个很大程度的互操作性,
东京内阁是用C语言,如C,Perl中,红宝石,爪哇,和Lua的API提供。 东京柜可以用具有API符合C99和POSIX平台。
至于你提到CDB的TC基准具有TC模式(TC支持的几种操作约束对不同PERF),它超越了国家开发银行10倍的读取性能和2倍的写入。
对于您的增量编码的要求,我在相当有信心bsdiff和它的到外进行任何的file.exe内容修补系统的能力,它也可能对您的日常需求的一些fundimental接口。
谷歌的新的二进制压缩应用程序, 胡瓜可能是值得一试,如果你错过了新闻稿,10级小差异的比在一个测试情况下,我已经看到了公布bsdiff。
你有两个相互矛盾的需求:
- 要压缩非常小件物品(每个8个字节)。
- 你需要为每个项目高效的随机访问。
第二个要求是非常有可能施加一个固定长度的每个项目。
究竟什么是你想压缩? 如果你正在考虑指数的总空间,是不是真的值得节省空间的努力?
如果是这样一两件事你可以尝试是砍空间分成一半,将其存储到两个表。 第一存储(上部UINT,开始索引,长度,指针到第二表)和第二将存储(索引,低级UINT)。
为了快速搜索,索引将使用类似实施B +树 。
我做了一件类似几年前的全文搜索引擎。 在我的情况下,每个索引词产生其中包括创纪录的数字(文档ID)和字号码(它可以很容易地存储了字偏移),它需要被压缩,尽可能的记录。 我用它注意到这一事实,将有一批文档中的同一个词的出现的优势增量压缩技术,这样的记录数往往并不需要在所有的重复。 和字偏移量增量往往会适合一个或两个字节内。 下面是我使用的代码。
因为它是在C ++中,代码可能不会对你有用原样,但可以写压缩例程一个很好的起点。
请原谅匈牙利命名法和幻数的代码中散落。 就像我说的,我这个在很多年前写的:-)
IndexCompressor.h
//
// index compressor class
//
#pragma once
#include "File.h"
const int IC_BUFFER_SIZE = 8192;
//
// index compressor
//
class IndexCompressor
{
private :
File *m_pFile;
WA_DWORD m_dwRecNo;
WA_DWORD m_dwWordNo;
WA_DWORD m_dwRecordCount;
WA_DWORD m_dwHitCount;
WA_BYTE m_byBuffer[IC_BUFFER_SIZE];
WA_DWORD m_dwBytes;
bool m_bDebugDump;
void FlushBuffer(void);
public :
IndexCompressor(void) { m_pFile = 0; m_bDebugDump = false; }
~IndexCompressor(void) {}
void Attach(File& File) { m_pFile = &File; }
void Begin(void);
void Add(WA_DWORD dwRecNo, WA_DWORD dwWordNo);
void End(void);
WA_DWORD GetRecordCount(void) { return m_dwRecordCount; }
WA_DWORD GetHitCount(void) { return m_dwHitCount; }
void DebugDump(void) { m_bDebugDump = true; }
};
IndexCompressor.cpp
//
// index compressor class
//
#include "stdafx.h"
#include "IndexCompressor.h"
void IndexCompressor::FlushBuffer(void)
{
ASSERT(m_pFile != 0);
if (m_dwBytes > 0)
{
m_pFile->Write(m_byBuffer, m_dwBytes);
m_dwBytes = 0;
}
}
void IndexCompressor::Begin(void)
{
ASSERT(m_pFile != 0);
m_dwRecNo = m_dwWordNo = m_dwRecordCount = m_dwHitCount = 0;
m_dwBytes = 0;
}
void IndexCompressor::Add(WA_DWORD dwRecNo, WA_DWORD dwWordNo)
{
ASSERT(m_pFile != 0);
WA_BYTE buffer[16];
int nbytes = 1;
ASSERT(dwRecNo >= m_dwRecNo);
if (dwRecNo != m_dwRecNo)
m_dwWordNo = 0;
if (m_dwRecordCount == 0 || dwRecNo != m_dwRecNo)
++m_dwRecordCount;
++m_dwHitCount;
WA_DWORD dwRecNoDelta = dwRecNo - m_dwRecNo;
WA_DWORD dwWordNoDelta = dwWordNo - m_dwWordNo;
if (m_bDebugDump)
{
TRACE("%8X[%8X] %8X[%8X] : ", dwRecNo, dwRecNoDelta, dwWordNo, dwWordNoDelta);
}
// 1WWWWWWW
if (dwRecNoDelta == 0 && dwWordNoDelta < 128)
{
buffer[0] = 0x80 | WA_BYTE(dwWordNoDelta);
}
// 01WWWWWW WWWWWWWW
else if (dwRecNoDelta == 0 && dwWordNoDelta < 16384)
{
buffer[0] = 0x40 | WA_BYTE(dwWordNoDelta >> 8);
buffer[1] = WA_BYTE(dwWordNoDelta & 0x00ff);
nbytes += sizeof(WA_BYTE);
}
// 001RRRRR WWWWWWWW WWWWWWWW
else if (dwRecNoDelta < 32 && dwWordNoDelta < 65536)
{
buffer[0] = 0x20 | WA_BYTE(dwRecNoDelta);
WA_WORD *p = (WA_WORD *) (buffer+1);
*p = WA_WORD(dwWordNoDelta);
nbytes += sizeof(WA_WORD);
}
else
{
// 0001rrww
buffer[0] = 0x10;
// encode recno
if (dwRecNoDelta < 256)
{
buffer[nbytes] = WA_BYTE(dwRecNoDelta);
nbytes += sizeof(WA_BYTE);
}
else if (dwRecNoDelta < 65536)
{
buffer[0] |= 0x04;
WA_WORD *p = (WA_WORD *) (buffer+nbytes);
*p = WA_WORD(dwRecNoDelta);
nbytes += sizeof(WA_WORD);
}
else
{
buffer[0] |= 0x08;
WA_DWORD *p = (WA_DWORD *) (buffer+nbytes);
*p = dwRecNoDelta;
nbytes += sizeof(WA_DWORD);
}
// encode wordno
if (dwWordNoDelta < 256)
{
buffer[nbytes] = WA_BYTE(dwWordNoDelta);
nbytes += sizeof(WA_BYTE);
}
else if (dwWordNoDelta < 65536)
{
buffer[0] |= 0x01;
WA_WORD *p = (WA_WORD *) (buffer+nbytes);
*p = WA_WORD(dwWordNoDelta);
nbytes += sizeof(WA_WORD);
}
else
{
buffer[0] |= 0x02;
WA_DWORD *p = (WA_DWORD *) (buffer+nbytes);
*p = dwWordNoDelta;
nbytes += sizeof(WA_DWORD);
}
}
// update current setting
m_dwRecNo = dwRecNo;
m_dwWordNo = dwWordNo;
// add compressed data to buffer
ASSERT(buffer[0] != 0);
ASSERT(nbytes > 0 && nbytes < 10);
if (m_dwBytes + nbytes > IC_BUFFER_SIZE)
FlushBuffer();
CopyMemory(m_byBuffer + m_dwBytes, buffer, nbytes);
m_dwBytes += nbytes;
if (m_bDebugDump)
{
for (int i = 0; i < nbytes; ++i)
TRACE("%02X ", buffer[i]);
TRACE("\n");
}
}
void IndexCompressor::End(void)
{
FlushBuffer();
m_pFile->Write(WA_BYTE(0));
}
你已经忽略你打算指数字符串的一些关键信息。
但考虑到你说你希望索引的字符串的最小长度为256,存储指数为64%, 至多 3%的开销招致。 如果字符串文件的总长度小于4GB,你可以使用32位的索引和收取1.5%的开销。 这些数字表明对我说,如果压缩的问题, 你就要去压缩字符串,而不是指数更好 。 对于这个问题上LZ77的变化似乎是为了。
如果你想尝试一个大胆的想法,把每串在一个单独的文件,它们全部拉成一个zip文件,看你怎么可以做zziplib
。 这可能不会很大,但它是在您的部分几乎为零的工作。
对这个问题的更多数据将受到欢迎:
- 弦数
- 字符串的平均长度
- 字符串的最大长度
- 字符串的长度中位数
- 程度的字符串文件压缩与
gzip
- 无论您是允许更改的字符串,以提高压缩的顺序
编辑
注释和修订问题使问题更加清晰。 我喜欢你的分组的想法,我会尝试一个简单的增量编码,组三角洲和各组内使用可变长度编码。 我会在64线不作为组大小,我想你可能会想,以确定经验。
你问对现有库。 对于分组和增量编码我怀疑你会发现很多。 对于可变长度整数代码,我没有看到太多的C库的方式,但你可以找到在可变长度值编码的Perl和Python的 。 有一吨的文件和关于这一主题的一些专利,我怀疑你会风有推出自己的。 但也有一些简单的代码在那里,你可以给UTF-8一试,它可以编码无符号整数高达32位,你可以从抢C代码计划9 ,我敢肯定,许多其他来源。
你是在Windows上运行? 如果是的话,我建议使用幼稚的解决方案的最初提议创建MMAP文件,然后压缩使用NTLM压缩文件。 您的应用程序代码从来不知道文件被压缩,而OS的确文件压缩为您服务。 你可能不认为这将是非常高性能或获得良好的压缩,但我认为,如果你尝试了,你会感到惊讶。