Unicode ve Karakter Kodlama
Bilgisayarlar sadece sayıları anlar — 0 ve 1. Ama biz ekranda harfler, semboller, emojiler görüyoruz. Peki bilgisayar "A" harfini nasıl biliyor? İşte karakter kodlama tam olarak bu sorunu çözer: hangi sayı hangi karaktere karşılık gelecek?
Bu derste ASCII'den Unicode'a uzanan yolculuğu, Java'nın bu konudaki yaklaşımını ve Türkçe karakter sorunlarının neden yaşandığını öğreneceğiz.
ASCII — Her Şeyin Başlangıcı
1960'larda Amerikalılar bir standart oluşturdular: ASCII (American Standard Code for Information Interchange). 7 bit kullanır, toplam 128 karakter tanımlar.
| Aralık | İçerik |
|---|---|
| 0-31 | Kontrol karakterleri (satır sonu, tab vb.) |
| 32-47, 58-64, 91-96, 123-127 | Noktalama, semboller |
| 48-57 | Rakamlar (0-9) |
| 65-90 | Büyük harfler (A-Z) |
| 97-122 | Küçük harfler (a-z) |
char a = 'A';
System.out.println((int) a); // 65
char sifir = '0';
System.out.println((int) sifir); // 48
// Karakter aritmetiği
char b = (char) ('A' + 1);
System.out.println(b); // 'B'ASCII'nin sorunu açık: sadece İngilizce harfler var. Türkçe "ş", "ğ", "ü" yok. Çince, Japonca, Arapça hiç yok. 128 karakter dünya için yeterli değil.
Karmaşa Dönemi — Code Pages
ASCII'den sonra her ülke kendi "genişletilmiş" kodlamasını yaptı. ISO-8859-1 (Batı Avrupa), ISO-8859-9 (Türkçe — Latin-5), Windows-1254 (Türkçe Windows)...
Sorun şu: aynı sayı farklı kodlamalarda farklı karakterlere denk geliyordu. Bir Türk bilgisayarında doğru görünen metin, bir Japon bilgisayarında çöp karakterlere dönüyordu.
Bunu şöyle düşün: Herkes kendi telefon rehberini yapıyor. "135 numaralı kişi" birinin rehberinde Ahmet, diğerininkininde Yuki. Ortak bir rehber lazım — işte Unicode bu ortak rehber.
Unicode — Evrensel Standart
Unicode, dünyadaki tüm yazı sistemlerini kapsayan tek bir karakter seti. Her karaktere benzersiz bir numara (code point) verir.
| Gösterim | Açıklama | Örnek |
|---|---|---|
| U+0041 | Unicode code point | A |
| U+015E | Unicode code point | Ş |
| U+1F600 | Unicode code point | 😀 |
Unicode 15.0 ile birlikte 149.000'den fazla karakter tanımlı. ASCII'nin 128 karakterinden geldiğimizi düşününce devasa bir gelişme.
Temel Unicode Blokları
| Aralık | İçerik |
|---|---|
| U+0000 – U+007F | Temel Latin (ASCII ile aynı) |
| U+0080 – U+00FF | Latin-1 Eki |
| U+0100 – U+017F | Latin Genişletilmiş-A (Türkçe harfler burada) |
| U+0400 – U+04FF | Kiril (Rusça vb.) |
| U+4E00 – U+9FFF | CJK (Çince/Japonca/Korece) |
| U+1F600 – U+1F64F | Emojiler |
Türkçe'ye özel karakterler:
| Karakter | Unicode | Açıklama |
|---|---|---|
| ç | U+00E7 | Latin-1 Eki'nde |
| Ç | U+00C7 | Latin-1 Eki'nde |
| ğ | U+011F | Latin Genişletilmiş-A'da |
| Ğ | U+011E | Latin Genişletilmiş-A'da |
| ı | U+0131 | Latin Genişletilmiş-A'da |
| İ | U+0130 | Latin Genişletilmiş-A'da |
| ö | U+00F6 | Latin-1 Eki'nde |
| Ö | U+00D6 | Latin-1 Eki'nde |
| ş | U+015F | Latin Genişletilmiş-A'da |
| Ş | U+015E | Latin Genişletilmiş-A'da |
| ü | U+00FC | Latin-1 Eki'nde |
| Ü | U+00DC | Latin-1 Eki'nde |
UTF-8, UTF-16, UTF-32 — Kodlama Formatları
Unicode bir "tablo" — hangi numaranın hangi karakter olduğunu söylüyor. Ama bu numaraları bilgisayar belleğinde nasıl saklayacağız? İşte burada UTF devreye giriyor.
UTF-8 — Web'in Standardı
Değişken uzunluklu kodlama: bir karakter 1-4 byte kullanır.
| Aralık | Byte Sayısı | Örnek |
|---|---|---|
| U+0000 – U+007F | 1 byte | A, B, 0-9 |
| U+0080 – U+07FF | 2 byte | ş, ğ, ü, é |
| U+0800 – U+FFFF | 3 byte | 中, 日, ★ |
| U+10000 – U+10FFFF | 4 byte | 😀, 🎉 |
UTF-8'in avantajı: ASCII metinler UTF-8'de de aynı kalır (1 byte). Bu yüzden geriye uyumlu ve verimli.
İnternetteki sayfaların %98'i UTF-8 kullanır. Yeni bir proje başlıyorsan soru sorma, UTF-8 kullan.
UTF-8 Encoding Detayı
UTF-8 nasıl çalışır? İlk byte'ın başındaki bit'ler kaç byte olduğunu söyler:
| Byte sayısı | İlk byte başlangıcı | Kullanılabilir bit |
|---|---|---|
| 1 | 0xxxxxxx | 7 bit |
| 2 | 110xxxxx 10xxxxxx | 11 bit |
| 3 | 1110xxxx 10xxxxxx 10xxxxxx | 16 bit |
| 4 | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx | 21 bit |
// 'A' (U+0041) → 1 byte: 01000001
// 'ş' (U+015F) → 2 byte: 11000101 10011111
// '中' (U+4E2D) → 3 byte: 11100100 10111000 10101101
String a = "A";
String s = "ş";
String c = "中";
System.out.println(a.getBytes("UTF-8").length); // 1
System.out.println(s.getBytes("UTF-8").length); // 2
System.out.println(c.getBytes("UTF-8").length); // 3UTF-16 — Java'nın İç Formatı
Değişken uzunluklu: bir karakter 2 veya 4 byte kullanır.
| Aralık | Boyut | Açıklama |
|---|---|---|
| U+0000 – U+FFFF | 2 byte | Basic Multilingual Plane (BMP) |
| U+10000 – U+10FFFF | 4 byte (surrogate pair) | Emojiler, nadir karakterler |
Java `char` tipi UTF-16 kullanır — yani 2 byte, 0-65535 arası. Bu, BMP'deki tüm karakterleri kapsar ama emoji gibi karakterler için yetmez.
UTF-32
Sabit uzunluk: her karakter 4 byte. Basit ama israf. Pratikte nadiren kullanılır.
Java'da char ve Unicode
Java'nın char tipi 16 bit (2 byte) ve UTF-16 code unit tutar. Bu çoğu karakter için yeterli:
char turkce = 'ş'; // U+015F — 2 byte'a sığar
char cin = '中'; // U+4E2D — 2 byte'a sığar
char ascii = 'A'; // U+0041 — sorunsuz
System.out.println(turkce); // ş
System.out.println(cin); // 中Ama emojiler ve bazı nadir karakterler 2 byte'a sığmaz. Bunlar surrogate pair denen iki char ile temsil edilir:
String emoji = "😀";
System.out.println(emoji.length()); // 2! (iki char)
System.out.println(emoji.codePointCount(0, emoji.length())); // 1 (bir karakter)
// Surrogate pair
char high = emoji.charAt(0); // 0xD83D
char low = emoji.charAt(1); // 0xDE00
System.out.println(Integer.toHexString(high)); // d83d
System.out.println(Integer.toHexString(low)); // de00💡 Modern Java'da karakter bazlı işlem yapıyorsan
codePointCount(),codePointAt()gibi metotları kullan.charAt()velength()surrogate pair'leri yanlış sayar.
Unicode Escape Sequence'ler
Java'da Unicode karakterlerini doğrudan kaynak kodda \uXXXX formatıyla yazabilirsin:
char a = '\u0041'; // 'A'
char sHarf = '\u015F'; // 'ş'
char omega = '\u03A9'; // 'Ω'
System.out.println(a); // A
System.out.println(sHarf); // ş
System.out.println(omega); // ΩYaygın escape sequence'ler:
| Escape | Karakter | Açıklama |
|---|---|---|
\n | Yeni satır | Line feed |
\r | Satır başı | Carriage return |
\t | Tab | Yatay tab |
\\ | \ | Ters eğik çizgi |
\" | " | Çift tırnak |
\' | ' | Tek tırnak |
\uXXXX | Unicode | 4 haneli hex code point |
System.out.println("Sat\u0131r 1\nSat\u0131r 2");
// Satır 1
// Satır 2
System.out.println("Tab\tAras\u0131");
// Tab Arası
System.out.println("\"T\u0131rnak i\u00E7inde\"");
// "Tırnak içinde"⚠️ Dikkat: \u escape'leri derleyici tarafından çok erken aşamada (lexical analysis) işlenir. Bu yüzden yorum satırlarında bile sorun çıkarabilir:
// Bu bir dosya yolu: C:\users\name
// Yukarıdaki satır HATA VEREBİLİR! \u'dan sonraki "sers"
// geçerli hex değil. Doğrusu:
// Bu bir dosya yolu: C:\\users\\nameTürkçe Karakter Sorunları
Türkçe, karakter kodlama açısından özel zorluklar taşır. En büyük sorun I/İ ve ı/i dönüşümü.
Büyük/Küçük Harf Tuzağı
İngilizcede:
i→I(büyük)I→i(küçük)
Türkçede:
i→İ(büyük, noktalı)ı→I(büyük, noktasız)İ→i(küçük, noktalı)I→ı(küçük, noktasız)
String s = "milk"; // İngilizce kelime
// Varsayılan locale Türkçe ise:
System.out.println(s.toUpperCase());
// "MILK" yerine "MİLK" olabilir! (i → İ)Çözüm: Locale belirt.
import java.util.Locale;
String s = "istanbul";
// Türkçe kurallarla
String trUpper = s.toUpperCase(new Locale("tr", "TR"));
System.out.println(trUpper); // İSTANBUL (doğru!)
// İngilizce kurallarla
String enUpper = s.toUpperCase(Locale.ENGLISH);
System.out.println(enUpper); // ISTANBUL (I noktasız)// Programlama amaçlı karşılaştırmalarda Locale.ROOT kullan
String protocol = "HTTP";
boolean esit = protocol.toLowerCase(Locale.ROOT).equals("http");
System.out.println(esit); // true — her locale'de doğru çalışır💡 Altın kural: Kullanıcıya gösterilecek metinlerde Türkçe locale kullan. Programatik karşılaştırmalarda (protokol adı, dosya uzantısı vb.)
Locale.ROOTveyaLocale.ENGLISHkullan.
Dosya Kodlama Sorunları
Java kaynak dosyaları modern IDE'lerde genellikle UTF-8 olarak kaydedilir. Ama bazen eski dosyalar farklı kodlamada olabilir:
// Dosya okurken kodlama belirt
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.charset.StandardCharsets;
// UTF-8 ile oku
String icerik = Files.readString(
Path.of("dosya.txt"),
StandardCharsets.UTF_8
);
// ISO-8859-9 (Türkçe Latin-5) ile oku
byte[] bytes = Files.readAllBytes(Path.of("eski_dosya.txt"));
String turkce = new String(bytes, "ISO-8859-9");Veritabanı ve Türkçe
Veritabanında Türkçe karakter sorunu yaşamamak için:
Veritabanı/tablo charset'ini
utf8mb4yap (MySQL)Connection string'de
characterEncoding=UTF-8belirtCollation olarak
utf8mb4_turkish_cikullan (Türkçe sıralama)
Karakter Sınıflandırma
Java'nın Character sınıfı Unicode'a uygun karakter sınıflandırma metotları sunar:
System.out.println(Character.isLetter('A')); // true
System.out.println(Character.isLetter('5')); // false
System.out.println(Character.isDigit('5')); // true
System.out.println(Character.isLetterOrDigit('A')); // true
System.out.println(Character.isWhitespace(' ')); // true
System.out.println(Character.isUpperCase('A')); // true
System.out.println(Character.isLowerCase('ş')); // true// Türkçe karakterler de doğru tanınır
System.out.println(Character.isLetter('ğ')); // true
System.out.println(Character.isLetter('İ')); // true
System.out.println(Character.toUpperCase('ş')); // 'Ş'Java'nın String Encoding Tarihi
Java'nın char tipinin neden 2 byte olduğunu anlamak için tarihsel bağlam önemli:
1991: Java tasarlanırken Unicode 1.0 henüz 65.536 karakterle sınırlıydı
Java tasarımcıları: "16 bit tüm karakterleri kapsar" dediler →
char= 2 byte1996: Unicode 2.0 ile supplementary characters eklendi (65.536'yı aştı)
Artık bazı karakterler 2
char'a sığmıyor → surrogate pair zorunluluğu
Bu geriye uyumluluk sorunu Java'da hâlâ yaşıyor. String.length() karakter sayısı değil, char (UTF-16 code unit) sayısını verir. Bu yüzden emoji içeren string'lerde codePointCount() kullanmalısın.
// "A" = 1 char, 1 code point
// "😀" = 2 char, 1 code point
// "🇹🇷" = 4 char, 2 code point (iki regional indicator)
String test = "A😀🇹🇷";
System.out.println("length(): " + test.length()); // 7
System.out.println("codePointCount: " +
test.codePointCount(0, test.length())); // 4Pratik Örnek: Türkçe Karakter Dönüştürücü
public class TurkceNormalizer {
public static String turkceKaldir(String girdi) {
if (girdi == null) return null;
return girdi
.replace('ç', 'c').replace('Ç', 'C')
.replace('ğ', 'g').replace('Ğ', 'G')
.replace('ı', 'i').replace('I', 'I')
.replace('İ', 'I').replace('i', 'i')
.replace('ö', 'o').replace('Ö', 'O')
.replace('ş', 's').replace('Ş', 'S')
.replace('ü', 'u').replace('Ü', 'U');
}
public static void main(String[] args) {
String sehir = "İstanbul Güneşli";
String ascii = turkceKaldir(sehir);
System.out.println(ascii); // "Istanbul Gunesli"
}
}Bu tür dönüşümler URL slug oluşturma, dosya adı temizleme, arama normalizasyonu gibi işlerde sık kullanılır.
String ve Byte Dönüşümleri
import java.nio.charset.StandardCharsets;
String metin = "Merhaba Dünya";
// String → byte dizisi
byte[] utf8Bytes = metin.getBytes(StandardCharsets.UTF_8);
byte[] asciiBytes = metin.getBytes(StandardCharsets.US_ASCII);
System.out.println("UTF-8 byte sayısı: " + utf8Bytes.length); // 15 (ü 2 byte)
System.out.println("ASCII byte sayısı: " + asciiBytes.length); // 13 (ü kaybolur)
// byte dizisi → String
String geriDonen = new String(utf8Bytes, StandardCharsets.UTF_8);
System.out.println(geriDonen); // "Merhaba Dünya"⚠️ Dikkat: Bir String'i yanlış kodlamayla byte'a çevirip geri dönüştürürsen veri kaybı olur. Her zaman aynı kodlamayı kullan.
Emoji ve Supplementary Characters
Unicode'un ilk 65.536 karakteri (BMP) char'a sığar. Ama emojiler ve bazı eski yazı sistemleri "supplementary" alanda — bunlar char'a sığmaz.
String bayrak = "🇹🇷"; // Türk bayrağı emojisi
System.out.println(bayrak.length()); // 4 (iki surrogate pair)
System.out.println(bayrak.codePointCount(0, bayrak.length())); // 2 (iki code point)
// Code point ile doğru iterasyon
String metin = "Merhaba 😀";
metin.codePoints().forEach(cp -> {
System.out.println(Character.toString(cp) + " → U+" +
Integer.toHexString(cp).toUpperCase());
});
// M → U+4D
// e → U+65
// r → U+72
// h → U+68
// a → U+61
// b → U+62
// a → U+61
// → U+20
// 😀 → U+1F600Encoding Dönüşüm Tablosu
Farklı kodlamalar aynı byte'ları farklı yorumlar. İşte somut bir örnek:
import java.nio.charset.Charset;
public class EncodingFarki {
public static void main(String[] args) throws Exception {
String metin = "Şeker";
// Farklı kodlamalarla byte'a çevir
byte[] utf8 = metin.getBytes("UTF-8");
byte[] latin5 = metin.getBytes("ISO-8859-9");
byte[] latin1 = metin.getBytes("ISO-8859-1");
System.out.println("UTF-8 byte sayısı: " + utf8.length); // 6
System.out.println("Latin-5 byte sayısı: " + latin5.length); // 5
System.out.println("Latin-1 byte sayısı: " + latin1.length); // 5
// Yanlış kodlamayla geri çevirince bozulur
String bozuk = new String(utf8, "ISO-8859-9");
System.out.println("Bozuk: " + bozuk); // Ş yerine garip karakterler
String dogru = new String(utf8, "UTF-8");
System.out.println("Doğru: " + dogru); // Şeker
}
}Bu "mojibake" denen bozuk karakter sorunu, web geliştirmede çok sık karşılaşılan bir problem. HTML'de <meta charset="UTF-8">, HTTP'de Content-Type: text/html; charset=UTF-8 header'ı bu yüzden kritik.
Java'da Charset Kullanımı
Java'nın Charset sınıfı desteklenen kodlamaları yönetir:
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
// Standart charset'ler (her JVM'de garanti)
Charset utf8 = StandardCharsets.UTF_8;
Charset ascii = StandardCharsets.US_ASCII;
Charset utf16 = StandardCharsets.UTF_16;
Charset iso8859 = StandardCharsets.ISO_8859_1;
// Varsayılan charset (platform bağımlı!)
Charset varsayilan = Charset.defaultCharset();
System.out.println("Varsayılan: " + varsayilan); // Genellikle UTF-8
// Kullanılabilir tüm charset'ler
System.out.println("Toplam charset: " + Charset.availableCharsets().size());💡 Java 18+ ile varsayılan charset UTF-8 olarak sabitlendi. Eski Java sürümlerinde platform bağımlıydı (Windows'ta genellikle Windows-1252). Bu yüzden her zaman açıkça belirt.
Regex ve Unicode
Java regex'te Unicode karakter sınıfları kullanabilirsin:
String metin = "Ahmet 42 yaşında, İstanbul'da yaşıyor.";
// \p{L} — herhangi bir Unicode harf
String sadecHarfler = metin.replaceAll("[^\\p{L}\\s]", "");
System.out.println(sadecHarfler); // "Ahmet yaşında İstanbulda yaşıyor"
// Türkçe karakterleri bul
boolean turkceVar = metin.matches(".*[çÇğĞıİöÖşŞüÜ].*");
System.out.println("Türkçe karakter var: " + turkceVar); // truePratik Örnek: Güvenli String Karşılaştırma
import java.text.Collator;
import java.util.Arrays;
import java.util.Locale;
public class TurkceSiralama {
public static void main(String[] args) {
String[] sehirler = {"Çanakkale", "Ankara", "İstanbul",
"Bursa", "Şanlıurfa", "Diyarbakır", "Üsküdar"};
// Yanlış sıralama — Unicode code point'e göre
String[] yanlis = sehirler.clone();
Arrays.sort(yanlis);
System.out.println("Yanlış: " + Arrays.toString(yanlis));
// Ç, İ, Ş, Ü sona gider — yanlış!
// Doğru sıralama — Türkçe Collator ile
Collator trCollator = Collator.getInstance(new Locale("tr", "TR"));
String[] dogru = sehirler.clone();
Arrays.sort(dogru, trCollator);
System.out.println("Doğru: " + Arrays.toString(dogru));
// Ankara, Bursa, Çanakkale, Diyarbakır, İstanbul, Şanlıurfa, Üsküdar
}
}Türkçe sıralama önemli bir konu. Normal String.compareTo() Unicode code point'e göre sıralar. Bu, Ç'yi C'den sonra, İ'yi I'dan sonra koymaz — tamamen farklı bir yere koyar. Collator kullanarak dil kurallarına uygun sıralama yapabilirsin.
BOM (Byte Order Mark) Sorunu
UTF-8 dosyaların başında bazen görünmez bir BOM karakteri (U+FEFF) olur. Windows Notepad bunu ekler. Bu, dosya okurken sorun çıkarabilir:
import java.nio.file.Files;
import java.nio.file.Path;
public class BomTemizle {
public static String bomTemizle(String metin) {
if (metin != null && metin.length() > 0 && metin.charAt(0) == '\uFEFF') {
return metin.substring(1);
}
return metin;
}
public static void main(String[] args) throws Exception {
String icerik = Files.readString(Path.of("dosya.txt"));
icerik = bomTemizle(icerik);
// Artık BOM olmadan işlem yapabilirsin
if (icerik.startsWith("<?xml")) {
System.out.println("XML dosyası");
}
}
}Sık Yapılan Hatalar
1. Kodlama belirtmemek:
// Kötü — platform bağımlı
byte[] bytes = str.getBytes();
// İyi — kodlama açık
byte[] bytes = str.getBytes(StandardCharsets.UTF_8);2. Türkçe büyük/küçük harf dönüşümünde locale unutmak:
// Riskli
"istanbul".toUpperCase();
// Güvenli
"istanbul".toUpperCase(new Locale("tr", "TR"));3. char ile emoji tutmaya çalışmak:
// Hata! Emoji char'a sığmaz
// char emoji = '😀'; // Derleme hatası
String emoji = "😀"; // Doğru — String kullan4. String length() ile karakter sayısı sanmak:
String s = "Merhaba 😀";
System.out.println(s.length()); // 10 (char sayısı, karakter değil!)
System.out.println(s.codePointCount(0, s.length())); // 9 (gerçek karakter)5. Farklı kodlamalarla okuma/yazma:
// Dosyayı UTF-8 ile yaz
Files.writeString(Path.of("test.txt"), "Türkçe", StandardCharsets.UTF_8);
// Ama Latin-5 ile okursan bozulur!
byte[] bytes = Files.readAllBytes(Path.of("test.txt"));
String bozuk = new String(bytes, Charset.forName("ISO-8859-9")); // Bozuk!Özet
ASCII 128 karakter tanımlar (sadece İngilizce), Unicode 149.000+ karakter kapsar (tüm diller)
UTF-8 web standardı (değişken 1-4 byte), UTF-16 Java'nın iç formatı (2 veya 4 byte)
Java'da
char2 byte'tır ve BMP karakterlerini tutar; emojiler için surrogate pair kullanılırTürkçe I/İ ve ı/i dönüşümü özel —
toUpperCase()/toLowerCase()çağrırken Locale belirtDosya ve ağ işlemlerinde kodlamayı her zaman açıkça belirt (
StandardCharsets.UTF_8)Modern Java'da karakter bazlı doğru işlem için
codePointCount()vecodePoints()kullan
AI Asistan
Sorularını yanıtlamaya hazır