İçeriğe geç
academia.sh

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.alloc sıfırlanmış bellek verir; sıfırlamayı atlayan ayırma, tamamen doldurulmadığında eski bellek içeriğini sızdırır.
  • subarray kopya değil görünüm döndürür; bağımsız kopya Buffer.from ile 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_decoder yarı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.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat