Mengapa panjang string ini lebih panjang dari jumlah karakter di dalamnya?

145

Kode ini:

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

output:

Length a = 3
Length b = 4

Mengapa? Satu-satunya hal yang dapat saya bayangkan adalah bahwa karakter Cina panjangnya 2 byte dan .Lengthmetode mengembalikan jumlah byte.

weini37
sumber
10
Bagaimana saya tahu itu adalah masalah pasangan pengganti hanya dari melihat judulnya. Ah, sistem yang bagus. Globalisasi adalah sekutu Anda!
Chris Cirefice
9
panjangnya 4 byte dalam UTF-16, bukan 2
phuclv
nilai desimal char 𠈓adalah 131603, dan karena chars adalah byte yang tidak ditandai, itu berarti Anda dapat mencapai nilai itu dalam 2 karakter daripada 4 (unsigned 16 bit nilai maks adalah 65535 (atau 65536 variasi) dan menggunakan 2 chars untuk mewakili itu memungkinkan untuk jumlah maksimum variasi bukan 65536 * 2 (131072) tetapi lebih tepatnya 65536 * 65536 variasi (4.294.967.296, efektif nilai 32 bit)
GMasucci
3
@ GMAsucci: Ini 2 karakter dalam UTF-16, tetapi 4 byte, karena karakter UTF16 berukuran 2 byte, jika tidak, ia tidak dapat menyimpan 65536 variasi, tetapi hanya 256.
Kaiserludi
4
Saya sarankan membaca artikel hebat 'Minimum Mutlak Setiap Pengembang Perangkat Lunak Sepenuhnya, Pasti Harus Tahu Tentang Unicode dan Set Karakter (Tanpa Alasan!)' Joelonsoftware.com/articles/Unicode.html
ItsMe

Jawaban:

232

Semua orang memberikan jawaban permukaan, tetapi ada alasan yang lebih dalam juga: jumlah "karakter" adalah pertanyaan yang sulit untuk didefinisikan dan bisa sangat mahal untuk dihitung, sedangkan properti panjang harus cepat.

Mengapa sulit untuk didefinisikan? Yah, ada beberapa opsi dan tidak ada yang benar-benar lebih valid daripada yang lain:

  • Jumlah unit kode (byte atau potongan data ukuran tetap lainnya; C # dan Windows biasanya menggunakan UTF-16 sehingga mengembalikan jumlah potongan dua-byte) pasti relevan, karena komputer masih perlu menangani data dalam bentuk itu untuk banyak tujuan (menulis ke file, misalnya, peduli dengan byte daripada karakter)

  • Jumlah Unicode codepoints cukup mudah untuk dihitung (walaupun O (n) karena Anda harus memindai string untuk pasangan pengganti) dan mungkin penting bagi editor teks .... tetapi sebenarnya tidak sama dengan jumlah karakter dicetak di layar (disebut graphemes). Misalnya, beberapa huruf beraksen dapat direpresentasikan dalam dua bentuk: satu titik kode tunggal, atau dua titik yang dipasangkan bersama, satu mewakili huruf, dan satu mengatakan "tambahkan aksen ke surat mitra saya". Apakah pasangan itu dua karakter atau satu? Anda dapat menormalkan string untuk membantu dengan ini, tetapi tidak semua huruf yang valid memiliki representasi codepoint tunggal.

  • Bahkan jumlah grafik tidak sama dengan panjang string yang dicetak, yang tergantung pada font di antara faktor-faktor lain, dan karena beberapa karakter dicetak dengan beberapa tumpang tindih dalam banyak font (kerning), panjang string di layar toh belum tentu sama dengan jumlah dari panjang grapheme pula!

  • Beberapa titik Unicode bahkan bukan karakter dalam arti tradisional, melainkan semacam penanda kontrol. Seperti penanda urutan byte atau indikator kanan-ke-kiri. Apakah ini diperhitungkan?

Singkatnya, panjang string sebenarnya adalah pertanyaan yang sangat rumit dan menghitungnya bisa memakan banyak waktu CPU serta tabel data.

Apalagi apa gunanya? Mengapa metrik ini penting? Yah, hanya Anda yang bisa menjawabnya untuk kasus Anda, tetapi secara pribadi, saya menemukan mereka pada umumnya tidak relevan. Membatasi entri data yang saya temukan lebih logis dilakukan oleh batas byte, karena itulah yang perlu ditransfer atau disimpan. Membatasi ukuran tampilan lebih baik dilakukan oleh perangkat lunak sisi tampilan - jika Anda memiliki 100 piksel untuk pesan, berapa banyak karakter yang Anda pas tergantung pada font, dll., Yang tidak diketahui oleh perangkat lunak lapisan data. Akhirnya, mengingat kompleksitas standar unicode, Anda mungkin akan memiliki bug di ujung kasus jika Anda mencoba yang lain.

Jadi itu adalah pertanyaan yang sulit dengan tidak banyak penggunaan tujuan umum. Jumlah unit kode sepele untuk dihitung - itu hanya panjang array data yang mendasarinya - dan yang paling bermakna / berguna sebagai aturan umum, dengan definisi sederhana.

Itu sebabnya bmemiliki panjang 4melampaui penjelasan permukaan "karena dokumentasi mengatakan demikian".

Adam D. Ruppe
sumber
9
Pada dasarnya '. Panjang' bukan yang dipikirkan oleh kebanyakan coders. Mungkin harus ada satu set properti yang lebih spesifik (mis. GlyphCount) dan Panjang yang ditandai sebagai Usang!
redcalx
8
@locster Saya setuju, tapi jangan berpikir Lengthharus usang, untuk mempertahankan analogi dengan array.
Kroltan
2
@locster Seharusnya tidak usang. Python masuk akal dan tidak ada yang mempertanyakannya.
simonzack
1
Saya pikir. Panjang membuat banyak akal dan merupakan properti alami, selama Anda mengerti apa itu dan mengapa demikian. Kemudian ia bekerja seperti array lain (dalam beberapa bahasa seperti D, string secara harfiah adalah array sejauh menyangkut bahasa dan berfungsi dengan sangat baik)
Adam D. Ruppe
4
Itu tidak benar (kesalahpahaman umum) - dengan UTF-32, lengthInBytes / 4 akan memberikan jumlah poin kode , tetapi itu tidak sama dengan jumlah "karakter" atau grafik. Pertimbangkan LATIN SMALL LETTER E diikuti oleh COMBINING DIAERESIS ... yang dicetak sebagai karakter tunggal, bahkan dapat dinormalisasi menjadi satu codepoint, tetapi masih dua unit panjang, bahkan di UTF-32.
Adam D. Ruppe
62

Dari dokumentasi dari String.Lengthproperti:

Properti Length mengembalikan jumlah objek Char dalam contoh ini, bukan jumlah karakter Unicode. Alasannya adalah bahwa karakter Unicode mungkin diwakili oleh lebih dari satu Char . Gunakan kelas System.Globalization.StringInfo untuk bekerja dengan setiap karakter Unicode, bukan masing-masing Char .

pengasuh
sumber
3
Java berperilaku dengan cara yang sama (juga mencetak 4 untuk String b), karena menggunakan representasi UTF-16 dalam array char. Ini adalah karakter 4 byte dalam UTF-8.
Michael
32

Karakter Anda di indeks 1 dalam "A𠈓C"adalah SurrogatePair

Poin utama yang perlu diingat adalah bahwa pasangan pengganti mewakili karakter tunggal 32-bit .

Anda dapat mencoba kode ini dan itu akan kembali True

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

Metode Char.IsSurrogatePair (String, Int32)

truejika parameter s menyertakan karakter yang berdekatan pada indeks posisi dan indeks + 1 , dan nilai numerik karakter pada indeks posisi berkisar dari U + D800 hingga U + DBFF, dan nilai numerik karakter pada indeks posisi + 1 berkisar dari U + DC00 hingga U + DFFF; jika tidak false,.

Ini dijelaskan lebih lanjut dalam properti String.Length :

Properti Length mengembalikan jumlah objek Char dalam contoh ini, bukan jumlah karakter Unicode. Alasannya adalah bahwa karakter Unicode mungkin diwakili oleh lebih dari satu Char. Gunakan kelas System.Globalization.StringInfo untuk bekerja dengan setiap karakter Unicode, bukan masing-masing Char.

Habib
sumber
24

Seperti yang ditunjukkan oleh jawaban lain, bahkan jika ada 3 karakter yang terlihat mereka diwakili dengan 4 charobjek. Itulah sebabnya Lengthadalah 4 dan bukan 3.

MSDN menyatakan itu

Properti Length mengembalikan jumlah objek Char dalam contoh ini, bukan jumlah karakter Unicode.

Namun jika yang Anda benar-benar ingin tahu adalah jumlah "elemen teks" dan bukan jumlah Charobjek yang dapat Anda gunakan StringInfokelas.

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

Anda juga dapat menghitung setiap elemen teks seperti ini

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

Menggunakan foreachpada string akan membagi "huruf" tengah menjadi dua charobjek dan hasil yang dicetak tidak akan sesuai dengan string.

dee-see
sumber
20

Itu karena Lengthproperti mengembalikan jumlah objek char , bukan jumlah karakter unicode. Dalam kasus Anda, salah satu karakter Unicode diwakili oleh lebih dari satu objek char (SurrogatePair).

Properti Length mengembalikan jumlah objek Char dalam contoh ini, bukan jumlah karakter Unicode. Alasannya adalah bahwa karakter Unicode mungkin diwakili oleh lebih dari satu Char. Gunakan kelas System.Globalization.StringInfo untuk bekerja dengan setiap karakter Unicode, bukan masing-masing Char.

Yuval Itzchakov
sumber
1
Anda memiliki penggunaan "karakter" yang ambigu dalam jawaban ini. Saya sarankan mengganti setidaknya yang pertama dengan terminologi yang tepat.
Lightness Races in Orbit
1
Terima kasih. Memperbaiki ambiguitas.
Yuval Itzchakov
10

Seperti yang orang lain katakan, itu bukan jumlah karakter dalam string tetapi jumlah objek Char. Karakter 𠈓 adalah kode titik U + 20213. Karena nilainya di luar kisaran tipe 16-bit, itu dikodekan dalam UTF-16 sebagai pasangan pengganti D840 DE13.

Cara mendapatkan panjang karakter disebutkan dalam jawaban lain. Namun harus digunakan dengan hati-hati karena ada banyak cara untuk mewakili karakter di Unicode. "à" dapat berupa 1 karakter tersusun atau 2 karakter (a + diakritik). Normalisasi mungkin diperlukan seperti dalam kasus twitter .

Anda harus membaca ini
Minimum Mutlak Setiap Pengembang Perangkat Lunak Sepenuhnya, Pasti Harus Tahu Tentang Unicode dan Karakter (Tidak Ada Alasan!)

phuclv
sumber
6

Ini karena length()hanya berfungsi untuk titik kode Unicode yang tidak lebih besar dari U+FFFF. Kumpulan poin kode ini dikenal sebagai Basic Multilingual Plane (BMP) dan hanya menggunakan 2 byte.

Poin kode Unicode di luar BMPdiwakili dalam UTF-16 menggunakan pasangan pengganti 4 byte.

Untuk menghitung jumlah karakter dengan benar (3), gunakan StringInfo

StringInfo b = new StringInfo("A𠈓C");
Console.WriteLine(string.Format("Length 2 = {0}", b.LengthInTextElements));
Dermaga-Alexandre Bouchard
sumber
6

Oke, di .Net dan C # semua string dikodekan sebagai UTF-16LE . A stringdisimpan sebagai urutan karakter. Setiap charmengenkapsulasi penyimpanan 2 byte atau 16 bit.

Apa yang kita lihat "di atas kertas atau layar" sebagai satu huruf, karakter, mesin terbang, simbol, atau tanda baca dapat dianggap sebagai Elemen Teks tunggal. Seperti yang dijelaskan dalam Unicode Standard Annex # 29 UNICODE SEGMENTATION TEXT , setiap Elemen Teks diwakili oleh satu atau lebih Poin Kode. Daftar lengkap Kode dapat ditemukan di sini .

Setiap Poin Kode perlu dikodekan ke dalam biner untuk representasi internal oleh komputer. Seperti yang dinyatakan, masing-masing charmenyimpan 2 byte. Poin Kode pada atau di bawah U+FFFFini dapat disimpan dalam satu char. Poin Kode di atas U+FFFFdisimpan sebagai pasangan pengganti, menggunakan dua karakter untuk mewakili satu Poin Kode.

Mengingat apa yang kita ketahui sekarang dapat kita simpulkan, Elemen Teks dapat disimpan sebagai satu char, sebagai Pasangan Pengganti dari dua karakter atau, jika Elemen Teks diwakili oleh beberapa Poin Kode beberapa kombinasi karakter tunggal dan Pasangan Pengganti. Seolah-olah itu tidak cukup rumit, beberapa Elemen Teks dapat diwakili oleh berbagai kombinasi Poin Kode seperti yang dijelaskan dalam, Unicode Standard Annex # 15, FORMULIR NORMALISASI UNICODE .


Selingan

Jadi, string yang terlihat sama ketika diberikan sebenarnya dapat terdiri dari kombinasi karakter yang berbeda. Suatu perbandingan ordinal (byte demi byte) dari dua string semacam itu akan mendeteksi perbedaan, ini mungkin tidak terduga atau tidak diinginkan.

Anda bisa menyandikan ulang string .Net. sehingga mereka menggunakan Formulir Normalisasi yang sama. Setelah dinormalisasi, dua string dengan Elemen Teks yang sama akan dikodekan dengan cara yang sama. Untuk melakukan ini, gunakan fungsi string . Normalisasi . Namun, ingat, beberapa Elemen Teks yang berbeda terlihat mirip satu sama lain. : -s


Jadi, apa artinya semua ini dalam kaitannya dengan pertanyaan? Elemen Teks '𠈓'diwakili oleh satu Code Point U + 20213 cjk unified ideaographs extension b . Ini berarti tidak dapat dikodekan sebagai tunggal chardan harus dikodekan sebagai Pasangan Pengganti, menggunakan dua karakter. Inilah sebabnya mengapa string bsatu charlagi itu string a.

Jika Anda perlu andal (lihat peringatan) hitung jumlah Elemen Teks dalam stringAnda harus menggunakan System.Globalization.StringInfokelas seperti ini.

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);

memberikan output,

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

seperti yang diharapkan.


Peringatan

Implementasi .Net Segmentasi Teks Unicode di dalam StringInfodan TextElementEnumeratorkelas harus umumnya bermanfaat dan, dalam banyak kasus, akan menghasilkan respons yang diharapkan oleh penelepon. Namun, seperti yang dinyatakan dalam Unicode Standard Annex # 29, "Tujuan pencocokan persepsi pengguna tidak selalu dapat dipenuhi persis karena teks saja tidak selalu berisi informasi yang cukup untuk menentukan batas secara jelas."

Jodrell
sumber
Saya pikir jawaban Anda berpotensi membingungkan. Dalam hal ini, 𠈓 hanya satu titik kode tunggal, tetapi karena titik kode melebihi 0xFFFF, ia harus direpresentasikan sebagai 2 unit kode dengan menggunakan pasangan pengganti. Grapheme adalah konsep lain yang dibangun di atas titik kode, di mana grapheme dapat diwakili oleh satu titik kode atau beberapa titik kode, seperti terlihat dalam Hangul Korea atau banyak bahasa berbasis Latin.
nhahtdh
@nhahtdh, saya setuju, jawaban saya salah. Saya telah menulis ulang dan semoga sekarang menciptakan kejelasan yang lebih besar.
Jodrell