我想我们可以做得比天真地计算每个加法的字符串的总长度更好。LINQ很酷,但它可能会意外地鼓励代码效率低下。如果我想要一个巨大的UTF字符串的第一个80,000字节呢?这是一个很多的不必要的计数。 “我有1个字节,现在我有2个。现在我有13个...现在我有52,384 ...”
这很愚蠢。大多数情况下,至少在l'anglais中,我们可以在该nth
字节上正好删除,。即使在另一种语言中,我们距离一个好的切点也不到6个字节。
因此,我将从@ Oren的建议开始,即关闭UTF8 char值的前导位。我们先从n+1th
字节开始,然后使用Oren的技巧来确定是否需要提前减少几个字节。
三种可能性
如果切割后的第一个字节中的龙头有点0
,我知道我在切割精确的单个字节之前(常规ASCII)字符,并且可以干净地切割。
如果我在切割后有11
,切割后的下一个字节是多字节字符的开始,所以这也是切割的好地方!
但是,如果我有10
,我知道我处于多字节字符的中间,需要返回以检查它是否真正开始。
也就是说,虽然我想在第n个字节之后切割字符串,但如果第n + 1个字节出现在多字节字符的中间,则切割会创建无效的UTF8值。我需要备份,直到找到一个以11
开头的文件,并在它之前剪切。
代码
注:我使用的东西一样Convert.ToByte("11000000", 2)
,这样可以很容易地告诉我什么屏蔽位(约多一点位屏蔽here)。简而言之,我是&
将返回字节的前两位中的内容,并将其余0
带回。然后我检查XX
XX000000
,看它是否为10
或11
,在适当的情况下。
我今天发现了那C# 6.0 might actually support binary representations,这很酷,但我们现在继续用这个kludge来说明发生了什么。
PadLeft
只是因为我太过于OCD输出到控制台。
因此,这里有一个函数可以将您缩减为一个长度为n
字节的字符串或小于n
的字符串,该字符串以“完整的”UTF8字符结尾。
public static string CutToUTF8Length(string str, int byteLength)
{
byte[] byteArray = Encoding.UTF8.GetBytes(str);
string returnValue = string.Empty;
if (byteArray.Length > byteLength)
{
int bytePointer = byteLength;
// Check high bit to see if we're [potentially] in the middle of a multi-byte char
if (bytePointer >= 0
&& (byteArray[bytePointer] & Convert.ToByte("10000000", 2)) > 0)
{
// If so, keep walking back until we have a byte starting with `11`,
// which means the first byte of a multi-byte UTF8 character.
while (bytePointer >= 0
&& Convert.ToByte("11000000", 2) != (byteArray[bytePointer] & Convert.ToByte("11000000", 2)))
{
bytePointer--;
}
}
// See if we had 1s in the high bit all the way back. If so, we're toast. Return empty string.
if (0 != bytePointer)
{
returnValue = Encoding.UTF8.GetString(byteArray, 0, bytePointer); // hat tip to @NealEhardt! Well played. ;^)
}
}
else
{
returnValue = str;
}
return returnValue;
}
我最初写道这是一个字符串扩展。当然,只需在string str
之前加上this
即可将其恢复为扩展格式。我删除了this
,以便我们可以在简单的控制台应用程序中将该方法拍成Program.cs
以进行演示。
测试和预期产出
这里是一个很好的测试条件下,输出其创造的下方,写预计是在Main
方法简单的控制台应用程序的Program.cs
。
static void Main(string[] args)
{
string testValue = "12345“”67890”";
for (int i = 0; i < 15; i++)
{
string cutValue = Program.CutToUTF8Length(testValue, i);
Console.WriteLine(i.ToString().PadLeft(2) +
": " + Encoding.UTF8.GetByteCount(cutValue).ToString().PadLeft(2) +
":: " + cutValue);
}
Console.WriteLine();
Console.WriteLine();
foreach (byte b in Encoding.UTF8.GetBytes(testValue))
{
Console.WriteLine(b.ToString().PadLeft(3) + " " + (char)b);
}
Console.WriteLine("Return to end.");
Console.ReadLine();
}
输出如下。请注意,testValue
中的“智能引用”在UTF8中的长度为3个字节(尽管当我们使用ASCII将字符写入控制台时,它会输出哑引号)。还要注意输出中每个智能报价的第二个和第三个字节的输出为?
。
我们的testValue
的前五个字符是UTF8中的单个字节,因此0-5字节值应该是0-5个字符。然后我们有一个三字节的智能报价,直到5 + 3个字节才能被完整包含。果然,我们看到,在呼叫弹出的8
。我们的下一个智能的报价为8 + 3 = 11弹出,然后我们又回到了单字节字符至14
0: 0::
1: 1:: 1
2: 2:: 12
3: 3:: 123
4: 4:: 1234
5: 5:: 12345
6: 5:: 12345
7: 5:: 12345
8: 8:: 12345"
9: 8:: 12345"
10: 8:: 12345"
11: 11:: 12345""
12: 12:: 12345""6
13: 13:: 12345""67
14: 14:: 12345""678
49 1
50 2
51 3
52 4
53 5
226 â
128 ?
156 ?
226 â
128 ?
157 ?
54 6
55 7
56 8
57 9
48 0
226 â
128 ?
157 ?
Return to end.
所以这是一种的乐趣,而我正处于问题五周年之前。尽管Oren对这些位的描述有一个小错误,那就是恰恰是你想要使用的技巧。感谢您的提问;整齐。
P.S.我包括介绍,以防万一任何人在将来使用我的Oracle错误消息。希望这会为他们节省一些时间。 – 2009-08-03 23:05:55