Ders 06 / 16
Karakter Kodlamaları
ASCII'den Unicode'a karakter kümeleri, kod noktası ile bayt dizisi ayrımı ve UTF-8 kodlaması.
İçindekiler
Sayılar için temsil kuralları donanımın yapısından türetilebiliyordu: konum değeri,
ikiye tümleyen ve kayan nokta, aritmetiğin gereklerine göre tasarlanmıştı. Metinde
böyle bir doğal zorunluluk yoktur. A harfinin hangi sayıya karşılık geleceği bir
tasarım kararıdır ve bu kararın, yazan ile okuyan taraflarca paylaşılması gerekir.
Bu dersin konusu, o paylaşımın nasıl kurulduğudur. İki ayrı soru vardır ve karıştırılmaları yaygın hata kaynağıdır: hangi karakterin hangi numarayı aldığı, ve o numaranın baytlara nasıl yazıldığı.
Karakter Kümesi ve Kodlama
Karakter kümesi (character set), hangi karakterlerin var olduğunu ve her birine hangi numaranın verildiğini söyler. Bu numaraya kod noktası (code point) denir.
Kodlama (encoding), kod noktasının baytlara nasıl yazıldığını söyler.
Küçük kümelerde bu iki katman çakışır: karakter sayısı 256’yı geçmiyorsa her kod noktası tek bir bayta sığar ve ayrım görünmez olur. Küme büyüdüğünde ayrım zorunlu hâle gelir — tek baytın taşıyamayacağı bir numarayı yazmanın birden çok yolu vardır ve hangisinin kullanıldığı belirtilmelidir.
ASCII
ASCII, 7 bitlik bir karakter kümesidir: kod noktası tanımlar. Yerleşim rastgele değildir, aritmetik kolaylıklar gözetilerek düzenlenmiştir:
| Aralık (onaltılık) | İçerik |
|---|---|
00–1F |
Denetim karakterleri (satır sonu, sekme, dosya sonu vb.) |
20 |
Boşluk |
30–39 |
0–9 rakamları |
41–5A |
A–Z büyük harfleri |
61–7A |
a–z küçük harfleri |
Rakamların ardışık olması, bir rakam karakterinden sayısal değerine geçmeyi tek çıkarma
işlemine indirger: '7' - '0' = 7. Büyük ve küçük harflerin arasındaki fark tam olarak
0x20’dir, yani tek bir bit: A = 0x41 = 0100 0001, a = 0x61 = 0110 0001.
Harf büyütme ve küçültme, ASCII sınırları içinde tek bir bit işlemidir.
Kursun ortak örneği burada üçüncü yorumuna kavuşur. 0x41424344 örüntüsünün dört baytı
0x41, 0x42, 0x43, 0x44’tür; ASCII karşılıkları:
Aynı otuz iki bit, işaretsiz tam sayı olarak , binary32 olarak
, metin olarak ABCD okunur. Bir dosyanın veya ağ paketinin
içeriğini doğru okumak, bu üç yorumdan hangisinin geçerli olduğunu bilmeyi gerektirir;
biçim tanımının işi budur.
Kod Sayfaları ve Bıraktıkları Sorun
ASCII yalnızca 128 kod noktası tanımladığından, sekiz bitlik baytın üst yarısı boş kalır.
Türkçedeki ğ, ş, ı gibi harfler bu boş yarıya yerleştirilmiştir — ancak her dil
topluluğu kendi yerleştirmesini yaptığı için birbiriyle uyumsuz kod sayfaları ortaya
çıkmıştır.
Sonuç, aynı baytın hangi kod sayfasıyla okunduğuna göre farklı harf vermesidir. Yanlış kod sayfasıyla açılan metinde harflerin bozuk simgelere dönüşmesi bu uyumsuzluğun belirtisidir. Daha ağır bir sınır da vardır: tek bayt en fazla 256 karakter taşır; çok dilli tek bir belge, kod sayfası yaklaşımıyla yazılamaz.
Unicode
Unicode bu sorunu, karakter kümesini kodlamadan ayırarak çözer. Dünyadaki yazı
sistemlerinin karakterlerine tek bir numara uzayı içinde kod noktası atar; gösterimi
U+ önekiyle ve onaltılık yazılır:
| Karakter | Kod noktası |
|---|---|
A |
U+0041 |
ç |
U+00E7 |
ğ |
U+011F |
漢 |
U+6F22 |
Kod noktası uzayı U+0000 ile U+10FFFF arasındadır; bu, bir milyondan fazla kod noktası demektir. İlk 128 kod noktası ASCII ile birebir aynıdır — bu, geriye dönük uyumluluğu sağlayan bilinçli bir karardır.
Unicode bir kodlama değildir: U+011F numarasının baytlara nasıl yazılacağını söylemez.
Bunu kodlamalar söyler.
UTF-8
UTF-8, kod noktasının büyüklüğüne göre 1 ile 4 arasında bayt kullanır. Bayt yapısı, ilk baytın önek bitlerinden okunur:
| Kod noktası aralığı | Bayt sayısı | Örüntü |
|---|---|---|
| U+0000 – U+007F | 1 | 0xxxxxxx |
| U+0080 – U+07FF | 2 | 110xxxxx 10xxxxxx |
| U+0800 – U+FFFF | 3 | 1110xxxx 10xxxxxx 10xxxxxx |
| U+10000 – U+10FFFF | 4 | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx |
Kod noktasının bitleri, x ile gösterilen yuvalara soldan sağa yerleştirilir. ç
(U+00E7 = 0000 0000 1110 0111) iki bayt gerektirir; anlamlı bitleri 00011 100111
olarak bölünür:
Aynı kuralla 漢 (U+6F22) üç bayta yazılır: 0xE6 0xBC 0xA2.
Tasarımın üç sonucu vardır:
- ASCII uyumluluğu. U+007F’ye kadar olan kod noktaları tek bayta, ASCII ile birebir aynı değerlere yazılır. Yalnızca ASCII içeren bir belge, UTF-8 belgesi olarak da geçerlidir.
- Kendini eşleyebilme. Devam baytları her zaman
10ile başlar; bir baytın karakter başlangıcı mı yoksa devamı mı olduğu, tek baştaki bitlerden anlaşılır. Bozulmuş bir akışta bir sonraki karakter sınırı bulunabilir. - Değişken uzunluk. Bir karakterin kaç bayt tuttuğu içeriğe bağlıdır; bu, metin işlemede doğrudan sonuçlar doğurur.
UTF-16 ve UTF-32 alternatif kodlamalardır. UTF-32 her kod noktasını sabit dört bayta yazar; erişimi doğrudandır ama yer tüketimi yüksektir. UTF-16 çoğu karakteri iki bayta yazar, kalanları için vekil çift (surrogate pair) kullanır — yani o da değişken uzunlukludur, sabit sanılması yaygın bir yanılgıdır.
Kod Noktası, Bayt ve Grafem
Değişken uzunluklu kodlamada “uzunluk” tek anlamlı bir kavram değildir. Üç ayrı sayım vardır ve üçü de farklı olabilir:
- Bayt sayısı — dosyada veya ağda kapladığı yer.
- Kod noktası sayısı — Unicode numaralarının adedi.
- Grafem sayısı — kullanıcının “harf” olarak gördüğü birimler.
Üçüncü ayrımın kaynağı birleştirici işaretlerdir. é karakteri iki farklı biçimde
yazılabilir: tek kod noktası olarak U+00E9, veya e (U+0065) ile birleştirici aksan
işaretinin (U+0301) art arda gelmesiyle. İki biçim ekranda aynı görünür, kod noktası
sayıları farklıdır ve doğrudan karşılaştırıldıklarında eşit çıkmazlar. Normalleştirme
işlemi, karşılaştırma öncesinde iki biçimden birini seçerek bu farkı ortadan kaldırır.
Harf büyütme ve küçültmenin de dilden bağımsız olmadığı burada görülür. ASCII’de tek bit
olan işlem, Türkçede i ↔ İ ve ı ↔ I eşleşmelerini gerektirir; dil bilgisi
olmadan yapılan dönüşüm yanlış sonuç verir.
metin = "ABCD" print(metin.encode("utf-8")) # b'ABCD' — 4 bayt turkce = "çağ" print(turkce.encode("utf-8")) # b'\xc3\xa7a\xc4\x9f' print(len(turkce), len(turkce.encode("utf-8"))) # 3 5 print(ord("ç"), hex(ord("ç"))) # 231 0xe7 — kod noktası U+00E7 print(chr(0x6F22)) # 漢 ayrisik = "e\u0301" # e + birleştirici aksan (U+0301) tek = "\u00e9" # tek kod noktası (U+00E9) print(ayrisik == tek) # False print(len(ayrisik), len(tek)) # 2 1 import unicodedata print(unicodedata.normalize("NFC", ayrisik) == tek) # True
len(turkce) sonucunun , kodlanmış hâlinin bayt olması, iki sayımın neden
karıştırılmaması gerektiğini gösterir. Sabit uzunluk varsayan bir dizgi işlemi — örneğin
“ilk üç baytı al” — çok baytlı bir karakteri ortasından böler ve geçersiz bayt dizisi
üretir.
Özet
- Karakter kümesi karakterlere numara (kod noktası) atar; kodlama o numaranın baytlara nasıl yazılacağını belirler. İki katman ayrıdır.
- ASCII 128 kod noktası tanımlar; rakamlar ve harfler ardışıktır, büyük–küçük harf farkı
tek bittir. Kursun ortak örneği
0x41424344, ASCII yorumundaABCDmetnidir. - Tek baytlık kod sayfaları hem uyumsuzdur hem de 256 karakterle sınırlıdır; çok dilli metin bu yaklaşımla yazılamaz.
- Unicode tek bir kod noktası uzayı tanımlar ve ilk 128 kod noktasında ASCII ile örtüşür.
- UTF-8 kod noktasını 1–4 bayta yazar; ASCII ile uyumludur, devam baytları
10ile başladığı için kendini eşleyebilir. - Bayt sayısı, kod noktası sayısı ve grafem sayısı farklı büyüklüklerdir; birleştirici işaretler ve dile bağlı harf dönüşümleri bu farkı görünür kılar.
Sonraki Adım
Bir karakterin birden çok bayta yazıldığı anlaşıldı. Peki bu baytlar belleğe hangi
sırayla yerleşir? Sayılar için de aynı soru geçerlidir: 0x41424344 değerinin dört
baytı bellekte hangi sırada durur? Sonraki ders, bu sıranın donanıma göre değiştiğini ve
veri iki sistem arasında taşınırken neden bir sorun kaynağına dönüştüğünü ele alacak.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.