34 puan yazan GN⁺ 2025-09-13 | 1 yorum | WhatsApp'ta paylaş
  • 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 10 ile 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 10 ile 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
    Reklam
  • 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ık 0x0905
  • U+0905 kod 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
    Reklam
  • 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-32 00 00 00 41

Bonus: UTF-8 Playground

1 yorum

 
GN⁺ 2025-09-13
Hacker News görüşü
  • UTF-8’de devam baytlarının her zaman 10 ile 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.4

    • Evet, 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 characters kullanı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ışabilir

    • LEB128/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: 0 ise dizinin sonu, bir sonraki bayt yeni bir dizi; 1 ise MSB’si 0 olan 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ı. ASCII 0xxxxxxx, genişletilmiş karakterler 1xxxxxxx 0xxxxxxx, 1xxxxxxx 1xxxxxxx 0xxxxxxx vb. biçimde, 3 baytta en fazla 0x1FFFFF’e kadar kodlanabiliyor; bu da Unicode’un gerektirdiğinden fazla. self-synchronizing değil ama daha sıkıştırılmış. ASCII yine 1 bayt, ayrıca matematik sembolleri ya da Japonca gibi U+3FFF altı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 0b01100001 gelirse, üç karakterlik a�a elde edersiniz. Çıktıdaki bir karakterin başlangıç noktası olup olmadığını anlamak için ondan önceki 1–3 bayta da bakmak gerekir

    • En 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 parity ya 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ıya 8-bit clean deniyor. 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 var

    • Uzman 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 Return ile Line Feed gibi ö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 bitlik byte geleneğ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-x ailesi 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 edilebilmesiydi

    • 7 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’ı (EBCDIC koduyla) 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ır

  • UTF-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ı combining biçimde de bulunabiliyor

    • Yeterince 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 char karakterleri 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ği utf-7 de 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ıkla utf-7 ile kodlanmış bir e-posta aldım. Nasıl gönderildiğini hâlâ bilmiyorum

    • UTF-7 esas olarak e-posta gibi 8-bit clean olmayan 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 00000001 de olur, 11000000 10000001 de 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 10000001 değeri 128+1 olur ve 0-127 aralığı 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 dizisi 10000001 olmalıydı, düzeltildi)

    • Pek çok cevap eşzamanlama işaretinden (synchronization indicator) bahsediyor ama asıl soru, U+0080 neden c2 80, neden c0 80 değ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-UTF additive constants içeriyor (aşağıya bakın)

    2 baytlık diziler 2^11 kod taşıyabiliyor ama 0-7f geçersiz. Muhtemelen bunun, özel bir kazanç sağlamayan additive constants kullanımından daha iyi olduğuna karar verilmişti
    Ayrıntılar için utf-8-history.txt dosyasının en altına bakın

    • Asıl önemli olan, bayt desenlerinin self-synchronicity özelliğini koruması. Eğer 11000000 10000001 gibi 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ılabiliyor

    • quectophoton yorumunda dendiği gibi, devam baytlarının her zaman 10 ile 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

    • Güvenlik açısından, bozuk UTF-8 ile hiç uğraşmamak; veriyi doğrudan tehlikeli madde gibi reddedip hata üretmek gerekir. Aksi durumda doğrulama atlatma saldırılarına açık hâle gelirsiniz
  • 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

    • UTF-8, uyumluluk için neredeyse hiçbir şey feda etmemiş gibi duruyor
      21 bitten büyük değerlerin kodlanması engellenmiş durumda. Bu, UTF-16 ile uyumluluk yüzünden böyle; UTF-16’nın surrogate mekanizması en fazla 2^21-1 değerine kadar çıkabiliyor. Belki bir gün bu sınırdan pişman olabiliriz. 21 bitten büyük kod noktalarını engellemek için bunun dışında gerçek bir gerekçe yok gibi

    • İlerleme adına yetki sahibi birilerinin bir şeyi cesurca değiştirmesini seviyorum
      Ama bağımlı olduğunuz sistemlerin sırf biri parametre adını değiştirdi ya da standart kütüphanenin bir kısmı “dağınık” görünüyor diye bozulması hiç eğlenceli değil

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

    • Katkı için teşekkürler, şu an birleştirildi ve hemen kullanıma alındı
  • 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