如何用1字节char实现字符串(并节省内存)

时间:2013-04-18 19:23:17

标签: .net string character-encoding

如何实现基于单字节的字符串?

应用程序使用大量单词 单词来自SQL并且是varchar(单字节) 每个单词也有Int32 ID 下载单词:

Dictionionary<Int32,string> 

表现。

问题是字典变得如此之大以至于会出现内存不足的异常 我们最终分割数据 该应用程序非常符合列表,因此不能选择针对每个请求点击SQL 数据库已经非常活跃 动态调入和调出字典不是一个选项 - 它绑定到ListView并且虚拟化工作很好 单词仅在晚上加载 - 用户只需要一个静态列表 他们使用这些词来搜索和处理其他数据,但他们不处理这些词。

因为char认为可以实现基于单字节的单词:

public class StringByte1252 : Object, IComparable, IComparable<StringByte1252>
{
    static Encoding win1252 = Encoding.GetEncoding("Windows-1252");

    public Int32 ID { get; private set; }
    public byte[] Bytes { get; private set; }

    public string Value { get { return win1252.GetString(Bytes); } }
    public Int32 Length { get { return Bytes.Length; } }

    public int CompareTo(object obj)
    {
        if (obj == null)
        {
            return 1;
        }
        StringByte1252 other = obj as StringByte1252;
        if (other == null)
        {
            throw new ArgumentException("A StringByte1252 object is required for comparison.", "obj");
        }
        return this.CompareTo(other);
    }
    public int CompareTo(StringByte1252 other)
    {
        if (object.ReferenceEquals(other, null))
        {
            return 1;
        }
        return string.Compare(this.Value, other.Value, StringComparison.OrdinalIgnoreCase);
    }
    public override bool Equals(Object obj)
    {
        //Check for null and compare run-time types.
        if (obj == null || !(obj is StringByte1252)) return false;
        StringByte1252 item = (StringByte1252)obj;
        return (this.Bytes == item.Bytes);
    }
    public override int GetHashCode() { return ID; }

    public StringByte1252(Int32 id, byte[] bytes) { ID = id; Bytes = bytes; } 
}

以上内容有效,但它的内存效率并不比

Dictionionary<Int32,string>

使用基于Int16的字符的字典实际上使用的内存略少。

我哪里出错?
字节数组是否比字节总和占用更多空间? 有没有办法实现单字节字符串?

4 个答案:

答案 0 :(得分:3)

数组在64位运行时中有大约50个字节的开销。在32位运行时中,它稍微少一些:可能是40个字节。有标准的.NET分配开销(64位运行时中的24个字节),然后是数组的所有元数据:维数,长度等。您不能通过使用单个字节数组来存储空间来节省内存字符串。

一种方法是分配一个非常大的字节数组,并将字符串存储在该数组中,UTF-8编码。您的词典变为Dictionary<int,int>Value是数组的索引。

我在文章Reducing Memory Required for Strings中展示了如何做到这一点。通过这种方式,我能够比正常的字符串分配节省大约50%。有关更多详细信息,请参阅文章。

另一个问题是Dictionary开销类似于每个条目24个字节。如果你有一大堆小物件,这是非常昂贵的。您可以考虑制作结构列表,按ID对其进行排序,以及使用二进制搜索。这不是Dictionary给你的O(1)访问,但对于用户界面,它可以足够快。那么你的开销就是每个条目8个字节。

结构将类似于:

struct WordEntry
{
    public readonly int Id;
    public readonly int IndexIntoStringTable;
}

答案 1 :(得分:1)

尽管char的大小是字节大小的两倍,但如果字符串很长,则只会在内存占用方面产生很大的差异。

内存以块为单位分配,例如16个字节(可能在平台和实现之间有所不同)。这意味着一个字符长的字符串可能会占用与六个字符长的字符串一样多的内存,因为它们都需要两个内存块来保存字符数据和字符串对象的开销。

由于字典中引用的开销,字符串对象的开销以及部分未使用的内存块的开销,在开销小于50%之前,需要在字符串中平均放置大约16个字符。 / p>

由于开销很大,仅通过减少数据大小就很难减少内存占用。

您可能会寻找一个解决方案,其中每个项目的开销较少,例如字符数据的一个巨型字符串(或字节数组),并为该大字符串中的每个字符串指定起始索引。

答案 2 :(得分:0)

由于构建数组的方式,字节数组肯定需要更多/更多的空间作为其内容的总和。阵列由特定大小的块组成。能够区分数组的不同元素,元素必须有固定的大小,因此数字不会混淆。这就像是说要存储最大为9999的十进制数,因此为了能够将它们“存储在一起”,您必须填充前导零的空白:1,5,32,1293,12 = 00010005003212930012。 一个词是由字符组成的。为了能够表示一个字符,您需要找到要使用的最小数量的可能字符,并从中定义数组的基本构造单元。 由于字母表中有26个字符,大写字母和小字符一起是52个字符,而其他符号可能会少于128个字符,导致您选择7位字符。内存由8位块组成 - 字节,因此您应该使用这些块并使用ASCII进行编码,或者找到一种操作数据的方法,每个字符只使用7位,并且每8个字符保存一个字节。我想有一些开放的解决方案,但我不知道。

字符串只是一个字符数组。在c中,字符串是byte []。 尝试使用byte []数组。

当我正在运行linux时,我觉得很难测试,但你也可以尝试使用:

byte[] bytes = Encoding.ASCII.GetBytes("Hello World!");

答案 3 :(得分:0)

得出结论,一个简单的结构是将char存储为单字节的最佳内存执行者。

这是我的假设 内存一次分配4个字节 单个变量末尾的字节或不在4字节边界的数组被浪费。

字节池消除了单个字上浪费的字节,但代价是池的起始点和字长的索引。

假设即使是Dictionionary Int32,字符串也有浪费 任何奇数长度的字符串将浪费16个字节。

考虑Int32单词索引 Int32浪费1个字节 在池的情况下,池索引只是Int32,所以很明显它不能容纳Int32字 对于.NET中的对象大小,有4 GB的限制 word加索引的最佳情况是8个字节 32(4GB) - 8 = 24
最大单词加索引计数为2 ** 24 = 16,777,216。

此结构使用索引中的一个字节作为一个字符 一个字节是一个字节。 结构不必存储长度,因为它可以从内容中获得长度。

public struct Word1252bytes 
{
    static Encoding win1252 = Encoding.GetEncoding("Windows-1252");
    private UInt64 packed;
    private byte[] bytes;
    public Int32 Key { get { return (Int32)(packed & ((1 << 24) - 1)); } }
    private byte[] Bytes
    {
        get
        {
            // yes a lot of work to salage just one byte out of the key 
            // but a byte is a byte and the design objective is size
            byte[] bytesT = new byte[Length];
            bytesT[0] = (byte)((packed >> 24) & ((1 << 8) - 1));
            for (int i = 0; i < bytes.Length; i++) bytesT[i + 1] = bytes[i];
            return bytesT;
        }
    }
    public Int32 Length { get { return bytes.Length + 1; } }
    public String Value
    {
        get
        {
            return win1252.GetString(Bytes);
        }
    }
    public Word1252bytes(UInt64 Packed, byte[] Bytes) 
    { 
        packed = Packed;
        bytes = Bytes;
    }
}

如何打包

pack32 = (UInt32)(bits24wordI) | ((UInt32)charB[0] << 24);
byte[] bytesT = new byte[bits8wLen - 1];
for (int i = 1; i < bits8wLen; i++) bytesT[i - 1] = charB[i];
iWordsList.Add(new Word1252bytes(pack32, bytesT));

它快速保湿。我的测试用例是600万字,Word1252bytes的构建和字典一样快(大约20秒)

尺寸比较。
使用字节池可以使用单词索引的字节作为字长 这将字长限制为256,但在字符串池中保存间隔。

上述结构在每个单词大小上赢得或与所述假设相关联。 索引是索引的大小。 对于Dict和上面的结构,它是32 对于字节池,它是64 - 单词索引和池索引 浪费是不在4字节边界上的字节。

        Index   Content Waste   Total
exactly 1               
dict16  dict16  32  16      16  64
word8   pool    64  8           72
word8   imbed   32  0       0   32

exactly 2               
dict16  dict16  32  32      0   64
word8   pool    64  16          80
word8   imbed   32  8       24  64

exactly 3               
dict16  dict16  32  48      16  96
word8   pool    64  24          88
word8   imbed   32  16      16  64

exactly 4               
dict16  dict16  32  64      0   96
word8   pool    64  32          96
word8   imbed   32  24      8   64

exactly 5               
dict16  dict16  32  80      16  128
word8   pool    64  40          104
word8   imbed   32  32  0   64

exactly 6               
dict 16 dict16  32  96      0   128
word 8  pool    64  48          112
word 8  imbed   32  40      24  96

exactly 7               
dict 16 dict16  32  112     16  160
word 8  pool    64  56          120
word 8  imbed   32  48      16  96

exactly 8               
dict 16 dict16  32  128     0   160
word 8  pool    64  64          128
word 8  imbed   32  56      8   96

exactly 9               
dict 16 dict16  32  144     16  192
word 8  pool    64  72          136
word 8  imbed   32  64      0   96

exactly 10              
dict 16 dict16  32  160      0  192
word 8  pool    64  80          144
word 8  imbed   32  72       24 128

exactly 254             
dict 16 dict16  32  4064    0   4096
word 8  pool    64  2032        2096
word 8  imbed   32  2024    24  2080

exactly 255             
dict 16 dict16  32  4080    16  4128
word 8  pool    64  2040        2104
word 8  imbed   32  2032    16  2080

exactly 256             
dict 16 dict16  32  4096    0   4128
word 8  pool    64  2048        2112
word 8  imbed   32  2040    8   2080

exactly 257             
dict 16 dict16  32  4112    16  4160
word 8  pool    64  2056        2120
word 8  imbed   32  2048    0   2080

字节池可以压缩的位置是搜索池中已存在的字符串。在小字符串上,池处于30%的劣势,因此需要具有高匹配率。在较大的字符串上,找到匹配的机会很低。问题是搜索时间。在列表上超过1,000,000甚至10毫秒的搜索时间是10,000秒= 2.78小时。