UTF-8 mükemmel bir tasarım
(iamvishnu.com)- UTF-8, milyonlarca karakteri ifade ederken ASCII ile geriye dönük uyumluluğu koruyan değişken uzunluklu bir kodlama yöntemidir
- ASCII ile aynı 7 bitlik alan (
U+0000~U+007F) aynen 1 bayt olarak kullanılır; bu sayede ASCII dosyaları aynı zamanda geçerli UTF-8 dosyalarıdır - Bunun dışındaki karakterler 2~4 baytlık dizilerle gösterilir; ilk baytın bit deseni uzunluğu belirler ve sonraki baytlar
10ile başlayarak bunların devam baytı olduğunu ayırt eder - Bu tasarım sayesinde UTF-8, evrensel karakter kümesini işlerken mevcut ASCII sistemleriyle de tam uyumluluk sağlar ve en yaygın kullanılan karakter kodlaması haline gelir
- UTF-16, UTF-32 gibi diğer Unicode kodlamaları bu tür bir ASCII uyumluluğu sunmaz
UTF-8 tasarımının üstünlüğü
- UTF-8 kodlamasıyla ilk karşılaştığımda, farklı dillerdeki ve yazı sistemlerindeki milyonlarca karakteri tek bir yapıda birleştirirken aynı zamanda mevcut ASCII ile uyumlu bir yapı sunması beni çok etkilemişti
- Temelde UTF-8 en fazla 32 biti kullanır, ancak ASCII yalnızca 7 bit kullanır
- UTF-8'in tasarım ilkeleri şöyledir
- Tüm ASCII kodlu dosyalar geçerli UTF-8 dosyalarıdır
- Yalnızca ASCII karakterleri içeren tüm UTF-8 dosyaları geçerli ASCII dosyalarıdır
- Sadece 128 karakterle sınırlı eski bir sistemi milyonlarca karakteri kapsayan bir yapıyla birleştirme fikri son derece yenilikçidir
UTF-8'in temel kavramı
- UTF-8, Unicode karakter kümesindeki tüm karakterleri göstermek amacıyla tasarlanmış değişken uzunluklu karakter kodlamasıdır (variable-width encoding)
- Her karakteri 1~4 bayt ile kodlar
- İlk 128 karakter (
U+0000~U+007F) tek bayt olarak saklanır; böylece ASCII ile geriye dönük uyumluluk sağlanır - Diğer karakterler iki, üç veya dört baytla kodlanır
- İlk bayttaki öncü bitler, kodlama için gereken toplam bayt sayısını belirler
| 1 bayt deseni | Bayt sayısı | Tüm bayt dizisi deseni |
|---|---|---|
| 0xxxxxxx | 1 | 0xxxxxxx (tipik ASCII) |
| 110xxxxx | 2 | 110xxxxx 10xxxxxx |
| 1110xxxx | 3 | 1110xxxx 10xxxxxx 10xxxxxx |
| 11110xxx | 4 | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx |
- Çok baytlı dizilerde 2., 3. ve 4. bayt her zaman
10ile başlar; bu da bunların devam baytı olduğunu açıkça gösterir - Ana bayt ile devam baytlarındaki kalan bitler birleştirilerek tek bir kod noktası oluşturulur
- Kod noktası, benzersiz Unicode karakter tanımlayıcısıdır ve "U+" önekiyle birlikte onaltılık biçimde gösterilir
- Örnek: "A" karakterinin kod noktası
U+0041'dir
- UTF-8 kodlama baytlarından karakter çözümleme akışı şöyledir
- 1. Baytı oku; ilk bit 0 ise bunu tek baytlık karakter (ASCII) olarak kabul et, kalan 7 bit ile karakteri belirle ve sonraki bayta geç
- 2. Eğer 0 değilse
- 110 ise 2 baytlık karakterdir; sonraki 1 baytı daha oku
- 1110 ise 3 baytlık karakterdir; sonraki 2 baytı oku
- 11110 ise 4 baytlık karakterdir; ek olarak 3 bayt oku
- 3. Belirlenen baytlarda öncü bitler dışındaki kalan bitleri birleştir ve bunu kod noktasının ikili değeri olarak kullan
- 4. Unicode karakter kümesinde bu kod noktasını bul ve ekranda göster
- 5. Sonraki bayt için tekrarla
Örnek: Hintçe harf "अ"
- UTF-8 gösterimi:
11100000 10100100 10000101(3 bayt) - İlk bayt (
11100000) → bunun 3 baytlık bir karakter olduğunu gösterir - Üç bayttaki anlamlı bitlerin birleşimi →
00001001 00000101= onaltılık0x0905 U+0905kod noktası, Devanagari harfi "अ" anlamına gelir
Dosya örnekleri
-
1.
Hey👋 Buddy- Toplam 13 bayttan oluşur
- ASCII karakterleri (H, e, y, B, u, d, d, y, boşluk) → her biri 1 bayt
- 👋 (U+1F44B) → 4 bayt
11110000 10011111 10010001 10001011
- Bu dosya geçerli bir UTF-8 dosyasıdır, ancak ASCII dışı karakter (emoji) içerdiği için ASCII ile geriye dönük uyumlu değildir
- Toplam 13 bayttan oluşur
-
2.
Hey Buddy- Toplam 9 bayt, hepsi ASCII aralığındadır
- Bu nedenle bu dosya aynı anda hem geçerli bir ASCII dosyası hem de geçerli bir UTF-8 dosyasıdır
Diğer kodlamalarla karşılaştırma
- ASCII ile uyumluluk sunan başka bazı kodlamalar da vardır, ancak UTF-8 kadar yaygın kullanılmazlar
- GB18030 (Çin standardı) gibi kodlamalar da ASCII uyumluluğu sunar, ancak yaygın değildir
- ISO/IEC 8859 ailesi, tek baytlık genişletme (en fazla 256 karakter) olduğu için sınırlıdır
- UTF-16/UTF-32 ASCII uyumluluğuna sahip değildir
- 'A' (U+0041): UTF-16
00 41, UTF-3200 00 00 41
- 'A' (U+0041): UTF-16
Bonus: UTF-8 Playground
- UTF-8 kodlama sürecini görsel olarak keşfetmenizi sağlayan etkileşimli araç
- https://utf8-playground.netlify.app/
1 yorum
Hacker News görüşü
UTF-8’de devam baytlarının her zaman
10ile başlaması sayesinde, rastgele bir bayta atlasanız bile o konumun bir karakter başlangıcı mı yoksa devam baytı mı olduğunu hemen anlayabilirsiniz; bu yüzden bir sonraki ya da önceki karakter başlangıcını bulmak kolaydır. EBML’nin değişken uzunluklu tamsayı kodlama yöntemi gibi bir kodlama kullanılsaydı (tek baytlık ASCII uyumluluğunu korumak için 1/0 tersleme yaklaşımı), rastgele bir konumdan karakter başlangıcını hemen saptamak zor olurdu. Ayrıntılar için RFC8794 section 4.4Evet, UTF-8’in büyük avantajlarından biri bu. UTF-8 dizelerini baştan okumadan ileri geri serbestçe gezebiliyorsunuz. Python örneğinde, karakter düzeyinde dizinlemeyi mümkün kılmak için CPython
wide characterskullanıyor. Bir zamanlar 2 bayt ya da 4 baytlık karakterler seçilebiliyordu, sonra çalışma anında otomatik geçişe geçildi. Ama bu hâlâwide character, UTF-8 değil. Örneğin tek bir emoji bile dizenin boyutunu dört katına çıkarabiliyor. Ben olsam iç temsilde UTF-8 kullanır, dizin tipini opak bir nesne yapar ve küçük tamsayılar ekleyip çıkararak dize içinde ileri geri hareket ettirirdim. Gerçekten tamsayıya çevirdiğinizde ya da doğrudan alt indis kullandığınızda dize dizinini hesaplayan bir yapı olurdu. Böyle bir yaklaşımda düzenli ifadeler gibi şeyler de opak dizin nesnelerini kullanarak UTF-8 temsili üzerinde düzgün çalışabilirLEB128/VLQ’nun EBML değişken uzunluklu tamsayı yönteminden daha iyi olduğunu düşünüyorum. Ayrım, bayt içindeki MSB ile yapılıyor:
0ise dizinin sonu, bir sonraki bayt yeni bir dizi;1ise MSB’si0olan bayta ulaşana kadar geriye sarılır. SIMD ile optimize edilmiş verimli uygulamalar da var. LEB128 ile VLQ arasındaki fark sadece endian farkı. ASCII0xxxxxxx, genişletilmiş karakterler1xxxxxxx 0xxxxxxx,1xxxxxxx 1xxxxxxx 0xxxxxxxvb. biçimde, 3 baytta en fazla0x1FFFFF’e kadar kodlanabiliyor; bu da Unicode’un gerektirdiğinden fazla.self-synchronizingdeğil ama daha sıkıştırılmış. ASCII yine 1 bayt, ayrıca matematik sembolleri ya da Japonca gibiU+3FFFaltındaki kod noktaları 2 baytta temsil edilebildiği için kod boyutunu küçültmekte avantajlıBunun ancak metnin bozulmadığı ya da kötü niyetle değiştirilmediği varsayımı altında geçerli olduğunu düşünüyorum. Hatalı UTF-8 dizilerini ayrıştırırken ya da escape ederken çok sayıda güvenlik açığı ortaya çıktı. Örnekler için CVE-2025-1094 PostgreSQL sorunu ve ayrıca UTF-8 ile ilgili CVE listesi
Bu her zaman doğru değil. UTF-8 geçersiz olduğunda karakterler devam baytına dönüşebiliyor. Örneğin
0b01100001 0b10000000 0b01100001gelirse, üç karakterlika�aelde edersiniz. Çıktıdaki bir karakterin başlangıç noktası olup olmadığını anlamak için ondan önceki 1–3 bayta da bakmak gerekirEn fazla 4 baytlık çok baytlı karakterler varsa, bulunduğunuz konumun devam baytı olup olmadığını anlamak için geriye en fazla 3 bayt bakmanız yeterlidir. Başlangıç baytı çıkmazsa tek baytlık bir karakter olduğunu anlarsınız. Kütüphane UTF-8’i doğru tanımasa bile, kesilmiş bir slice’ın başındaki ve sonundaki bozuk baytları görmezden gelip yine de makul bir dize çıkarabilmek için, bunun bir kurtarma amacıyla böyle tasarlandığını tahmin ediyorum
UTF-8’in gerçekten harika olduğunu düşünüyorum. Kilit nokta, ASCII’nin sadece 7 bit kullanma kararı. 1963’te bile 7 bit seçimi biraz sıra dışıydı. Bunun sadece tarihsel bir tesadüf mü olduğunu merak ediyorum. ASCII’yi tasarlayanlar bir bit daha kullanıp ek semboller koymayı düşündüler mi, yoksa code page ya da genişletilebilirlik gibi şeyleri mi hesaba kattılar, merak ediyorum
Kesin nedeni bilmiyorum ama eski zamanlarda 8 bit her zaman hazır değildi.
7-bit + 1 parityya da bir bayrak biti yaygındı (bu yüzden e-posta hâlâ quoted-printable ile 7 bit üzerinden 8 bit kodlayabiliyor). 8 bitin tamamını bozulmadan aktarabilen yapıya8-bit cleandeniyor. Bu bağlamda UTF-8 de aslında ASCII’de boşa çıkan 8. biti çok iyi kullanan bir örnek. Ayrıca 8-bit clean açıklaması da varUzman değilim ama ASCII’nin tarihini daha önce biraz okumuştum. ASCII’nin kökeni teleprinter kodlarına dayanıyor; onlar da telgraf kodlarından gelişmişti. Mors kodu değişken uzunluklu olduğu için makinede uygulaması zahmetliydi. Bu yüzden 5 bitlik Baudot kodu ortaya çıktı. Amaç sabit uzunluklu kodla makineleri basitleştirmekti, operatör yorgunluğunu azaltmak da hedeflerden biriydi. Baudot kodu yüzünden bugün hâlâ sembol oranına baud diyoruz. Daha sonra daktiloyla delikli kâğıt şerit girişi kullanılmaya başlanınca esneklik arttı ve
Carriage ReturnileLine Feedgibi özel karakterler eklendi. İlk bilgisayar endüstrisi girdi olarak delikli kartları benimsedi; IBM kartları daha hızlı işlemek için yeni bir 8 bit sistemi geliştirdi ve bu ASCII tabanlı hâle geldi. Sonuçta ikili kodlar teknoloji geliştikçe genişletildi. ASCII de 8 bitlikbytegeleneğinden önce gelen bir geçiş ürünüydüAslında boşta kalan bit parity amacıyla yeniden kullanılmak içindi
ASCII’nin 8 bitlik uzantıları (
ISO 8859-xailesi gibi) onlarca yıl boyunca çok yaygın kullanıldı ve Windows’un standart code page’lerinde hâlâ kullanılıyor. ASCII en baştan 8 bit olsaydı bile, temel karakterler yine ilk 128’de toplanacağı için UTF-8’e uygun olurdu diye düşünüyorum. Tarihsel tesadüf denecekse, bu ASCII’nin 7 bit olması değil; o dönemde bilgisayar gelişiminin çoğunlukla İngilizce konuşulan dünyada yaşanması ve İngilizcenin 7 bitle rahatça ifade edilebilmesiydi7 bit kendi başına o kadar da garip değil. Baudot 5 bitti, bu yetmeyince 6 bitlik kodlar geldi, sonra 7 bitlik ASCII yapıldı. IBM System/360 ile 8 bitlik byte’ı (
EBCDICkoduyla) standartlaştırdı ama diğer bilgisayar üreticilerinde byte uzunluğu sabit değildi. 7 bit tuhaf görünebilir ama mesele, karakterlerin ve sistem sözcüklerinin her zaman birbirine temizce oturmamasıdırUTF-8’in beklenenden bile iyi bir tasarım olduğuna katılıyorum. Ama Unicode’un kapsamı fazla genişliyor gibi. Unicode’a nelerin dahil edilmesi gerektiği konusunda soru işaretleri var. İlk bakışta “insanlığın iletişim amacıyla kullandığı tüm ayırt edilebilir, basılabilir karakterler” gibi görünüyor ama pratikte öyle değil.
Ayrım net değil. Kod noktaları
combiningbiçimde de bulunabiliyorYeterince somut değil. Aynı karakter birden çok şekilde yazılabiliyor. Dışarıdan aynı görünen bir karakterin farklı kod noktaları ve anlamları olabiliyor
Hepsi yazdırılabilir değil.
control charkarakterleri var. ASCII uyumluluğu için eklendiler ama kendine özgü kontrol karakterleri de artıyor Animasyonlu Unicode noktaları henüz yok sanırım. En azından yazdırılabilen bir şeyi kâğıda basabiliyoruz. Ama bunun gelecekte de değişmeden kalacağından emin değilim. Bu arada yazarın bahsetmediğiutf-7de var. UTF-8’e benziyor ama 80’lerin ağ ortamlarında son bitin güvenle kullanılamadığı varsayımıyla tasarlanmıştı. Bir keresinde yanlışlıklautf-7ile kodlanmış bir e-posta aldım. Nasıl gönderildiğini hâlâ bilmiyorumUTF-7 esas olarak e-posta gibi
8-bit cleanolmayan aktarım ortamları için yapıldı. Artık çağ dışı kaldı ve supplementary plane kodlamasını da desteklemiyor (yalnızca UTF-16 surrogate pair ile mümkün). Bir de UTF-9 var; o da 1 Nisan şakası olarak yazılmış bir RFC’de geçen bir parodi (PDP-10 gibi 36 bitlik ortamlar için)Benim hep merak ettiğim bir şey var: Unicode kod noktalarının gereksiz yere uzun bayt dizileriyle de kodlanabilmesi. UTF-8 bunu yasaklıyor ve yalnızca en kısa diziyi kabul ediyor. Örneğin
00000001de olur,11000000 10000001de aynı şeyi temsil edebilir. O hâlde neden baştan, başka türden geçersiz kodlamaların hiç var olamayacağı bir yapı yapılmadı? Mesela 2 baytlık bir dizinin en başını son geçerli değere eşitlesek,11000000 10000001değeri128+1olur ve0-127aralığı yine 1 bayt kalır. Böylece geçersiz kod da kalmaz, bazı uç durumlarda dizeler biraz daha kısa bile olurdu. Acaba dönemin donanım maliyetleri yüzünden mi düşünülmedi? (Güncelleme: Gerçek bit dizisi10000001olmalıydı, düzeltildi)synchronization indicator) bahsediyor ama asıl soru,U+0080nedenc2 80, nedenc0 80değil, bununla ilgili. Bence nedeni şu a)overlong encodinge izin verilirse, sadece kısa dizileri kontrol eden bazı sistemlerde güvenlik boşlukları oluşur b) Standart UTF-8 kodlama/kod çözme yalnızca bit maskesi (bitmask) ve kaydırma (bitshift) ile yapılabiliyor. Önerilen yöntemde ek çıkarma işlemleri gerekir 1992’deki e-posta tartışmalarında bu konu ele alınmıştı; FSS-UTFadditive constantsiçeriyor (aşağıya bakın)Asıl önemli olan, bayt desenlerinin
self-synchronicityözelliğini koruması. Eğer11000000 10000001gibi devam baytı desenini bozarsanız, kırpılmış bir UTF-8 akışında kod noktası sınırlarını her zaman bulabilme özelliğini kaybedersiniz. Bir de bu yönteme toplama/çıkarma eklenirse kod çözücü performansı düşer. Şu an her şey yalnızca bit işlemleriyle yapılabiliyorquectophotonyorumunda dendiği gibi, devam baytlarının her zaman10ile başlaması gerekiyor ki ayrıştırıcı akışın herhangi bir noktasında kod noktası sınırlarını bulabilsin. Bu da gerçekten UTF-8’in tasarlandığı 90’ların başındaki güvenilmez iletim ortamları düşünülerek alınmış bir karardıBu önerilen yöntem kullanılsaydı, kodlama/kod çözme hesapları daha karmaşık ve daha yavaş olurdu. Bugün birkaç bit kaydırma işlemiyle bitiyor ama o dönemki (90’lar) yavaş bilgisayarlar için bu önemliydi
UTF-8 tasarımı hakkında daha fazlasını görmek isterseniz Russ Cox’un one-pager yazısına ve Rob Pike’ın tarih özeti metnine bakın
UTF-8 harika ve keşke her yerde kullanılsa (sana bakıyorum JavaScript). Ama tek kusuru, geçersiz bayt dizilerinin nasıl yorumlanacağı konusunda standardın net olmaması. “Her bayt dizisi için mutlaka bir yorum tanımla” yaklaşımı daha kusursuz olurdu diye düşünüyorum. HTML5 spesifikasyonundaki gibi bir yöntemle bunun gayet başarılı şekilde işletilebileceğini düşünüyorum
Geriye dönük uyumlulukla aram hem iyi hem kötü. Kafa karıştıran yanını sevmiyorum ama bir şeyi kırmayı göze alarak ilerleme fikrini de takdir ediyorum. Yine de UTF-8 ya da EAN gibi, uyumluluğu korurken aynı zamanda zekice tasarlanmış örnekler bana çok hoş geliyor. Açıkçası UTF-8, uyumluluk için neredeyse hiçbir şey feda etmemiş gibi duruyor
Değiştirecek olsaydım, belki bazı kontrol karakterlerini daha yaygın kullanılan karakterlerle değiştirip biraz daha alan kazanmaya çalışırdım (Unicode uyumluluğunu da bozmayı göze alırsak). Ama çok baytlı karakter kodlama formatı olarak tek başına bakınca da neredeyse optimal olduğunu düşünüyorum
UTF-8 playground bağlantısını (utf8-playground.netlify.app) gerçekten çok beğendim. Arayüzde kod noktası doğrudan girilebilse daha da iyi olurdu (şimdilik yalnızca URL ile yapılabiliyordu). (Güncelleme: Zaten PR birleştirilmiş, artık mümkün)
Bu konuya daha derin dalmak istiyorsanız ve Advent of Code tarzını seviyorsanız, i18n-puzzles içinde metin kodlamalarıyla ilgili epey bulmaca var. UTF-8 ve UTF-16 gibi şeylerin nasıl çalıştığını gerçekten içselleştirmeye yardımcı oluyor
Güzel yazı, keyifle okudum. Ben de UTF-8’i tavsiye ediyorum ama ancak BOM ile birlikte kullanıldığında iyi olduğunu düşünüyorum. Aksi takdirde uygulama bunun UTF-8 olduğunu anlayamaz ve kaydederken de UTF-8 kullanması gerektiğini kaçırabilir. Örneğin Windows’ta yeni bir metin belgesi oluşturduğunuzda, dosya boş olsa bile yalnızca BOM varsa herhangi bir uygulama bunun daha sonra düzenlenip kaydedilirken UTF-8 olarak tutulması gerektiğini otomatik anlayabilir. BOM yoksa uygulama kodlamayı otomatik tespit etmeye çalışsa bile buna tam güvenilemez; aksanlı harfler gibi özel karakterler eklendiğinde karışıklık daha da büyür (editör dili yanlış tahmin edebilir ya da Notepad bir güncellemeden sonra varsayılan kodlamayı değiştirebilir). Bu yüzden UTF-8 kullanımına katılıyorum ama BOM’un işletim sistemi ve uygulama tarafında varsayılan olması gerektiğini düşünüyorum