我想我們可以做得比天真地計算每個加法的字符串的總長度更好。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