Ders 08 / 20
Arabellekler
Ham bayt dizisinin dizgiden farkı, sıfırlanmış ve sıfırlanmamış ayırma, görünüm ile kopya ayrımı, karakter sınırında bölünme sorunu ve ikili kayıt kodlama.
İçindekiler
Önceki derste akıştan gelen parçaların türü Buffer göründü ve metne çevrilirken
kodlama adı elle verildi. Bir parçanın çok baytlı bir karakterin ortasından bölünmesi
durumunda ne olacağı ise sorulmadan bırakıldı.
Bu ders o soruyu yanıtlar. Konu, Bilgisayarlar Nasıl Çalışır kursundaki karakter kodlamaları ve bayt sırası derslerinin doğrudan sürdürülmesidir: orada kurulan kavramlar burada çalışan koda dönüşür.
Bayt Dizisi Olarak Arabellek
Arabellek (buffer), sabit uzunlukta ham bayt dizisini temsil eden nesnedir.
Dilin Uint8Array tipinden türer; dolayısıyla dizin erişimi, uzunluk ve yineleme
davranışı tipli dizilerle aynıdır. Üzerine eklenen şey, kodlama dönüşümleri ve
sayısal alan okuma-yazma yöntemleridir.
Dizgi ile arabellek arasındaki fark, ölçü biriminde görülür:
// arabellek.mjs const metin = 'sıcaklık'; const ab = Buffer.from(metin, 'utf8'); console.log('karakter sayisi :', metin.length); console.log('bayt sayisi :', ab.length, '=', Buffer.byteLength(metin, 'utf8')); console.log('onaltilik :', ab.toString('hex')); console.log('Uint8Array mi :', ab instanceof Uint8Array); console.log('ilk uc bayt :', [...ab.subarray(0, 3)]);
node arabellek.mjs
karakter sayisi : 8 bayt sayisi : 10 = 10 onaltilik : 73c4b163616b6cc4b16b Uint8Array mi : true ilk uc bayt : [ 115, 196, 177 ]
Sekiz karakterlik dizgi on bayt tuttu; ı harfi UTF-8’de iki bayt ile
(c4 b1) kodlanır ve sözcükte iki kez geçer. Bu ayrım, bir HTTP yanıtına
Content-Length başlığı yazarken önemlidir: başlığa yazılması gereken sayı bayt
sayısıdır, karakter sayısı değil. Buffer.byteLength, arabellek üretmeden bu sayıyı
verir.
Ayırma, Görünüm ve Kopya
Arabellek üretmenin üç yolu vardır ve aralarındaki fark bellek davranışındadır.
// arabellek-ayirma.mjs const sifirli = Buffer.alloc(8); console.log('alloc(8) :', sifirli.toString('hex')); console.log('allocUnsafe(8) boy:', Buffer.allocUnsafe(8).length); // subarray bir kopya degil, ayni bellegin uzerine acilmis bir gorunumdur const tam = Buffer.from('kenar-01', 'utf8'); const gorunum = tam.subarray(0, 5); gorunum[0] = 'K'.charCodeAt(0); console.log('gorunum degistirildi ->', tam.toString('utf8')); // Kopya gerektiginde acikca istenir const kopya = Buffer.from(tam.subarray(0, 5)); kopya[0] = 'x'.charCodeAt(0); console.log('kopya degistirildi ->', tam.toString('utf8'), '|', kopya.toString('utf8')); // Parcalari tek arabellekte birlestirmek const birlesik = Buffer.concat([Buffer.from('kenar'), Buffer.from('-'), Buffer.from('01')]); console.log('concat ->', birlesik.toString('utf8'), `(${birlesik.length} bayt)`);
node arabellek-ayirma.mjs
alloc(8) : 0000000000000000 allocUnsafe(8) boy: 8 gorunum degistirildi -> Kenar-01 kopya degistirildi -> Kenar-01 | xenar concat -> kenar-01 (8 bayt)
Buffer.alloc, istenen boyutta sıfırlanmış bellek verir. Buffer.allocUnsafe
sıfırlama adımını atlar ve daha hızlıdır; karşılığında arabellek, o bellekte daha
önce ne varsa onu içerir. İçeriği baştan sona yazmadan bu arabelleği kullanmak,
sürecin başka bir yerinde bulunan veriyi sızdırabilir. Kural: arabelleği hemen ve
tamamen doldurmuyorsanız sıfırlanmış ayırma kullanın.
Çıktının üçüncü satırı görünüm davranışını gösteriyor: subarray yeni bellek
ayırmaz, aynı belleğe açılmış bir pencere döndürür. Pencereye yazmak asıl arabelleği
değiştirir. Bu, kopyalamadan dilimleme olanağı verdiği için akış kodunda değerlidir;
farkında olunmadığında ise açıklaması zor hatalar üretir. Bağımsız bir kopya
gerektiğinde Buffer.from ile açıkça istenir.
Buffer.concat, parça listesini tek bir arabellekte birleştirir. Akıştan gelen
parçaları toplayıp sonunda tek metne çevirmenin standart yolu budur; parçaları
dizgiye çevirip birleştirmek, aşağıdaki soruna yol açar.
Karakter Sınırında Bölünme
Bir parça, çok baytlı bir karakterin ortasında bitebilir. O parça tek başına metne çevrilirse, yarım kalan karakter yerine değiştirme karakteri konur ve bilgi geri dönüşsüz kaybolur.
// arabellek-sinir.mjs import { StringDecoder } from 'node:string_decoder'; const ab = Buffer.from('sıcaklık', 'utf8'); // 10 bayt const parcaA = ab.subarray(0, 2); // 'ı' harfinin ortasindan keser const parcaB = ab.subarray(2); console.log('dogrudan cozme :', parcaA.toString('utf8') + parcaB.toString('utf8')); const cozucu = new StringDecoder('utf8'); console.log('cozucu ile :', cozucu.write(parcaA) + cozucu.write(parcaB) + cozucu.end());
node arabellek-sinir.mjs
dogrudan cozme : s��caklık cozucu ile : sıcaklık
İlk satırdaki iki değiştirme karakteri, yarım kalan ı harfinin iki baytının ayrı
ayrı çözülmesinden doğdu. node:string_decoder modülündeki çözücü, tamamlanmamış
bayt dizisini kendinde tutar ve sonraki yazımla birleştirir; end çağrısı, elde
kalan eksik dizi varsa onu bildirir.
Bu, önceki dersteki SatirBolucu sınıfının gizli varsayımını açığa çıkarır. Orada
her parça doğrudan toString('utf8') ile çevrildi; ölçüm dosyası yalnızca ASCII
karakterler içerdiği için sorun görünmedi. Düğüm adlarında Türkçe karakter bulunan
bir veri kümesinde aynı kod bozulur. Doğru düzeltme, dönüştürücüde bir çözücü
tutmak ve parca.toString('utf8') yerine cozucu.write(parca) yazmaktır.
İkili Kayıt Biçimi
Arabellek, sayısal alanları belirli genişlik ve bayt sırasıyla okuyup yazan yöntemler sunar. Bu, ağ üzerinden gelen ikili bir protokolü ayrıştırmanın ve yer kazanmak için kendi biçiminizi tanımlamanın yoludur.
Ölçüm kaydını altı bayta sığdıralım: bir bayt düğüm numarası, bir bayt metrik numarası, dört bayt tek duyarlıklı kayan noktalı değer.
// arabellek-ikili.mjs // Bir olcumu 6 bayta sigdiran ikili bicim: // [0] dugum no (uint8) [1] metrik no (uint8) [2..5] deger (float32, buyuk uclu) function kodla(dugumNo, metrikNo, deger) { const ab = Buffer.alloc(6); // sifirlanmis bellek ab.writeUInt8(dugumNo, 0); ab.writeUInt8(metrikNo, 1); ab.writeFloatBE(deger, 2); return ab; } function coz(ab) { return { dugumNo: ab.readUInt8(0), metrikNo: ab.readUInt8(1), deger: Number(ab.readFloatBE(2).toFixed(2)), }; } const kayit = kodla(1, 0, 21.4); console.log('ikili gosterim :', kayit.toString('hex')); console.log('cozulmus kayit :', coz(kayit)); const kucukUclu = Buffer.alloc(4); kucukUclu.writeFloatLE(21.4, 0); console.log('kucuk uclu :', kucukUclu.toString('hex')); console.log('buyuk uclu :', kayit.subarray(2).toString('hex')); const metinBoyu = Buffer.byteLength(JSON.stringify({ dugum: 'kenar-01', metrik: 'sicaklik', deger: 21.4 })); console.log(`metin gosterimi ${metinBoyu} bayt, ikili gosterim ${kayit.length} bayt`);
node arabellek-ikili.mjs
ikili gosterim : 010041ab3333
cozulmus kayit : { dugumNo: 1, metrikNo: 0, deger: 21.4 }
kucuk uclu : 3333ab41
buyuk uclu : 41ab3333
metin gosterimi 53 bayt, ikili gosterim 6 bayt
Yöntem adlarındaki BE ve LE sonekleri, Bilgisayarlar Nasıl Çalışır kursunda
tanıtılan büyük uçlu ve küçük uçlu yerleşimi seçer. Çıktının üçüncü ve dördüncü
satırları aynı sayının iki yerleşimini yan yana gösteriyor: bayt dizisi ters. Ağ
protokollerinde geleneksel seçim büyük uçlu yerleşimdir; bir biçim tanımlarken hangi
uçluluğun kullanıldığı yazılmalıdır, yoksa iki uç birbirinin verisini yanlış okur.
Son satır, ikili biçimin neden var olduğunu gösterir: aynı ölçüm metin olarak 53, ikili olarak 6 bayt tutuyor. Karşılığında ikili biçim insan tarafından okunamaz, kendini tanımlamaz ve alan eklendiğinde eski çözücüleri kırar. Bu ödünleşim İnternet Nasıl Çalışır kursunda HTTP’nin metin tabanlı olması gerekçelendirilirken tartışılmıştı; seçim, hacim ile incelenebilirlik arasındadır.
Değeri dört bayta sıkıştırmanın bir bedeli daha vardır. Tek duyarlıklı kayan noktalı
gösterim, yaklaşık yedi anlamlı ondalık basamak taşır; 21.4 değeri bu gösterimde
tam olarak temsil edilemez. Örnekte toFixed(2) ile yuvarlanmasının nedeni budur.
Ölçüm duyarlığı bu sınırın üstündeyse çift duyarlıklı yazma yöntemleri kullanılır.
Arabellek yöntemleri sınır denetimi yapar: dizinin dışına yazmaya çalışan bir çağrı
RangeError fırlatır. Bu, Bilgisayarlar Nasıl Çalışır kursunda anlatılan sınırsız
bellek erişimi sınıfındaki hataları çalışma zamanı düzeyinde engeller.
Özet
- Arabellek,
Uint8Arrayüzerine kurulmuş ham bayt dizisidir; karakter sayısı ile bayt sayısı çok baytlı kodlamalarda ayrışır ve protokol başlıklarına bayt sayısı yazılır. Buffer.allocsıfırlanmış bellek verir; sıfırlamayı atlayan ayırma, tamamen doldurulmadığında eski bellek içeriğini sızdırır.subarraykopya değil görünüm döndürür; bağımsız kopyaBuffer.fromile açıkça istenir.- Çok baytlı bir karakter parça sınırında bölündüğünde doğrudan çözme veriyi bozar;
node:string_decoderyarım baytları saklayarak bunu önler. - Sayısal alanlar genişlik ve uçluluk seçilerek okunup yazılır; ikili biçim yer kazandırır, incelenebilirlik ve genişletilebilirlikten ödün verir.
Sonraki Adım
Buraya kadar akışların olay yaydığından söz edildi ama olay düzeneğinin kendisi açılmadı. Ölçüm toplayıcının bir sonraki adımı, veri geldiğinde ilgilenen tarafları haberdar eden bir yapı gerektiriyor: eşik aşıldığında uyarı üreten, bozuk satırı bildiren, kaynağın kapandığını duyuran bir düzenek. Sonraki ders bu düzeneğin temel sınıfını — olay yayıcıyı — ele alır.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.