在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
方法来检索各个文本元素
所以:
- 调用ParseCombinigCharacters获取每个文本元素的起始索引
- 使用步骤一提供的索引调用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;
}