加算ごとに文字列の合計の長さを単純にカウントするよりも、うまくいくと思います。LINQ は優れていますが、効率の悪いコードを誤って助長する可能性があります。巨大な UTF 文字列の最初の 80,000 バイトが必要な場合はどうすればよいでしょうか? それは多くの不必要なカウントです。「私は 1 バイトを持っています。今は 2 を持っています。今は 13 を持っています...今は 52,384 を持っています...」
それはばかげている。ほとんどの場合、少なくとも言語では、そのバイトを正確にカットできます。nth
別の言語でも、適切な切断点から 6 バイト未満離れています。
そこで、@Oren の提案から始めます。これは、UTF8 char 値の先頭ビットをキーオフすることです。バイトを右にカットすることから始めましょうn+1th
。Oren のトリックを使用して、数バイト前にカットする必要があるかどうかを判断します。
3つの可能性
カット後の最初のバイト0
の先頭ビットに a がある場合、1 バイト (従来の ASCII) 文字の前で正確にカットしていることがわかるので、きれいにカットできます。
カットに続くバイトがある場合11
、カットの後の次のバイトはマルチバイト文字の開始であるため、ここもカットするのに適しています!
ただし、がある場合10
は、マルチバイト文字の途中にいることがわかっているため、実際にどこから始まるかを確認するために戻って確認する必要があります。
つまり、n バイト目以降の文字列を切り取りたいのですが、その n+1 バイト目がマルチバイト文字の途中にある場合、切り取りによって無効な UTF8 値が作成されます。11
で始まり、その直前でカットされるものに到達するまで、バックアップする必要があります。
コード
注:Convert.ToByte("11000000", 2)
マスクしているビットを簡単に判別できるように、次のようなものを使用しています (ビット マスキングについては、ここでもう少し詳しく説明します)。簡単に言えば、バイトの最初の 2 ビットの内容を返し、残りの s を&
返すということです。0
次に、必要に応じてXX
fromをチェックして、またはであるXX000000
かどうかを確認します。10
11
今日、C# 6.0 がバイナリ表現を実際にサポートしている可能性があることを知りました。
これ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;
}
最初はこれを文字列拡張として書きました。this
もちろん、前に追加string str
して拡張形式に戻すだけです。を削除したので、単純なコンソール アプリで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 でコンソールに書き込むと、ダム クォートが出力されます)。?
出力内の各スマート クォートの 2 番目と 3 番目のバイトの s 出力に も注意してください。
私たちの最初の 5 文字はtestValue
UTF8 のシングル バイトであるため、0 ~ 5 バイトの値は 0 ~ 5 文字である必要があります。次に、3 バイトのスマート クォートがあり、5 + 3 バイトになるまで全体を含めることはできません。案の定、8
次のスマート クォートは 8 + 3 = 11 で表示され、14 までは 1 バイト文字に戻ります。
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.
それはちょっと楽しいです。私は質問の 5 周年記念の直前にいます。オレンのビットの説明には小さな誤りがありましたが、それはまさにあなたが使いたいトリックです. ご質問ありがとうございます。きちんとした。