เหตุใดความยาวของสตริงนี้จึงยาวเกินจำนวนอักขระในนั้น


145

รหัสนี้:

string a = "abc";
string b = "A𠈓C";
Console.WriteLine("Length a = {0}", a.Length);
Console.WriteLine("Length b = {0}", b.Length);

เอาท์พุท:

Length a = 3
Length b = 4

ทำไม? สิ่งเดียวที่ฉันจินตนาการได้คือตัวอักษรจีนมีความยาว 2 ไบต์และ.Lengthวิธีคืนค่าจำนวนไบต์


10
ฉันรู้ได้อย่างไรว่ามันเป็นปัญหาของคู่ตัวแทนเพียงแค่มองจากชื่อเรื่อง อ่าดี 'ol System.Globalization เป็นพันธมิตรของคุณ!
Chris Cirefice

9
มีความยาว 4 ไบต์ใน UTF-16 ไม่ใช่ 2
phuclv

ค่าทศนิยมของ char 𠈓คือ 131603 และเนื่องจาก chars เป็นไบต์ที่ไม่ได้ลงนามซึ่งหมายความว่าคุณสามารถบรรลุค่านั้นใน 2 อักขระแทนที่จะเป็น 4 (16 บิตที่ไม่ได้ลงชื่อที่มีค่าสูงสุดคือ 65535 (หรือ 65536 การเปลี่ยนแปลง) และการใช้ 2 chars สำหรับจำนวนรูปแบบสูงสุดที่ไม่ใช่ 65536 * 2 (131072) แต่เป็นรูปแบบ 65536 * 65536 (4,294,967,296 รูปแบบที่มีประสิทธิภาพค่า 32 บิต)
GMasucci

3
@GMAsucci: เป็นอักขระ 2 ตัวใน UTF-16 แต่ 4 ไบต์เนื่องจากอักขระ UTF16 มีขนาด 2 ไบต์มิฉะนั้นจะไม่สามารถเก็บรูปแบบได้ 65536 รูป แต่มีเพียง 256 รูปเท่านั้น
Kaiserludi

4
ฉันขอแนะนำให้อ่านบทความยอดเยี่ยม 'The Absolute Minimum ทุกผู้พัฒนาซอฟต์แวร์อย่างแน่นอนต้องรู้เกี่ยวกับ Unicode และชุดอักขระ (ไม่มีข้อแก้ตัว!)' joelonsoftware.com/articles/Unicode.html
ItsMe

คำตอบ:


232

คนอื่น ๆ กำลังให้คำตอบที่ชัดเจน แต่ก็มีเหตุผลที่ลึกกว่าเช่นกัน: จำนวนของ "ตัวละคร" เป็นคำถามที่ยากที่จะกำหนดและอาจมีราคาแพงในการคำนวณอย่างน่าประหลาดใจ

ทำไมจึงยากที่จะกำหนด มีตัวเลือกน้อยและไม่มีอะไรที่ถูกต้องมากกว่าตัวเลือกอื่น

  • จำนวนหน่วยโค้ด (ไบต์หรือก้อนข้อมูลขนาดคงที่อื่น ๆ C # และ Windows มักใช้ UTF-16 ดังนั้นจึงส่งคืนจำนวนชิ้นสองไบต์) มีความเกี่ยวข้องอย่างแน่นอนเนื่องจากคอมพิวเตอร์ยังคงต้องจัดการกับข้อมูลในรูปแบบนั้น สำหรับวัตถุประสงค์มากมาย (การเขียนไปยังไฟล์เช่นใส่ใจไบต์มากกว่าตัวอักษร)

  • จำนวน Unicode codepoints นั้นค่อนข้างง่ายในการคำนวณ (แม้ว่า O (n) เพราะคุณต้องสแกนสตริงสำหรับคู่ตัวแทนแทน) และอาจมีความสำคัญกับโปรแกรมแก้ไขข้อความ .... แต่จริง ๆ แล้วไม่ใช่สิ่งเดียวกับจำนวนอักขระ พิมพ์บนหน้าจอ (เรียกว่า graphemes) ตัวอย่างเช่นตัวอักษรที่เน้นเสียงบางตัวสามารถแสดงได้ในสองรูปแบบ: codepoint เดียวหรือสองจุดจับคู่กันหนึ่งตัวแทนตัวอักษรและอีกคนหนึ่งพูดว่า "เพิ่มการเน้นเสียงให้กับจดหมายหุ้นส่วนของฉัน" ทั้งคู่จะเป็นตัวละครสองตัวหรือหนึ่งตัว คุณสามารถทำให้ปกติสตริงเพื่อช่วยในเรื่องนี้ แต่ไม่ใช่ตัวอักษรที่ถูกต้องทั้งหมดมีการเป็นตัวแทน codepoint เดียว

  • แม้ว่าจำนวนของกราฟจะไม่เท่ากับความยาวของสตริงที่พิมพ์ซึ่งขึ้นอยู่กับฟอนต์ท่ามกลางปัจจัยอื่น ๆ และเนื่องจากอักขระบางตัวถูกพิมพ์ด้วยการทับซ้อนบางอย่างในฟอนต์จำนวนมาก (การจัดช่องไฟ) ความยาวของสตริงบนหน้าจอ ไม่จำเป็นต้องเท่ากับผลรวมของความยาวของภาพวาดต่อไป!

  • บางจุดของ Unicode ไม่ได้เป็นแบบตัวอักษร แต่เป็นเครื่องหมายควบคุมบางชนิด เช่นเครื่องหมายคำสั่งซื้อไบต์หรือตัวบ่งชี้จากขวาไปซ้าย นับเหล่านี้หรือไม่

กล่าวโดยย่อความยาวของสตริงนั้นเป็นคำถามที่ซับซ้อนอย่างน่าขันและการคำนวณอาจใช้เวลา CPU นานพอ ๆ กับตารางข้อมูล

ยิ่งกว่านั้นประเด็นคืออะไร? ทำไมการวัดเหล่านี้ถึงมีความสำคัญ มีเพียงคุณเท่านั้นที่สามารถตอบได้ว่าสำหรับกรณีของคุณ แต่โดยส่วนตัวแล้วฉันพบว่าพวกเขามักจะไม่เกี่ยวข้อง การ จำกัด การป้อนข้อมูลที่ฉันพบนั้นกระทำได้อย่างมีเหตุผลมากขึ้นโดยการ จำกัด ไบต์เนื่องจากนั่นคือสิ่งที่ต้องโอนหรือจัดเก็บอย่างไรก็ตาม การ จำกัด ขนาดการแสดงผลทำได้ดีกว่าโดยซอฟต์แวร์ด้านการแสดงผล - หากคุณมี 100 พิกเซลสำหรับข้อความจำนวนอักขระที่คุณพอดีจะขึ้นอยู่กับแบบอักษร ฯลฯ ซึ่งซอฟต์แวร์ชั้นข้อมูลไม่รู้จัก ในที่สุดด้วยความซับซ้อนของมาตรฐานยูนิโคดคุณอาจจะมีข้อบกพร่องที่ขอบกรณีอย่างไรก็ตามถ้าคุณลองทำอย่างอื่น

ดังนั้นจึงเป็นคำถามที่ยากและมีวัตถุประสงค์ไม่มากนัก จำนวนหน่วยโค้ดนั้นมีความสำคัญในการคำนวณซึ่งเป็นเพียงความยาวของอาเรย์ข้อมูลและมีความหมาย / มีประโยชน์มากที่สุดตามกฎทั่วไปพร้อมด้วยคำจำกัดความง่ายๆ

นั่นเป็นเหตุผลที่bมีความยาว4เกินคำอธิบายพื้นผิวของ "เพราะเอกสารกล่าวว่า"


9
โดยพื้นฐานแล้ว '.Length' ไม่ใช่สิ่งที่นักเขียนส่วนใหญ่คิดว่าเป็น อาจจะมีชุดของคุณสมบัติที่เฉพาะเจาะจงมากขึ้น (เช่น GlyphCount) และความยาวที่ทำเครื่องหมายเป็นล้าสมัย!
redcalx

8
@locster ฉันเห็นด้วย แต่ไม่คิดว่าLengthควรล้าสมัยเพื่อรักษาความคล้ายคลึงกับอาร์เรย์
Kroltan

2
@locster มันไม่ควรล้าสมัย หลามนั้นมีเหตุผลและไม่มีใครตั้งคำถามเลย
simonzack

1
ฉันคิดว่าความยาวนั้นสมเหตุสมผลและเป็นสมบัติทางธรรมชาติตราบใดที่คุณเข้าใจว่ามันคืออะไรและทำไมมันถึงเป็นเช่นนั้น จากนั้นก็ทำงานเหมือนอาเรย์อื่น ๆ (ในบางภาษาเช่น D, สตริงแท้จริงคืออาเรย์เท่าที่ภาษานั้นเกี่ยวข้องและใช้งานได้ดีจริงๆ)
Adam D. Ruppe

4
นั่นไม่ใช่ความจริง (ความเข้าใจผิดที่พบบ่อย) - ด้วย UTF-32 ความยาวInBytes / 4 จะให้จำนวนรหัสคะแนนแต่ไม่เหมือนกับจำนวนของ "อักขระ" หรือกราฟ พิจารณา LATIN SMALL LETTER E ตามด้วย DIAERESIS รวม ... ที่พิมพ์เป็นอักขระเดียวมันยังสามารถทำให้ปกติเป็น codepoint เดียว แต่ก็ยังมีความยาวสองหน่วยแม้ใน UTF-32
Adam D. Ruppe

62

จากเอกสารของString.Lengthทรัพย์สิน:

กระบวนการความยาวคุณสมบัติส่งกลับจำนวนของวัตถุCharในอินสแตนซ์นี้ไม่ใช่จำนวนของอักขระ Unicode เหตุผลคืออักขระ Unicode อาจถูกแสดงโดยCharมากกว่าหนึ่งตัว ใช้คลาสSystem.Globalization.StringInfoเพื่อทำงานกับอักขระ Unicode แต่ละตัวแทนCharแต่ละตัว


3
Java ทำงานในลักษณะเดียวกัน (เช่นการพิมพ์ 4 เพื่อString b) เนื่องจากใช้การแทน UTF-16 ในอาร์เรย์ char เป็นอักขระ 4 ไบต์ใน UTF-8
ไมเคิล

32

ตัวละครของคุณที่ดัชนี 1 ใน"A𠈓C"คือSurrogatePair

จุดสำคัญที่ต้องจำคือคู่ตัวแทนแทน32 ตัวอักษรเดี่ยว

คุณสามารถลองรหัสนี้และมันจะกลับมา True

Console.WriteLine(char.IsSurrogatePair("A𠈓C", 1));

วิธี Char.IsSurrogatePair (สตริง, Int32)

trueหากพารามิเตอร์ s ประกอบด้วยอักขระที่อยู่ติดกันที่ดัชนีตำแหน่งและดัชนี + 1และค่าตัวเลขของอักขระที่ดัชนีตำแหน่งมีช่วงตั้งแต่ U + D800 ถึง U + DBFF และค่าตัวเลขของอักขระที่ตำแหน่งดัชนี + 1 จาก U + DC00 ถึง U + DFFF; มิฉะนั้น, false.

นี่คือคำอธิบายเพิ่มเติมในคุณสมบัติString.Length :

กระบวนการความยาวคุณสมบัติส่งกลับจำนวนของวัตถุ Char ในอินสแตนซ์นี้ไม่ใช่จำนวนของอักขระ Unicode สาเหตุคืออักขระ Unicode อาจถูกแสดงโดย Char มากกว่าหนึ่งตัว ใช้คลาส System.Globalization.StringInfo เพื่อทำงานกับอักขระ Unicode แต่ละตัวแทน Char แต่ละตัว


24

ตามที่คำตอบอื่น ๆ ได้ชี้ให้เห็นแม้ว่าจะมีตัวละครที่มองเห็นได้ 3 ตัวพวกมันจะแสดงด้วยcharวัตถุ4 อย่าง ซึ่งเป็นสาเหตุที่Length4 และไม่ใช่ 3

MSDN ระบุว่า

กระบวนการความยาวคุณสมบัติส่งกลับจำนวนของวัตถุ Char ในอินสแตนซ์นี้ไม่ใช่จำนวนของอักขระ Unicode

อย่างไรก็ตามหากสิ่งที่คุณต้องการทราบจริงๆคือจำนวนของ "องค์ประกอบข้อความ" และไม่ใช่จำนวนCharวัตถุที่คุณสามารถใช้StringInfoคลาสได้

var si = new StringInfo("A𠈓C");
Console.WriteLine(si.LengthInTextElements); // 3

คุณสามารถระบุองค์ประกอบข้อความแต่ละรายการเช่นนี้

var enumerator = StringInfo.GetTextElementEnumerator("A𠈓C");
while(enumerator.MoveNext()){
    Console.WriteLine(enumerator.Current);
}

การใช้foreachบนสตริงจะแบ่ง "ตัวอักษร" กึ่งกลางออกเป็นสองcharวัตถุและผลลัพธ์ที่พิมพ์ออกมาจะไม่ตรงกับสตริง


20

นั่นเป็นเพราะLengthคุณสมบัติส่งกลับจำนวนของวัตถุถ่านไม่ใช่จำนวนตัวอักษรยูนิโค้ด ในกรณีของคุณอักขระ Unicode หนึ่งตัวจะถูกแสดงโดยวัตถุ char มากกว่าหนึ่งตัว (SurrogatePair)

กระบวนการความยาวคุณสมบัติส่งกลับจำนวนของวัตถุ Char ในอินสแตนซ์นี้ไม่ใช่จำนวนของอักขระ Unicode เหตุผลคืออักขระ Unicode อาจถูกแสดงโดย Char มากกว่าหนึ่งตัว ใช้คลาส System.Globalization.StringInfo เพื่อทำงานกับอักขระ Unicode แต่ละตัวแทน Char แต่ละตัว


1
คุณมีการใช้ "อักขระ" ที่คลุมเครือในคำตอบนี้ ฉันขอแนะนำให้แทนที่อย่างน้อยหนึ่งรายการแรกด้วยคำศัพท์ที่แม่นยำ
การแข่งขัน Lightness ใน Orbit

1
ขอบคุณ. แก้ไขความกำกวม
Yuval Itzchakov

10

อย่างที่คนอื่น ๆ พูดกันมันไม่ใช่จำนวนตัวอักษรในสตริง แต่เป็นจำนวนของวัตถุ Char อักขระ𠈓คือรหัสจุด U + 20213 ตั้งแต่ค่าเป็นช่วงนอก 16 บิตชนิดถ่านของมันเข้ารหัส UTF-16 D840 DE13เป็นคู่ตัวแทน

วิธีที่จะได้ความยาวเป็นตัวละครถูกกล่าวถึงในคำตอบอื่น ๆ อย่างไรก็ตามควรใช้ด้วยความระมัดระวังเนื่องจากมีหลายวิธีในการแสดงอักขระใน Unicode "à" อาจเป็น 1 อักขระที่ประกอบด้วยหรือ 2 อักขระ (+ เครื่องหมายกำกับ) การปรับสภาพอาจจำเป็นต้องใช้เช่นในกรณีของตัวสั่นด้วยความตื่นเต้น

คุณควรอ่านสิ่งนี้
ขั้นต่ำที่แน่นอนผู้พัฒนาซอฟต์แวร์ทุกรายอย่างแน่นอนต้องรู้เกี่ยวกับ Unicode และชุดอักขระ (ไม่มีข้อแก้ตัว!)


6

เพราะนี่คือการlength()ทำงานเฉพาะสำหรับจุดรหัส Unicode U+FFFFที่มีขนาดไม่เกิน ชุดของจุดรหัสนี้เรียกว่าBasic Multilingual Plane (BMP) และใช้เพียง 2 ไบต์

จุดโค้ด Unicode ด้านนอกBMPจะแสดงเป็น UTF-16 โดยใช้คู่ตัวแทน 4 ไบต์

หากต้องการนับจำนวนอักขระอย่างถูกต้อง (3) ให้ใช้ StringInfo

StringInfo b = new StringInfo("A𠈓C");
Console.WriteLine(string.Format("Length 2 = {0}", b.LengthInTextElements));

6

เอาล่ะในสุทธิและ C # สตริงทั้งหมดจะถูกเข้ารหัสเป็นUTF-16LE A stringถูกจัดเก็บตามลำดับตัวอักษร แต่ละcharแค็ปซูลจัดเก็บข้อมูล 2 ไบต์หรือ 16 บิต

สิ่งที่เราเห็น "บนกระดาษหรือหน้าจอ" เป็นตัวอักษรตัวเดียวสัญลักษณ์สัญลักษณ์หรือเครื่องหมายวรรคตอนสามารถคิดได้ว่าเป็นองค์ประกอบข้อความเดียว ดังที่อธิบายไว้ในUnicode Standard Annex # 29 การแบ่งส่วนข้อความแบบ UNICODEแต่ละองค์ประกอบข้อความจะถูกแสดงด้วยจุดรหัสหนึ่งจุดหรือมากกว่า รายการครบถ้วนสมบูรณ์ของรหัสสามารถพบได้ที่นี่

จุดรหัสแต่ละจุดจะต้องเข้ารหัสเป็นเลขฐานสองสำหรับการแสดงภายในโดยคอมพิวเตอร์ ตามที่ระบุไว้charร้านค้าแต่ละ2 ไบต์ จุดรหัสที่หรือต่ำกว่าสามารถเก็บไว้ในหนึ่งเดียวU+FFFF charคะแนนโค้ดด้านบนU+FFFFจะถูกเก็บไว้เป็นคู่ตัวแทนโดยใช้สองตัวอักษรเพื่อแทนจุดรหัสเดียว

เมื่อพิจารณาจากสิ่งที่เรารู้แล้วว่าเราสามารถอนุมานได้องค์ประกอบข้อความสามารถจัดเก็บเป็นหนึ่งcharเดียวเป็นคู่ตัวแทนสองตัวอักษรหรือหากองค์ประกอบข้อความนั้นมีตัวแทนหลายจุดรหัสรวมกันของตัวอักษรเดี่ยวและคู่ตัวแทน ราวกับว่าเขาไม่ได้มีความซับซ้อนพอองค์ประกอบข้อความบางส่วนสามารถแสดงโดยชุดที่แตกต่างกันของจุดรหัสตามที่อธิบายไว้ในมาตรฐาน Unicode ภาคผนวก # 15 รูปแบบ


การแสดงสลับฉาก

ดังนั้นสตริงที่มีลักษณะเหมือนกันเมื่อเรนเดอร์สามารถสร้างขึ้นด้วยชุดอักขระที่แตกต่างกัน การเปรียบเทียบลำดับ (ไบต์ต่อไบต์) ของทั้งสองสายดังกล่าวจะตรวจพบความแตกต่างซึ่งอาจไม่คาดคิดหรือไม่พึงประสงค์

คุณสามารถเข้ารหัสสตริง. Net อีกครั้ง เพื่อให้พวกเขาใช้แบบฟอร์มการทำให้เป็นมาตรฐานเดียวกัน เมื่อปรับมาตรฐานแล้วสองสายที่มีองค์ประกอบข้อความเดียวกันจะถูกเข้ารหัสในลักษณะเดียวกัน หากต้องการทำสิ่งนี้ให้ใช้สตริงฟังก์ชั่นNormalize อย่างไรก็ตามโปรดจำไว้ว่าองค์ประกอบข้อความที่แตกต่างกันมีลักษณะคล้ายกัน : -s


ดังนั้นสิ่งนี้หมายถึงอะไรที่เกี่ยวข้องกับคำถาม? ข้อความธาตุ'𠈓'เป็นตัวแทนจากซิงเกิ้ลพอยท์รหัส U + 20213 CJK ideographs แบบครบวงจรขยายข นี่หมายความว่าไม่สามารถเข้ารหัสเป็นแบบเดี่ยวcharและต้องเข้ารหัสเป็นคู่ตัวแทนโดยใช้สองตัวอักษร นี่คือเหตุผลที่string bเป็นหนึ่งอีกต่อไปว่าcharstring a

หากคุณจำเป็นต้องเชื่อถือได้ (ดูข้อแม้) นับจำนวนองค์ประกอบข้อความในตัวstringคุณควรใช้ System.Globalization.StringInfoคลาสเช่นนี้

using System.Globalization;

string a = "abc";
string b = "A𠈓C";

Console.WriteLine("Length a = {0}", new StringInfo(a).LengthInTextElements);
Console.WriteLine("Length b = {0}", new StringInfo(b).LengthInTextElements);

ให้ผลผลิต

"Length a = 3"
"Length b = 3"

อย่างที่คาดไว้.


ข้อแม้

การใช้งาน. Net ของการแบ่งส่วนข้อความ Unicode ในStringInfoและTextElementEnumeratorคลาสควรเป็นประโยชน์โดยทั่วไปและในกรณีส่วนใหญ่จะให้การตอบสนองที่ผู้เรียกคาดหวัง อย่างไรก็ตามตามที่ระบุไว้ในภาคผนวกมาตรฐาน Unicode # 29 "เป้าหมายของการจับคู่การรับรู้ของผู้ใช้ไม่สามารถบรรลุได้อย่างแน่นอนเพราะข้อความเพียงอย่างเดียวไม่ได้มีข้อมูลเพียงพอที่จะตัดสินขอบเขตอย่างชัดเจน"


ฉันคิดว่าคำตอบของคุณอาจสับสน ในกรณีนี้𠈓เป็นเพียงจุดรหัสเดียว แต่เนื่องจากจุดรหัสของมันมีค่าเกิน 0xFFFF จะต้องแสดงเป็น 2 หน่วยรหัสโดยใช้คู่ตัวแทน Grapheme เป็นอีกแนวคิดที่สร้างขึ้นจากจุดโค้ดซึ่งสามารถแสดงกราฟได้ด้วยรหัสจุดเดียวหรือหลายจุดรหัสตามที่เห็นในภาษาเกาหลีอังกูลหรือภาษาละตินที่ใช้หลายภาษา
nhahtdh

@nhahtdh ฉันเห็นด้วยคำตอบของฉันผิดพลาด ฉันเขียนมันใหม่และหวังว่าตอนนี้จะสร้างความชัดเจนมากขึ้น
Jodrell
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.