Golang Derinlemesine String, Rune ve Unicode

Ömer Burak Demirpolat
3 min readDec 1, 2021

--

Selam, bu yazının içeriği Go programlama dilinde String, Rune veri tipleri ve Unicode dönüşümleri üzerine olacak. Yazının devamında “Ş” karakterinin ekranımızda yazdırılana kadar nerelerden geçtiğini, nasıl dönüştürüldüğünü anlatmaya çalışacağım.

Go programlama dilinde String read-only byte dizisi olarak tanımlanır. Peki ne demek bu read-only byte dizisi?

Dizi içerisinden bir index okunabilir fakat değiştirilemez. Go kaynaklarında da String veri tipi immutable olarak geçmektedir. Basit bir örnek ile inceleyelim.

name := "burak"
fmt.Println(name[0]) // 98

Yukarıdaki kod çalıştırıldığında sorunsuz şekilde derlendi ve çalıştı. name adlı değişkenin 0. index’inin neden “98” döndürdüğüne dair bilgiyi yazının ilerleyen kısımlarında paylaşacağım.

Şimdi ise name adlı değişkenin 0. index’ini set etmeyi deneyelim.

name := "burak"
name[0] = 'a'

Yukarıdaki kodu derlemeye çalıştığımda şöyle bir hata alacağım.

cannot assign to name[0] (strings are immutable)

Peki neden? Bunun sebebi string içerisindeki karakterlerin 1 byte’dan fazla yer kaplayabilir olması. Burada konumuz rune veri tipine geliyor.

Rune aslında bir uint32'dir. Bu veri tipinin değeri taşıdığı karakterin Unicode tablosundaki numarasını temsil eder.

Go’da String’in byte dizisi olduğunu söylemiştik, sonuçta byte dizisi her bir index’inde 1 byte boyutunda veri taşıyabiliyor.

ASCII karakterlerinin 1 byte olduğunu biliyoruz yani her ASCII karakteri örneğin “a” harfi String içerisinde 1 adet index’e yerleşebilir. Bunda bir sorun yok.

Peki “Ş” karakteri 2 byte’lık bir karakter. Go bunu nasıl yapıyor, benim kafam karıştı açıkcası. Bir örnek ile ilerleyelim.

c := "Ş"
fmt.Println(c[0]) // 197

Yukarıdaki kod “197” olarak çıktı verdi. Şimdi 197'nin Unicode tablosundaki karşılığı ise “Å” karakteri.

Peki neden “Ş” karakteri yerine bize “Å” karakteri geldi. Bunu Go kodu ile deneyip görelim.

c := "Ş"
fmt.Println(string(c[0])) // Å

Daha önce de bahsettiğim gibi “Ş” karakteri 2 byte boyutunda. Yine Go kodu ile bir göz atalım.

c := "Ş"
fmt.Println(len(c)) // 2

c = "a"
fmt.Println(len(c)) // 1

Go bunu nasıl yönetiyor, anlamaya çalışalım.

c := "Ş"
fmt.Println([]byte(c)) // [197 158]

Ş” karakterinin byte dizisi temsili yukarıdaki gibi 2 byte’dan oluşuyor, 197 ve 158. Peki Go bunu ekrana yazdırırken nasıl yapıyor, 2 byte’ı birleştiriyor, bazı işlemler yapıyor aslında bu 2 byte’ın gerçek temsilinin “Ş” karakteri olduğunu anlıyor ve ekrana “Ş” yazıyor.

Peki şimdi şunu inceleyelim.

c := "AŞI"
fmt.Println([]byte(c)) // [65 197 158 73]

AŞI” kelimesi [65 197 158 73] byte dizisi olarak çıktı verdi. Her bir karakterin byte karşılığı da aşağıdaki gibi göstermek istedim.

Peki Go ekrana “AŞI” yazdırırken ilk byte’ı okudu, 65'in unicode tablosundaki karşılığı olan “A” karakterini buldu ve yazdırdı, sonrasında ikinci byte’ı okudu 197'nin unicode tablosundaki karşılığı olan “Å” karakterini buldu ama bunu yazdırmadı, 3. byte ile birleştirip, bazı işlemler yazıp sonuç olarak ekrana “Ş” yazdı.

Bu yapılan işlemleri utf8 adlı package içerisinden inceleyebilirsiniz.

https://cs.opensource.google/go/go/+/refs/tags/go1.17.3:src/unicode/utf8/utf8.go;l=151

Aslında bu konunun devamını anlamak için UTF-8 encoding işleminin nasıl yapıldığını anlamak gerekecek.

Şimdi “Ş” karakterine tekrar dönelim.

a := "Ş"
fmt.Println(strconv.FormatInt(int64(a[0]), 2), strconv.FormatInt(int64(a[1]), 2)) // 11000101 10011110

Ş” karakterinin içerdiği 197 ve 158'in binary temsilleri “11000101ve10011110şeklinde.

UTF-8 variable width encoding’de şöyle bir durum var.

İlk bit 1 olmalı, bu 1 olan bitin ardından gelen ilk 0'a kadar arada kalan kaç tane 1 bit var ise, bu sayı kadar byte takip edilecek anlamına geliyor. Karmaşık olduğunun farkındayım şöyle bir örnek ile özetleyelim.

110x xxxx    1 byte takip edilir
1110 xxxx 2 byte takip edilir
1111 0xxx 3 byte takip edilir

Sanırım örnek daha açıklayıcı oldu.

Takip eden byte’lar ile alakalı da ufak bir detay var. Başlangıç byte’ı ile takip eden byte’lar arasında ufak bir fark mevcut.

Takip eden byte’lar daima “10xxxxxx” şeklinde olmalı. Yani “10” ile başlamalı.

Kısacası ilk byte üzerinden kaç adet byte’ı takip edileceği bulunuyor ve byte dizisi bu şekilde oluşuyor.

Örneğimizden gidecek olursak “Ş” karakterinin ilk byte’ı 11000101 bu byte bize 1 adet byte takip edileceğini söylüyor, devamında ise “Ş” karakterinin ikinci byte’ı 10011110 şeklindeydi, bu byte’da takip eden byte kuralına uyuyor yani “10” ile başlıyor. Bu durumu Go’nun kaynak kodunda utf8 adlı package içerisinde bulunan RuneStart adlı function ile de doğrulayabiliriz.

Ekranımıza “Ş” karakterinin yazdırılma hikayesi bu şekilde diyebilirim.

Kaynaklar

Dilim döndüğünce anlatmaya çalıştım, umarım olmuştur.

Teşekkürler.

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

--

--

No responses yet

Write a response