在 C# 中将字符串存储为 UTF8
Posted
技术标签:
【中文标题】在 C# 中将字符串存储为 UTF8【英文标题】:Storing a string as UTF8 in C# 【发布时间】:2012-08-22 02:15:05 【问题描述】:我在 C# 中进行了大量的字符串操作,并且确实需要每个字符存储一个字节的字符串。这是因为我需要同时在内存中存储千兆字节的文本,这会导致内存不足的问题。我确定该文本永远不会包含非 ASCII 字符,因此出于我的目的,System.String 和 System.Char 将所有内容存储为每个字符两个字节的事实既是不必要的,也是一个真正的问题。
我即将开始编写我自己的 CharAscii 和 StringAscii 类 - 字符串 1 基本上将其数据保存为 byte[],并公开类似于 System.String 的字符串操作方法。然而,这似乎需要做很多工作来做一些看起来像一个非常标准的问题,所以我真的在这里发帖以检查是否还没有更简单的解决方案。例如,有什么方法可以让 System.String 在内部将数据存储为我没有注意到的 UTF8,或者以其他方式解决问题?
【问题讨论】:
.NET 是最好的方式吗?似乎 C/C++ 是处理内存中巨大字符串的更好选择。 Jon Skeet 去年对此进行了调查msmvps.com/blogs/jon_skeet/archive/2011/04/05/… @Jon 的文章不错,但它确实只关注内存使用的详细分析,而不是替换String
本身。此外,FWIW,Jon 在文章上添加了“古怪想法”和“邪恶代码”标签。
如果您有 Reflector 或类似的东西,您可以从框架中提取String
类的副本,并将其内部结构更改为使用字节数组。
@RobertHarvey - 我也会这样做,不幸的是,它不包含最有趣的部分,例如著名的 InternalMarvin32HashString() 方法(我发誓这种方法存在:-) 更严重的是,很多stuff 不安全和/或内部 CLR 调用。
【参考方案1】:
好吧,您可以创建一个包装器,将数据检索为 UTF-8 字节,并根据需要将片段转换为 System.String,反之亦然,将字符串推回内存。 Encoding 类将在此处为您提供帮助:
var utf8 = Encoding.UTF8;
byte[] utfBytes = utf8.GetBytes(myString);
var myReturnedString = utf8.GetString(utfBytes);
【讨论】:
+1,我在为一家房地产公司收集大量数据时自己调查了这个问题,这个解决方案虽然有点神奇而且看起来很笨拙,但几乎是我能想到的最好的东西在 C# 中使用。 顺便说一句,这最终变成了 UTF-16 编码的字符串对象。 @Tigran,如果您要在任何时候使用 System.String,就无法解决这个问题。但是,您可以提取编码字节数组的子部分并以受控方式将它们写出,从而对您占用的资源数量设置上限。 @Tigran 请详细说明。 utf8 不是真的 utf8? @Tigran - 是的。除非 OP 想要完全放弃使用 .NET Framework 免费获得的所有字符串(我强烈建议反对),否则至少他正在使用的一些数据必须转换为并从 UTF-16 System.String 使用它。但是,他正在处理的大量数据可以保留在 UTF-8 中(如果他真的确定数据不会包含任何非 ASCII 字符,甚至可以保留 ASCII)。【参考方案2】:如您所见,CLR 使用 UTF-16 进行字符编码。您最好的选择可能是使用 Encoding 类和 BitConverter 来处理文本。这个问题有一些在两种编码之间转换的好例子:
Convert String (UTF-16) to UTF-8 in C#
【讨论】:
谢谢。将此标记为答案,因为该链接包含大量有关进行转换的信息。我认为您和 KeithS 建议的方法可能是在我的情况下在最大性能和获得某种解决方案之间的最佳折衷方案,这种解决方案可以节省内存而不需要太长时间来实施。【参考方案3】:不是真的。 System.String
专为存储字符串而设计。您的要求是具有特定内存优势的非常特殊的字符串子集。
现在,“具有特殊内存优势的非常特殊的字符串子集”出现了很多,但并不总是相同的非常特殊的子集。仅 ASCII 的代码不是供人类阅读的,因此它往往是短代码,或者可以以流处理方式处理的东西,或者是与字节合并的文本块做其他工作(例如,相当多的二进制格式会有直接转换为 ASCII 的小位)。
因此,您有一个非常奇怪的要求。
当您谈到千兆字节部分时更是如此。如果我正在处理演出,我会立即考虑如何停止处理演出,和/或获得比 50% 更可观的节省。我会考虑将我目前不感兴趣的块映射到文件,或者关于绳索,或者关于一堆其他事情。当然,这些适用于某些情况,而不适用于所有情况,所以再一次,我们不是在谈论 .NET 应该坚持作为一种万能的东西,因为一种尺寸不适合全部。
除此之外,只是 utf-8 位并不难。所有其他方法都变得有效。同样,您需要的东西不会与其他人相同。
【讨论】:
这并不奇怪。 OP 希望字符串的工作方式与System.String
相同,但占用一半的空间。换句话说,就是一种替代品。
@RobertHarvey 是的,但他们例如不会想要 O(n) 长度,因为他们知道从数据的知识中他们不需要它。对基于 utf-8 的字符串有相似但不相同的需求的人将需要 O(n) 计数,因为他们不坚持仅使用 ASCII。一般问题会出现很多,但微小的细节会有所不同,这使得一个人可以完美地替代另一个人的毒药。
罗伯特·哈维正是如此。 Jon - 我正在做的,非常粗略地,涉及文本位之间的广泛交叉引用。因此,在处理过程中很难避免将整个文本保存在内存中。将我不立即感兴趣的块写入文件只是必须在一毫秒后读回它们,我认为这对性能来说是可怕的! (以及使代码更复杂)
是的。现在,如果我是你,我仍然会寻找编写了符合我需要的漂亮开源 utf8string 类的人,因为有时我们确实很幸运。即便如此,我还是希望在某个时候,他们聪明的记忆保护程序必须作为对我的灾难而被删除,或者我可以做一个会毁了他们的记忆保存技巧。
说到节省内存的技巧,你知道en.wikipedia.org/wiki/Rope_%28computer_science%29 中的绳索作为另一个例子,它对一些必须处理非常大的字符串的人来说非常有用,而且绝对没用其他。以为我会提到它,以防你在前营地:)【参考方案4】:
我可以看到您的问题是 C# 中的 char 占用 2 个字节,而不是 1 个。
读取文本文件的一种方法是使用以下命令打开它:
System.IO.FileStream fs = new System.IO.FileStream(file, System.IO.FileMode.Open);
System.IO.BinaryReader br = new System.IO.BinaryReader(fs);
byte[] buffer = new byte[1024];
int read = br.Read(buffer, 0, (int)fs.Length);
br.Close();
fs.Close();
这样你就可以从文件中读取字节了。 我尝试使用以 UTF-8 编码的 *.txt 文件,即 每个字符 2 个字节,以及 ANSI 即 1 个字节每个字符。
【讨论】:
你的意思是 UTF-16 吗?对于我要询问的特定数据,UTF-8 与 ANSI 一样,每个字符 1 个字节。但是谢谢,这实际上正是我读取数据的方式。 @PhantomDrummer 我实际上尝试了 UTF-8,记事本常用编码,每个字符占用 2 个字节 :) 很高兴帮助以上是关于在 C# 中将字符串存储为 UTF8的主要内容,如果未能解决你的问题,请参考以下文章