Chapter sebelumnya memperlihatkan Buffer sebagai array byte, tetapi kita masih memperlakukan konversi dari string seperti kotak hitam. Padahal setiap konversi mengikuti aturan encoding: sekumpulan kaidah yang menentukan karakter mana menjadi byte mana, dan sebaliknya. Salah memilih encoding membuat teks rusak diam-diam — data terbaca, tetapi isinya keliru. Chapter ini membahas encoding yang didukung Node.js, cara menghitung ukuran byte dengan tepat, serta cara menulis data ke posisi tertentu di dalam Buffer.

Encoding Menentukan Bentuk Byte #

Encoding adalah kesepakatan dua arah: Buffer.from() memakainya saat mengubah string menjadi byte, dan buf.toString() memakainya saat mengubah byte kembali menjadi string. Daftar lengkap encoding yang didukung Node.js tercantum pada Buffers and character encodings . Contoh berikut melakukan perjalanan bolak-balik dari string ke byte lalu pulang lagi:

js
import { Buffer } from "node:buffer";

const teks = "Halo, Dunia!";
const byte = Buffer.from(teks, "utf8");

console.log("string  :", teks);
console.log("hex     :", byte.toString("hex"));
console.log("base64  :", byte.toString("base64"));

const dikodekan = byte.toString("base64");
const kembali = Buffer.from(dikodekan, "base64");

console.log("decode  :", kembali.toString("utf8"));
console.log("sama?   :", kembali.equals(byte));

Output yang Dihasilkan:

text
string  : Halo, Dunia!
hex     : 48616c6f2c2044756e696121
base64  : SGFsbywgRHVuaWEh
decode  : Halo, Dunia!
sama?   : true

Mari kita telaah baris-baris kode di atas:

  1. Baris 4 (Buffer.from(teks, "utf8")): argumen kedua menentukan encoding konversi. Tanpa argumen tersebut, "utf8" tetap menjadi nilai bawaan.
  2. Baris 7-8 (byte.toString("hex") dan toString("base64")): byte yang sama ditampilkan dalam dua bentuk berbeda. Encoding hex memakai dua karakter per byte sehingga mudah dibaca manusia, sedangkan base64 memadatkan 3 byte menjadi 4 karakter ASCII.
  3. Baris 10-11: string base64 dikonversi kembali menjadi Buffer dengan Buffer.from(dikodekan, "base64") — encoding yang sama harus dipakai pada kedua arah agar data utuh.
  4. Baris 14 (kembali.equals(byte)): method equals() membandingkan isi dua Buffer byte demi byte. Hasil true membuktikan proses pulang-pergi tidak mengubah data. Perilaku toString() dengan seluruh opsinya dirinci pada dokumentasi buf.toString() .

Node.js mendukung sejumlah encoding resmi. Tabel berikut merangkum yang paling sering kita jumpai:

EncodingUkuran per karakterKegunaan umum
utf81-4 byteStandar web dan nilai bawaan Node.js untuk teks
utf16le2-4 byteFormat internal string JavaScript dan API Windows
latin1Selalu 1 byteSistem barat lawas; alias binary
ascii1 byte (7 bit)Teks bahasa Inggris murni; byte di atas 127 dihilangkan
base644 karakter per 3 byteMembawa data biner di media teks seperti email dan data URI
base64urlSama dengan base64Varian URL-safe tanpa karakter + dan /
hex2 karakter per byteMenampilkan hash, debug data biner

Ukuran Karakter Bukan Ukuran Byte #

Properti length pada string menghitung unit kode UTF-16, sedangkan length pada Buffer menghitung byte. Keduanya hanya sama untuk teks ASCII murni. Untuk teks lain, gunakan Buffer.byteLength() guna mengetahui kebutuhan byte yang sebenarnya:

js
import { Buffer } from "node:buffer";

const kata = "café";
const aksara = "中";

console.log("'café' : karakter =", kata.length, "| byte =", Buffer.byteLength(kata));
console.log("'中'   : karakter =", aksara.length, "| byte =", Buffer.byteLength(aksara));
console.log("hex '中'          :", Buffer.from(aksara).toString("hex"));
console.log("hex 'café'        :", Buffer.from(kata).toString("hex"));

Output yang Dihasilkan:

text
'café' : karakter = 4 | byte = 5
'中'   : karakter = 1 | byte = 3
hex '中'          : e4b8ad
hex 'café'        : 636166c3a9

Kata "café" terdiri atas 4 karakter tetapi memakan 5 byte, karena huruf é dikodekan UTF-8 sebagai dua byte c3 a9. Aksara "中" lebih ekstrem lagi: 1 karakter menjadi 3 byte e4 b8 ad. Perbedaan ini bukan sekadar trivia — saat mengalokasikan Buffer atau memotong data biner, yang kita hitung adalah byte, bukan karakter.

Method buf.write(string, offset, length) menyalin byte dari string ke dalam Buffer mulai posisi offset, sebanyak maksimum length byte, lalu mengembalikan jumlah byte yang benar-benar tersalin:

js
import { Buffer } from "node:buffer";

const buf = Buffer.alloc(6);

buf.write("ab", 0);
buf.write("cd", 2);
buf.write("ef", 4);
console.log("isi awal :", buf.toString());

const byteDitulis = buf.write("XYZ", 1, 3);
console.log("byte yang ditulis:", byteDitulis);
console.log("isi akhir:", buf.toString());

Output yang Dihasilkan:

text
isi awal : abcdef
byte yang ditulis: 3
isi akhir: aXYZef

Tiga panggilan write() pertama mengisi byte 0-1, 2-3, dan 4-5 sehingga Buffer berisi abcdef. Panggilan keempat menulis mulai offset 1 dengan batas 3 byte: hanya "XYZ" yang muat, hasilnya aXYZef, dan nilai baliknya 3. Nilai balik ini penting ketika ruang Buffer lebih sempit daripada string — sisa string yang tidak tertampung harus ditangani, misalnya ditulis pada iterasi berikutnya.

Ketika Encoding Tidak Cocok #

Data tidak selalu rusak secara kasat mata. Ketika byte UTF-8 dibaca dengan encoding lain, kita tetap mendapatkan string — hanya saja isinya keliru:

js
import { Buffer } from "node:buffer";

const asli = "héllo";
const byteUtf8 = Buffer.from(asli, "utf8");

console.log("asli            :", asli);
console.log("hex (utf8)      :", byteUtf8.toString("hex"));
console.log("dibaca latin1   :", byteUtf8.toString("latin1"));
console.log("dibaca utf8     :", byteUtf8.toString("utf8"));

Output yang Dihasilkan:

text
asli            : héllo
hex (utf8)      : 68c3a96c6c6f
dibaca latin1   : héllo
dibaca utf8     : héllo

Dua byte c3 a9 untuk huruf é dibaca latin1 sebagai dua karakter terpisah à dan ©, menghasilkan héllo. Jenis kerusakan ini sering muncul ketika membaca berkas lama, data dari sistem lain, atau respons jaringan tanpa encoding yang jelas. Pembacaan dengan encoding yang benar mengembalikan teks asli tanpa masalah.

Dengan Buffer dan encoding sebagai fondasi, kita kini bisa menyimpan dan mengubah data biner secara presisi. Pertanyaan berikutnya menyangkut skala: bagaimana memproses berkas berukuran besar atau data jaringan yang datang bertahap tanpa memuat semuanya ke memori sekaligus? Jawabannya adalah stream, yang kita mulai dari konsep dasarnya pada Chapter Konsep Stream.