.net字符串类交替

由于我正在计划一个将许多数据保存在内存中的应用程序,因此,我希望拥有某种“紧凑”字符串类,至少其中一个包含的字符串格式不得大于该字符串的零终止ASCII版本。

您知道任何这样的字符串类实现吗?它应该具有一些实用程序功能,例如原始的字符串类。

编辑:

我需要对字符串进行排序并能够对其进行扫描,仅提及我将要使用的一些操作。

理想情况下,它将与System.String源兼容,因此基本的搜索和替换操作将优化应用程序的内存占用。

号码:

我可以有10万条记录,每条记录最多包含10个字符串,包含30-60个字符。 所以:

100000x10x60 = 60000000 = 57兆字符。 为什么不使用60兆公羊的内存而不是120兆公羊的内存呢? 操作会更快,一切都会更紧密。

树将用于搜索,但对我打算进行的正则表达式扫描没有帮助。

Daniel Mošmondor asked 2020-06-29T19:32:32Z
10个解决方案
51 votes

编辑:我现在有一个关于该主题的博客文章,其中有很多细节。


按照您的数字:

我可以有10万条记录,每条记录最多包含10个字符串,包含30-60个字符。

让我们开始添加对象开销-由于不可避免的对象开销和长度,字符串占用大约20个字节(IIRC-在64位CLR上可能更多)加上实际数据。 让我们再次做数学:

使用字符串:100万个对象(20 + 120字节)= 140MB

使用新类:100万个对象(20 + 60字节)= 80MB

当然仍然相差60MB,但比您预期的要小。 您仅节省42%的空间,而不是50%。

现在,您谈论的事情是更快的:鉴于CLR本身了解string,我怀疑第三方类无法满足某些操作的速度,因此您必须付出很多努力 努力使其他许多人保持相同的速度。 诚然,您将具有更好的缓存一致性,并且如果您可以忽略区域性问题,那么通过使所有比较顺序都可以节省一点时间。

为了60 MB,我不会打扰。 如今,这是一个微小的差异-考虑一下通过节省少量资金才能获得多少客户,以弥补使用两种不同的字符串类型所产生的大量额外成本。

综上所述,无论如何我还是很想将自己作为Edulinq之类的博客项目来实现。 不过,不要期待数周或数月的结果:)

编辑:我刚刚想到了另一个问题。 我们上面得到的数字实际上并不正确...因为字符串类很特殊。 它将数据直接嵌入到对象中-与除数组以外的任何其他数据类型不同,它不是固定的data实例的大小; 它根据其中的数据而变化。

编写自己的data类,您将无法做到这一点-您必须在该类中嵌入一个数组引用:

public class AsciiString
{
    private readonly byte[] data;
}

这意味着您需要为引用(32或64位CLR)额外增加4或8个字节,并且每个字符串需要一个数组对象的额外开销(16字节,IIRC)。

如果您像Java那样设计它,则使用子字符串可以重用现有的字节数组(可以共享两个字符串),但是在data952之内,您将需要一个额外的长度和偏移量。您还将失去一些缓存一致性的好处。

您可以只使用原始字节数组作为数据结构,并编写一堆扩展方法来对它们进行操作……但是那太可怕了,因为这样您就无法分辨普通字节数组和原本字节数组之间的区别了。 表示ASCII字符串。

另一种可能性是创建这样的结构:

struct AsciiString
{
    private readonly byte[] data;
    ...
}

这将有效地使您再次进行强类型输入,但是您需要考虑以下内容:

AsciiString x = new AsciiString();

最终将得到空的data参考。 您可以有效地将其视为x为空值,但这将是非常惯用的。

Jon Skeet answered 2020-06-29T19:35:27Z
13 votes

我实际上遇到过类似的问题,但是问题参数有些不同。我的应用程序处理两种类型的字符串-相对较短的字符串,其大小为60-100个字符,而较长的字符串为100-1000个字节(平均大约300个字符)。

我的用例还必须支持unicode文本,但是相对较少的字符串实际上具有非英语字符。

在我的用例中,我将每个String属性公开为一个本地String,但是底层的数据结构是一个字节[],其中包含unicode字节。

我的用例还需要对这些字符串进行搜索和排序,获取子字符串以及其他常见的字符串操作。 我的数据集以百万计。

基本实现如下所示:

byte[] _myProperty;

public String MyProperty
{

   get 
   { 
        if (_myProperty== null)
            return null;

        return Encoding.UTF8.GetString(value);
   }

   set
   { 
        _myProperty = Encoding.UTF8.GetBytes(value);

   }

}

即使您进行搜索和排序时,这些转换的性能命中率也相对较小(约为10-15%)。

暂时还可以,但是我想进一步减少开销。下一步是为给定对象中的所有字符串创建一个合并数组(一个对象将包含1个短和1个长字符串,或4个短和1个长字符串)。因此每个对象只有一个byte [],每个字符串只需要1个字节(保存其长度始终小于256)。 即使您的字符串可以长于256,并且int仍然便宜,然后byte []的12-16字节开销。

这减少了很多byte []开销,并增加了一点复杂度,但对性能没有其他影响(与所涉及的数组副本相比,编码过程相对昂贵)。

这个实现看起来像这样:

byte _property1;
byte _property2;
byte _proeprty3;

private byte[] _data; 

byte[] data;
//i actually used an Enum to indicate which property, but i am sure you get the idea
private int GetStartIndex(int propertyIndex)
{  

   int result = 0;
   switch(propertyIndex)
   {
       //the fallthrough is on purpose 
       case 2:
          result+=property2;
       case 1:
          result+=property1;

   }

   return result;
}

private int GetLength(int propertyIndex)
{
   switch (propertyIndex)
   {
     case 0:
        return _property1;
     case 1: 
        return _property2;
     case 2:
        return _property3;
   }
    return -1;
}

private String GetString(int propertyIndex)
{
   int startIndex = GetStartIndex(propertyIndex);
   int length = GetLength(propertyIndex);
   byte[] result = new byte[length];
   Array.Copy(data,startIndex,result,0,length);

   return Encoding.UTF8.GetString(result);

}

所以吸气剂看起来像这样:

public String Property1
{
   get{ return GetString(0);}
}

设置器的精神是相同的-将原始数据复制到两个数组中(从0开始到startIndex之间,以及从startIndex + length到length之间),然后用3个数组创建一个新数组(dataAtStart + NewData + EndData)并设置 数组的长度为适当的局部变量。

我仍然对每个属性的节省的内存和手动实现的辛苦工作不满意,因此我构建了一个内存中压缩分页系统,该系统使用了惊人的快速QuickLZ来压缩整页。这使我对时间记忆的权衡(实际上就是页面的大小)有了很多控制。

我的用例的压缩率(与更有效的byte []存储相比)接近50%(!)。 我使用的页面大小约为每页10个字符串,并将相似的属性分组在一起(这些属性往往具有相似的数据)。这增加了10-20%的额外开销(在仍然需要的编码/解码过程之上)。 分页机制将最近访问的页面缓存到可配置的大小。即使没有压缩,此实现也允许您为每个页面的开销设置固定的因子。我当前的页面高速缓存实现的主要缺点是,使用压缩它不是线程安全的(没有它就没有这种问题)。

如果您对压缩分页机制感兴趣,请告诉我(我一直在寻找开放它的借口)。

NightDweller answered 2020-06-29T19:33:49Z
6 votes

备用数据结构

我建议,鉴于您也希望搜索存储的“字符串”值,您应该考虑是使用Trie结构(例如Patricia Trie),还是为了获得更好的内存摊销,使用有向无环字图(称为affialty) DAWG)会更好。

构建它们会花费更长的时间(尽管通常在底层存储本身很好地表示这种形式的情况下使用它们,从而可以快速进行预先构建),即使对它们的某些操作在算法上是优越的,您也可能会发现在实际使用中 实际上,它们的速度较慢,只要存在合理的重复次数,它们的确会显着减少数据的内存占用。

这些可以看作是字符串实习的.net(以及Java和许多其他托管语言)中提供的(内置)重复数据消除的概括。

如果您特别希望以某种字典顺序的方式保留字符串的顺序(因此您一次只需要考虑一个字符或代码点),那么Patricia Trie可能是更可取的选择,它是在DAWG之上实现的 会有问题。

如果您具有特定的字符串域,则可以使用其他更深奥的解决方案,包括:

行程编码和其他形式的压缩。

如果输入结果不符合预期,则会以随机访问字符串为代价,并且冒着实际使用更多内存的风险。 霍夫曼编码倾向于在英文文本上很好地工作,并且很容易实现,它的优点是,只要字母的频率分布是可比较的,它的字典就可以在整个集合中分片。 排序将再次成为问题。

固定长度的字符串。

如果您知道字符串很小,并且所有大小都几乎相同(或完全相同),则可以存储为固定大小的值(如果需要,即使字符数在16个或更少的范围内,也可以存储结构,如果需要的话) 此处的使用限制将取决于您的精确用法,并且可能在很大程度上取决于您希望代码如何调整以使其在此设计中发挥出色的作用)

ShuggyCoUk answered 2020-06-29T19:36:29Z
5 votes

您可以创建一个新的数据结构来容纳这些数据,尽管我认为这太过分了。

但是,如果每个单词或常用短语都有一个数组,则将索引存储为每个单词的数组。

然后,您为每个单词支付4个字节,但是如果每个单词平均使用3.6个字符,则每个单词平均要为自己节省3.2个字节,因为您每个单词支付2个字节/字母的罚款。

但是,为了进行搜索或排序,您至少必须在短时间内重建字符串,从而对性能造成重大影响。

您可能需要重新考虑如何设计程序,因为许多程序使用大量数据并且可以在相对受限的内存中运行。

James Black answered 2020-06-29T19:37:08Z
4 votes

好吧,这里有UTF8Encoding类

//Example from MSDN
using System;
using System.Text;

public class Example
{
   public static void Main()
   {
      Encoding enc = new UTF8Encoding(true, true);
      string value = "\u00C4 \uD802\u0033 \u00AE"; 

      try
      {
         byte[] bytes= enc.GetBytes(value);
         foreach (var byt in bytes)
            Console.Write("{0:X2} ", byt);
         Console.WriteLine();

         string value2 = enc.GetString(bytes);
         Console.WriteLine(value2);
      }
      catch (EncoderFallbackException e)
      {
         //Encoding error
      }                     
   }
}

但是,就像Jon所说的,只要您想将其与任何需要使用字符串的方法(大多数.Net库)一起使用,无论如何,您都必须将其转换回普通的unicode字符串... 有关您要执行的操作的信息,也许我们可以帮助您提出更好的解决方案?

或者,如果您确实需要低级字节数组不可国际化的以null终止的字符串,那么最好只用C ++编写。

BlueRaja - Danny Pflughoeft answered 2020-06-29T19:37:37Z
4 votes

您希望复制多少? 如果阵列中有很多重复项,则您可能需要考虑实现一个字符串缓存(围绕Dictionary<string, string>的包装器),该缓存用于缓存特定字符串的实例,并为您在其中缓存的每个重复字符串返回对该实例的引用。

您可以将其与检查内联字符串结合使用,因此,如果在整个程序中共享许多字符串,则始终使用内联版本。

根据您的数据,这可能会比尝试优化每个单个字符串的存储效果更好。

thecoop answered 2020-06-29T19:38:06Z
1 votes

我认为关键在于每个记录都有很多字符串字段…

通过将每个记录的所有字符串字段存储在单个char数组中,然后使用具有偏移量的int字段,可以大大减少对象的数量。 (甚至在您将任何数据放入对象之前,每个对象的开销约为2个字。)

然后,您的属性可以与标准字符串转换。 垃圾收集器擅长处理大量短时垃圾,因此在访问属性时创建许多“ tmp”字符串应该不是问题。

(现在,如果许多字符串字段的值从未更改过,事情就会变得容易得多)

Ian Ringrose answered 2020-06-29T19:38:40Z
1 votes

您可以通过使用大byte []来存储每个对象的开销,该byte []存储字符,然后将int-offset作为“字符串”存储到该数组中。

usr answered 2020-06-29T19:39:00Z
0 votes

也许一个好的老式时尚字符数组可以满足您的需求。

Steve Wellens answered 2020-06-29T19:39:20Z
0 votes

所有这些字符串都不同吗?

在大多数现实世界的数据集中,我会认为不同字符串的实际数量可能不会那么高,并且如果考虑到字符串实习,那么最终消耗的实际内存量可能会大大少于您的想象。

Paul Creasey answered 2020-06-29T19:39:44Z
translate from https://stackoverflow.com:/questions/5435913/net-string-class-alternative