关于sqlite:如何压缩小字符串

How to compress small strings

我有一个包含大量URL的sqlite数据库,它占用了大量的磁盘空间,访问它会导致许多磁盘查找,而且速度很慢。URL路径的平均长度是97字节(主机名重复很多,所以我将它们移到了一个外键表中)。有没有压缩它们的好方法?大多数压缩算法都能很好地处理大文档,而不是平均小于100字节的"文档",但即使减少20%也非常有用。有什么有效的压缩算法吗?不需要任何标准。


使用压缩算法,但使用共享字典。

我以前做过类似的事情,在这里我使用了lzc/lzw算法,正如unix compress命令所使用的那样。

使用短字符串进行良好压缩的诀窍是使用由正在压缩的URL的标准示例组成的字典。

你应该很容易得到20%。

编辑:LZC是LZW的变体。您只需要lzw,因为您只需要一个静态字典。LZC增加了对字典/表满后重置的支持。


我用下面的策略尝试了这个方法。它使用的是一个共享字典,但是围绕着python的zlib不允许您访问字典本身的方式工作。

首先,通过运行一组训练字符串初始化预先训练的压缩器和解压器。丢弃输出字符串。

然后,使用经过训练的压缩器的副本来压缩每个小字符串,并使用解压器的副本来解压它们。

这里是我的python代码(为丑陋的测试道歉):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import zlib
class Trained_short_string_compressor(object):
    def __init__(self,
                 training_set,
                 bits = -zlib.MAX_WBITS,
                 compression = zlib.Z_DEFAULT_COMPRESSION,
                 scheme = zlib.DEFLATED):
        # Use a negative number of bits, so the checksum is not included.
        compressor = zlib.compressobj(compression,scheme,bits)
        decompressor = zlib.decompressobj(bits)
        junk_offset = 0
        for line in training_set:
            junk_offset += len(line)
            # run the training line through the compressor and decompressor
            junk_offset -= len(decompressor.decompress(compressor.compress(line)))

        # use Z_SYNC_FLUSH. A full flush seems to detrain the compressor, and
        # not flushing wastes space.
        junk_offset -= len(decompressor.decompress(compressor.flush(zlib.Z_SYNC_FLUSH)))

        self.junk_offset = junk_offset
        self.compressor = compressor
        self.decompressor = decompressor

    def compress(self,s):
        compressor = self.compressor.copy()
        return compressor.compress(s)+compressor.flush()

    def decompress(self,s):
        decompressor = self.decompressor.copy()
        return (decompressor.decompress(s)+decompressor.flush())[self.junk_offset:]

通过测试,我在一组10000个短(50->300个字符)的Unicode字符串上节省了30%以上。压缩和解压缩也需要大约6秒(相比之下,使用简单的zlib压缩/解压缩大约需要2秒)。另一方面,简单的zlib压缩节省了大约5%,而不是30%。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
def test_compress_small_strings():
    lines =[l for l in gzip.open(fname)]
    compressor=Trained_short_string_compressor(lines[:500])

    import time
    t = time.time()
    s = 0.0
    sc = 0.
    for i in range(10000):
        line = lines[1000+i] # use an offset, so you don't cheat and compress the training set
        cl = compressor.compress(line)
        ucl = compressor.decompress(cl)
        s += len(line)
        sc+=len(cl)
        assert line == ucl

    print 'compressed',i,'small strings in',time.time()-t,'with a ratio of',s0/s
    print 'now, compare it ot a naive compression '
    t = time.time()
    for i in range(10000):
        line = lines[1000+i]
        cr = zlib.compressobj(zlib.Z_DEFAULT_COMPRESSION,zlib.DEFLATED,-zlib.MAX_WBITS)
        cl=cr.compress(line)+cr.flush()
        ucl = zlib.decompress(cl,-zlib.MAX_WBITS)
        sc += len(cl)
        assert line == ucl


    print 'naive zlib compressed',i,'small strings in',time.time()-t, 'with a ratio of ',sc/s

注意,如果删除它,它就不会持久。如果你想要坚持,你就必须记住训练集。


你考虑过使用静态哈夫曼编码吗?

您可以使用现有的URL体,根据其频率计算URL中出现的所有字节的哈夫曼代码。然后您可以将这组代码存储一次,并使用它对所有URL进行编码。我觉得它应该能给你很好的压缩。


你的网址是什么格式?

如果任何一个URL共享一个或多个域,并且您拥有大约20亿个域名,那么您可以为域名创建一个池。如果您共享了相对路径,那么可以将它们汇集到一起。

对于数据库中的每个URL,将每个URL分为三部分。方案和域,例如http://my domain.com、realtive url/my/path/和rest mypage.html?id=4(如果有查询字符串参数)

这样,您应该将每个域和相对路径的开销减少到8字节左右。如果你想查找URL的一部分,那就应该更好、更快。

注意:只有"http"方案字符串本身是4个字节,您将在每个域条目上保存超出该值的任何内容。如果每个URL都以"http://www."开头,则每次保存":/www."7个字节。

尝试一下如何拆分和构造URL,我敢打赌这是您将找到压缩的地方。现在,剩下的字符串不是公共域或相对路径,您可以用它做什么?

压缩URL

一般用途的压缩,这种方法是从算术编码派生出来的。信息理论之父香农(Shannon)在60年代写了一篇关于这一点的论文。我从事压缩工作已有一段时间了,我一直发现,通用压缩永远无法解决实际问题。

您很幸运,因为URL具有结构,您应该使用该结构来更好地存储您的URL。

如果要应用压缩算法(我认为应该更改主题以反映URL压缩,因为它是特定于域的),则必须检查数据的熵。因为它会告诉你一些关于存储量的信息。URL是ASCII字符,任何不在ASCII范围0x20-0x7e内的字符都不会发生,并且会丢弃区分大小写的能力,您只剩下63个不同的状态。!"%&;'()*+,-/0123456789:;<=>?@abcdefghijklmnopqrstuvwxyz包括空格。

您可以创建剩余字符的频率表并执行算术编码。您知道您最多需要6位,这意味着对于URL数据库中的每一个字符,您现在都在浪费2位,如果您只是将内容转移到适当的位置并使用查找表,您将得到20%的压缩。就像那样;)

因为数据非常具体,所以只使用通用方法进行压缩并不是一个好主意。最好是对信息进行结构化,并将其拆分为可以更有效地存储的数据片段。你对这个领域了解很多,用这些知识来压缩你的数据。


文摘:

大型搜索引擎和网络蜘蛛的一个常见问题是如何处理大量遇到的URL。传统的搜索引擎和网络蜘蛛使用硬盘来存储URL而不进行任何压缩。这会导致性能降低和空间需求增加。本文描述了一个简单的URL压缩算法,允许有效的压缩和解压缩。压缩算法基于增量编码方案,提取共享公共前缀的URL,并利用AVL树实现高效的搜索速度。实验结果表明,该方法可使尺寸减小50%。1。

--Kasom Koht Arsa计算机工程系。

资源


如何使用URL表?

您通常只进行"范围扫描"或唯一ID查找吗?

如果你不做像WHERE url like"/xxxx/question/%"这样的事情。可以使用哈希索引,而不是varchar()上的B树索引来减少磁盘查找的次数。


是97字节,还是97 8位ASCII字符,还是97 16位Unicode字符?

假设您的所有URL都是符合http://www.w3.org/addressing/url/url-spec.txt的合法URL,那么您应该只有ASCII字符。

如果仅存储每个字符的低位字节的97个16位Unicode字符将自动为您节省50%。

如果是97个8位字符,请注意您只需要7位。您可以简单地将7位一次传入您的位流,并将该位流存储到数据库中;使用一些旧的7位传输协议;或者使用您自己的特殊方法将每个8位字符的位存储在前7个字符的高位。