Bu dizenin uzunluğu neden içindeki karakter sayısından daha uzun?


146

Bu kod:

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

çıktılar:

Length a = 3
Length b = 4

Neden? Hayal edebildiğim tek şey, Çince karakterin 2 bayt uzunluğunda olması ve .Lengthyöntemin bayt sayısını döndürmesidir.


10
Sadece başlığa bakarak bunun bir vekil çift problemi olduğunu nasıl anladım? Ah, iyi Sistem, Küreselleşme senin müttefikin!
Chris Cirefice

9
UTF-16'da 4 bayt uzunluğunda, 2 değil
phuclv

𠈓char'ın ondalık değeri 131603'tür ve karakterler işaretsiz bayt olduğundan, bu değeri 4 yerine 2 karakterde elde edebileceğiniz anlamına gelir (işaretsiz 16 bit değer maksimum 65535'tir (veya 65536 varyasyon) ve bunu temsil etmek için 2 karakter kullanmak izin verir 65536 * 2 (131072) değil, 65536 * 65536 varyasyon (4,294,967,296, efektif olarak 32 bit değer) olan maksimum varyasyon sayısı için
GMasucci

3
@GMAsucci: UTF-16'da 2 karakter, ancak 4 bayt, çünkü bir UTF16 karakteri 2 bayt boyutunda, aksi takdirde 65536 varyasyon saklayamaz, ancak yalnızca 256.
Kaiserludi

4
'Her Yazılım Geliştiricisinin Kesinlikle Unicode ve Karakter Kümeleri Hakkında Bilmesi Gereken Mutlak Asgari (Mazeret Yok!)' Harika makalesini okumanızı tavsiye ederim joelonsoftware.com/articles/Unicode.html
ItsMe

Yanıtlar:


233

Diğer herkes yüzey cevabını veriyor, ancak daha derin bir mantık da var: "karakterlerin" sayısı tanımlanması zor bir sorudur ve hesaplanması şaşırtıcı derecede pahalı olabilir, oysa uzunluk özelliği hızlı olmalıdır.

Tanımlamak neden zor? Pekala, birkaç seçenek var ve hiçbiri diğerinden daha geçerli değil:

  • Kod birimlerinin sayısı (bayt veya diğer sabit boyutlu veri yığınları; C # ve Windows genellikle UTF-16 kullanır, bu nedenle iki baytlık parçaların sayısını döndürür), bilgisayarın yine de bu formdaki verilerle ilgilenmesi gerektiğinden kesinlikle önemlidir. birçok amaç için (örneğin bir dosyaya yazmak, karakterlerden çok baytları önemsiyor)

  • Unicode kod noktalarının sayısını hesaplamak oldukça kolaydır (ancak O (n) çünkü dizeyi vekil çiftler için taramanız gerekir) ve bir metin editörü için önemli olabilir ... ama aslında karakter sayısıyla aynı şey değildir ekrana basılmıştır (grafik olarak adlandırılır). Örneğin, bazı aksanlı harfler iki biçimde temsil edilebilir: tek bir kod noktası veya birlikte eşleştirilmiş iki nokta, biri harfi temsil eder ve diğeri "ortak mektubuma aksan ekle". Çift iki karakter mi olur yoksa bir mi? Buna yardımcı olmak için dizeleri normalleştirebilirsiniz, ancak tüm geçerli harflerin tek bir kod noktası gösterimi yoktur.

  • Grafiklerin sayısı bile, diğer faktörlerin yanı sıra yazı tipine bağlı olan basılı bir dizenin uzunluğu ile aynı değildir ve bazı karakterler birçok yazı tipinde (karakter aralığı) bir miktar örtüşme ile basıldığından, ekrandaki dizenin zaten grafiklerin uzunluklarının toplamına eşit olması gerekmez!

  • Bazı Unicode noktaları geleneksel anlamda karakter bile değildir, daha ziyade bir tür kontrol işaretleyicisidir. Bir bayt sırası işaretçisi veya sağdan sola göstergesi gibi. Bunlar sayılıyor mu?

Kısacası, bir dizenin uzunluğu aslında gülünç derecede karmaşık bir sorudur ve bunun hesaplanması, veri tablolarının yanı sıra çok fazla CPU süresi alabilir.

Üstelik ne anlamı var? Bu ölçütler neden önemlidir? Pekala, davanız için buna sadece siz cevap verebilirsiniz, ama şahsen, bunların genellikle alakasız olduğunu düşünüyorum. Bulduğum veri girişini sınırlamak, daha mantıklı bir şekilde bayt sınırları ile yapılır, çünkü zaten aktarılması veya depolanması gereken şey budur. Ekran boyutunun sınırlandırılması, ekran tarafı yazılımı tarafından daha iyi yapılır - mesaj için 100 pikseliniz varsa, kaç karakter sığdıracağınız yazı tipine vb. Bağlıdır, ki bu zaten veri katmanı yazılımı tarafından bilinmemektedir. Son olarak, unicode standardının karmaşıklığı göz önüne alındığında, başka bir şey denerseniz muhtemelen uç durumlarda hatalar yaşayacaksınız.

Bu yüzden çok fazla genel amaçlı kullanımı olmayan zor bir sorudur. Kod birimlerinin sayısının hesaplanması önemsizdir - bu yalnızca temel alınan veri dizisinin uzunluğudur - ve basit bir tanımla genel bir kural olarak en anlamlı / yararlı olanıdır.

Bu nedenle b, 4"çünkü dokümantasyon öyle diyor" şeklindeki yüzey açıklamasının ötesinde bir uzunluğa sahiptir .


9
Esasen 'uzunluk' çoğu kodlayıcının düşündüğü şey değildir. Belki bir dizi daha spesifik özellik (örn. GlyphCount) ve Geçersiz olarak işaretlenmiş Uzunluk olmalıdır!
redcalx

8
@locster Katılıyorum, ancak Lengthdizilerle analojiyi sürdürmek için modası geçmiş olması gerektiğini düşünmüyorum .
Kroltan

2
@locster Eski olmamalı. Python bir çok mantıklı ve kimse onu sorgulamıyor.
simonzack

1
Bence.Uzun, ne olduğunu ve neden böyle olduğunu anladığınız sürece çok mantıklı ve doğal bir özelliktir. O zaman diğer diziler gibi çalışır (D gibi bazı dillerde, dil söz konusu olduğu sürece bir dizi kelimenin tam anlamıyla bir dizidir ve gerçekten iyi çalışır)
Adam D. Ruppe

4
Bu doğru değil (yaygın bir yanlış anlama) - UTF-32 ile lengthInBytes / 4 kod noktalarının sayısını verir , ancak bu "karakterlerin" veya grafiklerin sayısı ile aynı değildir . LATIN KÜÇÜK E HARFİNİ ve ardından bir KOMBİNE DİYAEREZİ düşünün ... tek bir karakter olarak yazdırılır, tek bir kod noktasına bile normalleştirilebilir, ancak yine de UTF-32'de bile iki birim uzunluğundadır.
Adam D. Ruppe

62

Gönderen belgelere ait String.Lengthmülkiyet:

Length özelliği , Unicode karakterlerinin sayısını değil, bu örnekteki Char nesnelerinin sayısını döndürür . Bunun nedeni, bir Unicode karakterinin birden fazla Char ile temsil edilebilmesidir . Her Char yerine her Unicode karakteriyle çalışmak için System.Globalization.StringInfo sınıfını kullanın .


3
Java String b, char dizilerinde UTF-16 gösterimini kullandığı için aynı şekilde davranır (4 için de basar). UTF-8'de 4 baytlık bir karakterdir.
Michael

32

1. sıradaki dizindeki karakteriniz "A𠈓C"bir Vekil Çifti

Hatırlanması gereken anahtar nokta, vekil çiftlerin 32 bitlik tek karakterleri temsil etmeleridir .

Bu kodu deneyebilirsiniz ve geri dönecektir True

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

Char.IsSurrogatePair Yöntemi (String, Int32)

trues parametresi, konum indeksinde ve + 1 dizininde bitişik karakterler içeriyorsa ve konum dizinindeki karakterin sayısal değeri U + D800 ile U + DBFF arasında değişiyorsa ve konum dizini + 1'deki karakterin sayısal değeri U'dan değişiyorsa + DC00 üzerinden U + DFFF; aksi takdirde false,.

Bu, String.Length özelliğinde daha ayrıntılı açıklanmıştır :

Length özelliği , Unicode karakterlerinin sayısını değil, bu örnekteki Char nesnelerinin sayısını döndürür . Bunun nedeni, bir Unicode karakterinin birden fazla Char ile temsil edilebilmesidir. Her Char yerine her Unicode karakteriyle çalışmak için System.Globalization.StringInfo sınıfını kullanın.


24

Diğer cevapların da işaret ettiği gibi görünür 3 karakter olsa bile 4 charnesne ile temsil edilmektedir . Bu yüzden Length4 değil, 3.

MSDN şunu belirtir:

Length özelliği, Unicode karakterlerinin sayısını değil, bu örnekteki Char nesnelerinin sayısını döndürür.

Ancak, gerçekten bilmek istediğiniz şey "metin öğelerinin" sayısı ise ve Charnesnelerin sayısı değilse StringInfosınıfı kullanabilirsiniz .

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

Ayrıca her bir metin öğesini şu şekilde numaralandırabilirsiniz

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

Dizide kullanmak foreachortadaki "harfi" iki charnesneye böler ve yazdırılan sonuç dizeye karşılık gelmez.


20

Bunun nedeni, Lengthözelliğin , unicode karakterlerinin sayısını değil, char nesnelerinin sayısını döndürmesidir . Sizin durumunuzda, Unicode karakterlerinden biri birden fazla char nesnesi (SurrogatePair) ile temsil edilir.

Length özelliği, Unicode karakterlerinin sayısını değil, bu örnekteki Char nesnelerinin sayısını döndürür. Bunun nedeni, bir Unicode karakterinin birden fazla Char ile temsil edilebilmesidir. Her Char yerine her Unicode karakteriyle çalışmak için System.Globalization.StringInfo sınıfını kullanın.


1
Bu cevapta belirsiz bir "karakter" kullanımı var. En azından ilkini kesin terminoloji ile değiştirmenizi öneririm.
Orbit'te Hafiflik Yarışları

1
Teşekkür ederim. Belirsizlik düzeltildi.
Yuval Itzchakov

10

Diğerlerinin dediği gibi, bu dizedeki karakter sayısı değil, Char nesnelerinin sayısıdır. 𠈓 karakteri, U + 20213 kod noktasıdır. Değer 16 bit karakter türünün aralığının dışında olduğundan, UTF-16'da yedek çift olarak kodlanmıştır D840 DE13.

Karakter uzunluğunu almanın yolu diğer cevaplarda belirtilmiştir. Bununla birlikte, Unicode'da bir karakteri temsil etmenin birçok yolu olabileceğinden dikkatli kullanılmalıdır. "à" 1 oluşturulmuş karakter veya 2 karakter (a + aksan) olabilir. Twitter durumunda olduğu gibi normalizasyon gerekebilir .

Bunu okumalı
Olumlu Unicode ve Karakter Kümeleri hakkında bilmeniz gereken, kesinlikle Mutlak Asgari Her Yazılım Geliştirici (No Excuses!)


6

Bunun nedeni, length()yalnızca Unicode kod noktalarından daha büyük olmayan kod noktaları için çalışmasıdır U+FFFF. Bu kod noktaları kümesi Temel Çok Dilli Düzlem (BMP) olarak bilinir ve yalnızca 2 bayt kullanır.

Unicode kod noktaları dışındaki Unicode kod noktaları BMP4 bayt yedek çiftler kullanılarak UTF-16'da temsil edilir.

Karakter sayısını (3) doğru saymak için, şunu kullanın: StringInfo

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

6

Tamam, .Net ve C # 'de tüm dizeler UTF-16LE olarak kodlanmıştır . A string, bir karakter dizisi olarak saklanır. Her charbiri 2 bayt veya 16 bitlik depolamayı kapsüller.

Tek bir harf, karakter, glif, sembol veya noktalama işareti olarak "kağıtta veya ekranda" gördüğümüz şey, tek bir Metin Öğesi olarak düşünülebilir. Unicode Standardı Ek # 29 UNICODE METİN BÖLÜMÜ'nde açıklandığı gibi , her Metin Öğesi bir veya daha fazla Kod Noktası ile temsil edilir. Kapsamlı bir Kod listesi burada bulunabilir .

Her bir Kod Noktasının bir bilgisayar tarafından dahili gösterimi için ikiliye kodlanması gerekir. Belirtildiği gibi, her biri char2 bayt depolar. Kod Noktaları veya daha düşük U+FFFFtek bir yerde saklanabilir char. Yukarıdaki Kod Noktaları U+FFFF, tek bir Kod Noktasını temsil etmek için iki karakter kullanılarak bir vekil çifti olarak saklanır.

Şu anda çıkarsayabileceğimizi bildiğimiz göz önüne alındığında, bir Metin Öğesi bir charolarak, iki karakterden oluşan bir Vekil Çifti olarak veya Metin Öğesi birden fazla Kod Noktasıyla temsil ediliyorsa, tek karakterlerin ve Vekil Çiftlerinin bazı kombinasyonlarının bir arada saklanabilir . Bu yeterince karmaşık değilmiş gibi, bazı Metin Öğeleri , Unicode Standard Annex # 15, UNICODE NORMALIZATION FORMS'da açıklandığı gibi farklı Kod Noktası kombinasyonlarıyla temsil edilebilir .


Perde arkası

Dolayısıyla, oluşturulduklarında aynı görünen dizeler aslında farklı bir karakter kombinasyonundan oluşabilir. Bu tür iki dizinin sıralı (bayt bayt) karşılaştırması bir farkı tespit eder, bu beklenmedik veya istenmeyen olabilir.

.Net dizelerini yeniden kodlayabilirsiniz. böylece aynı Normalleştirme Formunu kullanırlar. Normalleştirildikten sonra, aynı Metin Öğelerine sahip iki dize aynı şekilde kodlanacaktır. Bunu yapmak için string.Normalize işlevini kullanın . Ancak, bazı farklı Metin Öğelerinin birbirine benzediğini unutmayın. : -s


Öyleyse, tüm bunlar soruyla ilgili olarak ne anlama geliyor? Metin Öğesi '𠈓', tek Kod Noktası U + 20213 cjk birleşik ideograflar uzantısı b ile temsil edilir . Bu, tek charolarak kodlanamayacağı ve iki karakter kullanılarak Vekil Çifti olarak kodlanması gerektiği anlamına gelir . Bu yüzden string bbir tane chardaha uzun string a.

Eğer güvenilir bir şekilde (uyarıya bakın) bir içindeki Metin Öğelerinin sayısını saymanız stringgerekiyorsa, System.Globalization.StringInfosınıfı şu şekilde kullanmalısınız.

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

çıktı vermek,

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

beklenildiği gibi.


Uyarı

Unicode Metin Segmentasyonunun StringInfove TextElementEnumeratorsınıflarında .Net uygulaması genel olarak yararlı olmalı ve çoğu durumda arayanın beklediği bir yanıt verecektir. Bununla birlikte, Unicode Standardı Ek # 29'da belirtildiği gibi , "Kullanıcı algılarını eşleştirme hedefi her zaman tam olarak yerine getirilemez çünkü metin tek başına her zaman sınırları belirsiz bir şekilde karar vermek için yeterli bilgi içermemektedir."


Cevabınızın kafa karıştırıcı olabileceğini düşünüyorum. Bu durumda, 𠈓 yalnızca tek bir kod noktasıdır, ancak kod noktası 0xFFFF'yi aştığı için, vekil çifti kullanılarak 2 kod birimi olarak temsil edilmesi gerekir. Grapheme, Kore'nin Hangul'unda veya birçok Latin temelli dilde görüldüğü gibi, bir grafemin tek bir kod noktası veya birden çok kod noktası ile temsil edilebildiği kod noktasının üzerine inşa edilmiş başka bir kavramdır.
nhahtdh

@nhahtdh, katılıyorum, cevabım hatalıydı. Yeniden yazdım ve umarım şimdi daha fazla netlik yaratır.
Jodrell
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.