拉丁文、中文、西里尔文等的子字符串UTF-8



在Windows Phone上,我想将任何给定的字符串的子字符串长度相当于100个ASCII字符。

字符串长度显然是无用的,因为一个中文字符串每个字符使用3个字节,一个丹麦字符串每个字符需要2或4个字节,而一个俄罗斯字符串每个字符要使用4个字节。

唯一可用的编码是UTF-8和UTF-16。那我该怎么办?

想法是这样的:

private static string UnicodeSubstring(string text, int length)
{
var bytes = Encoding.UTF8.GetBytes(text);
return Encoding.UTF8.GetString(bytes, 0, Math.Min(bytes.Length, length));
}

但是,长度需要与每个字符使用的字节数正确地划分,因此最后一个字符始终正确呈现。

一个选项是简单地遍历字符串,计算每个字符的字节数。

如果你知道你不需要处理BMP之外的字符,这相当简单:

public string SubstringWithinUtf8Limit(string text, int byteLimit)
{
int byteCount = 0;
char[] buffer = new char[1];
for (int i = 0; i < text.Length; i++)
{
buffer[0] = text[i];
byteCount += Encoding.UTF8.GetByteCount(buffer);
if (byteCount > byteLimit)
{
// Couldn't add this character. Return its index
return text.Substring(0, i);
}
}
return text;
}

如果必须处理代理对,则会变得稍微棘手一些:(

一个选项是简单地将"字符"(如果需要,包括代理项对)添加到生成的字符串中,看看它是否被转换为您想要的正确数量。

虽然这是一个非常古老的问题,但我认为正确的方法是使用System.Globalization.StringInfo类的StringInfo.SubstringByTextElements方法。这样做的主要优点是.NET文档保证net461及以上版本,StringInfo的Notes To Callers保证符合Unicode标准8.0.0:

来电者须知

在内部,StringInfo类调用的方法CharUnicodeInfo类的方法来确定字符类别。从.NET Framework 4.6.2开始,字符分类基于Unicode标准8.0.0版。对于从.NET Framework 4到.NET Framework 4.6.1,它是基于Unicode标准,版本6.3.0。在.NET Core中,它基于Unicode标准,版本8.0.0。

现在,鉴于Microsoft文档中没有关于如何调用SubstringByTextElements的示例,您实际上是如何调用它的?

StringInfo类中,有一个注释写道:

  • 通过调用ParseCombiningCharacters方法来检索包含每个文本元素的起始索引的数组。然后,您可以通过将这些索引传递给SubstringByTextElements方法来检索各个文本元素

所以:

  1. 调用ParseCombinigCharacters获取每个文本元素的起始索引
  2. 使用步骤一提供的索引调用SubstringByTextElements

另一个想法是检查最后一个字符是否为Unicode替换字符,并删除一个字符,直到其正确呈现。

private static string UnicodeSubstring(string text, int length)
{
var bytes = Encoding.UTF8.GetBytes(text);
var result = Encoding.UTF8.GetString(bytes, 0, Math.Min(bytes.Length, length));
while ('uFFFD' == result[result.Length - 1])
{
result = result.Substring(0, result.Length - 1);
}
return result;
}

最新更新