我有一个c#方法,需要检索字符串的第一个字符,并查看它是否存在于包含特定unicode字符(所有从右到左的字符)的HashSet中。
所以我在写
var c = str[0];
,然后检查哈希集。
问题是这段代码不适用于第一个字符的码位大于65535的字符串。
我实际上创建了一个循环,遍历从0到70,000的所有数字(最高的RTL代码点大约是68,000,所以我四舍五入),我从数字创建一个字节数组,并使用
Encoding.UTF32.GetString(intValue);
创建一个包含此字符的字符串。然后我把它传递给在HashSet中搜索的方法,这个方法失败了,因为当它得到
str[0]
这个值永远不是它应该的值。
我做错了什么?
String
是一个UTF-16编码单元序列,一个或两个编码Unicode码点。如果你想从字符串中获得一个代码点,你必须迭代字符串中的代码点。"字符"也是一个基本码点,后面跟着一个由零个或多个组合码点组成的序列("组合字符")。
// Use a HashSet<String>
var itor = StringInfo.GetTextElementEnumerator(s);
while (itor.MoveNext()) {
var character = itor.GetTextElement();
// find character in your HashSet
}
如果你不需要考虑组合代码点,你可以把它们擦掉。(但它们在某些语言中非常重要)
对于将来看到这个问题并且对我最终得到的解决方案感兴趣的任何人-这是我的方法,它决定字符串是否应该根据字符串中的第一个字符显示RTL或LTR。它考虑了UTF-16代理对。
感谢Tom Blodget,他为我指明了正确的方向。
if (string.IsNullOrEmpty(str)) return null;
var firstChar = str[0];
if (firstChar >= 0xd800 && firstChar <= 0xdfff)
{
// if the first character is between 0xD800 - 0xDFFF, this is the beginning
// of a UTF-16 surrogate pair. there MUST be one more char after this one,
// in the range 0xDC00-0xDFFF.
// for the very unreasonable chance that this is a corrupt UTF-16 string
// and there is no second character, validate the string length
if (str.Length == 1) return FlowDirection.LeftToRight;
// convert surrogate pair to a 32 bit number, and check the codepoint table
var highSurrogate = firstChar - 0xd800;
var lowSurrogate = str[1] - 0xdc00;
var codepoint = (highSurrogate << 10) + (lowSurrogate) + 0x10000;
return _codePoints.Contains(codepoint)
? FlowDirection.RightToLeft
: FlowDirection.LeftToRight;
}
return _codePoints.Contains(firstChar)
? FlowDirection.RightToLeft
: FlowDirection.LeftToRight;
我不确定我理解你的问题,一小段代码可能有用。当你有这样一行'var c = str[0]'时,假设'str'是一个字符串,那么c将是一个字符,编码为UTF16。因此,c永远不会大于(2^16 - 1)。Unicode字符可以大于此值,但当这种情况发生时,它们被编码为跨越多个'字符'位置。在UTF-16的情况下,'first'字符可以占用1或2个16位值。