Pada tutorial kali ini di JadiFullstack, saya ingin berbagi pengalaman pribadi saat pertama kali belajar Golang. Dulu, saya juga sempat bingung membedakan arrays dan slices—apalagi saat harus memilih mana yang paling efisien untuk aplikasi yang sedang saya buat. Kalau kamu juga pernah merasakan hal yang sama, artikel ini cocok banget untukmu. kita akan mempelajari perbedaan arrays dan slices di Golang secara mendalam. Kedua data struktur ini merupakan fundamental dalam Golang, namun memiliki karakteristik dan penggunaan yang berbeda.
Artikel ini akan membahas perbedaan arrays dan slices, memory management, performance comparison, dan best practices untuk penggunaan optimal dalam project Go Anda.
Table of Contents
- Apa itu Arrays dan Slices di Golang?
- Arrays di Golang
- Slices di Golang
- Advanced Slice Operations
- Performance Comparison
- Use Cases dan Best Practices
- Common Pitfalls dan Solutions
- FAQ: Arrays vs Slices Golang
Apa itu Arrays dan Slices di Golang?
Arrays dan Slices adalah dua cara utama untuk menyimpan kumpulan data di Golang:
- Arrays: Kumpulan data dengan jumlah elemen tetap (fixed size) yang sudah ditentukan sejak awal program dijalankan.
- Slices: Versi yang lebih fleksibel dari array yang ukurannya bisa berubah-ubah, meskipun tetap menggunakan array di belakang layar.
Mengapa Perbedaan Arrays dan Slices Penting untuk Go Developer?
Memahami arrays vs slices Golang penting untuk:
- Memory management yang efisien dalam aplikasi Go
- Performance optimization untuk aplikasi dengan performa tinggi
- Pemilihan struktur data yang tepat sesuai kebutuhan use case
- Berinteraksi dengan built-in functions dan pustaka standar Go
Arrays di Golang: Struktur Data Berukuran Tetap
Array di Go memiliki ukuran tetap yang sudah ditentukan saat program dikompilasi, dan bersifat sebagai tipe nilai (value type).
Array Declaration dan Initialization
package main
import "fmt"
func main() {
// Cara 1: Membuat array kosong dengan nilai default (zero values)
var nilaiUjian [5]int // [0 0 0 0 0] - 5 elemen dengan nilai 0
fmt.Printf("Array kosong: %v\n", nilaiUjian)
// Cara 2: Membuat array langsung dengan isi
buahFavorit := [3]string{"mangga", "jeruk", "apel"}
fmt.Printf("Buah favorit: %v\n", buahFavorit)
// Cara 3: Biarkan Go menghitung ukuran array otomatis
warnaPerangi := [...]string{"merah", "putih", "biru", "kuning"}
fmt.Printf("Warna bendera: %v (jumlah: %d)\n", warnaPerangi, len(warnaPerangi))
// Cara 4: Mengisi array di posisi tertentu
scoreMatematika := [5]int{1: 85, 3: 92, 4: 78} // posisi 0,2 = 0
fmt.Printf("Nilai matematika: %v\n", scoreMatematika)
// Cara 5: Array 2 dimensi (seperti tabel)
jadwalPiket := [3][3]string{
{"Senin", "Selasa", "Rabu"},
{"Andi", "Budi", "Citra"},
{"Dina", "Eko", "Fitri"},
}
fmt.Printf("Jadwal piket kelas: %v\n", jadwalPiket)
}
Array Operations
package main
import "fmt"
func main() {
// Contoh: Menyimpan harga makanan di kantin sekolah
hargaMakanan := [5]int{5000, 3000, 8000, 2500, 6000}
// Mengakses elemen array (dimulai dari index 0)
fmt.Printf("Harga nasi gudeg: Rp %d\n", hargaMakanan[0])
fmt.Printf("Harga termahal: Rp %d\n", hargaMakanan[len(hargaMakanan)-1])
// Mengubah harga (misal ada diskon)
hargaMakanan[2] = 7000 // Turunkan harga dari 8000 ke 7000
fmt.Printf("Setelah diskon: %v\n", hargaMakanan)
// Mengetahui jumlah menu
fmt.Printf("Total menu makanan: %d\n", len(hargaMakanan))
// Cara 1: Loop menggunakan for biasa
fmt.Print("Daftar harga (for loop): ")
for i := 0; i < len(hargaMakanan); i++ {
fmt.Printf("Rp%d ", hargaMakanan[i])
}
fmt.Println()
// Cara 2: Loop menggunakan range (lebih mudah)
fmt.Print("Daftar lengkap: ")
for urutan, harga := range hargaMakanan {
fmt.Printf("Menu-%d: Rp%d ", urutan+1, harga)
}
fmt.Println()
// Cara 3: Hanya ambil harganya saja (abaikan index)
fmt.Print("Hanya harga: ")
for _, harga := range hargaMakanan {
fmt.Printf("Rp%d ", harga)
}
fmt.Println()
}
Array as Value Types
package main
import "fmt"
// Function yang menerima array sebagai kopian (by value)
func ubahNilaiSiswa(nilaiRapor [5]int) {
nilaiRapor[0] = 100 // Mengubah nilai pertama
fmt.Printf("Nilai di dalam function: %v\n", nilaiRapor)
}
// Function yang menerima array asli (by reference)
func ubahNilaiSiswaAsli(nilaiRapor *[5]int) {
nilaiRapor[0] = 100 // Mengubah nilai pertama di array asli
fmt.Printf("Nilai di dalam function (asli): %v\n", *nilaiRapor)
}
func main() {
// Contoh: Nilai rapor siswa
nilaiAsli := [5]int{80, 75, 90, 85, 88}
fmt.Printf("Nilai rapor asli: %v\n", nilaiAsli)
// Passing by value (kirim kopian) - array asli tidak berubah
ubahNilaiSiswa(nilaiAsli)
fmt.Printf("Setelah function kopian: %v\n", nilaiAsli) // Tidak berubah!
// Passing by reference (kirim alamat asli) - array asli berubah
ubahNilaiSiswaAsli(&nilaiAsli)
fmt.Printf("Setelah function asli: %v\n", nilaiAsli) // Berubah!
// Array assignment juga membuat kopian
nilaiCadangan := nilaiAsli
nilaiCadangan[1] = 95
fmt.Printf("Nilai asli: %v\n", nilaiAsli)
fmt.Printf("Nilai cadangan: %v\n", nilaiCadangan)
}
Array Comparison
package main
import "fmt"
func main() {
// Arrays bisa dibandingkan jika memiliki tipe dan ukuran yang sama
jadwalA := [3]string{"senin", "selasa", "rabu"}
jadwalB := [3]string{"senin", "selasa", "rabu"}
jadwalC := [3]string{"senin", "selasa", "kamis"}
fmt.Printf("Jadwal A sama dengan B: %t\n", jadwalA == jadwalB) // true
fmt.Printf("Jadwal A sama dengan C: %t\n", jadwalA == jadwalC) // false
// Arrays dengan ukuran berbeda TIDAK bisa dibandingkan
// var jadwalD [4]string
// fmt.Println(jadwalA == jadwalD) // Error kompilasi!
// Contoh perbandingan dengan angka
nilaiKelas1 := [3]int{85, 90, 78}
nilaiKelas2 := [3]int{85, 90, 78}
fmt.Printf("Nilai kedua kelas sama: %t\n", nilaiKelas1 == nilaiKelas2) // true
}
Slices di Golang: Array Dinamis yang Fleksibel
Slice adalah tipe referensi (reference type) yang menyediakan array dengan ukuran dinamis dan fleksibel. Slice merupakan struktur data yang paling umum digunakan dalam pemrograman Go.
Slice Declaration dan Initialization
package main
import "fmt"
func main() {
// Cara 1: Slice kosong (nil slice)
var daftarBelanja []string
fmt.Printf("Daftar kosong: %v (panjang: %d, kapasitas: %d)\n",
daftarBelanja, len(daftarBelanja), cap(daftarBelanja))
// Cara 2: Membuat slice dengan make (ukuran tetap dulu)
nilaiUlangan := make([]int, 5) // panjang 5, kapasitas 5
fmt.Printf("Nilai ulangan: %v (panjang: %d, kapasitas: %d)\n",
nilaiUlangan, len(nilaiUlangan), cap(nilaiUlangan))
// Cara 3: Membuat slice dengan kapasitas lebih besar
skorGame := make([]int, 3, 10) // panjang 3, kapasitas 10
fmt.Printf("Skor game: %v (panjang: %d, kapasitas: %d)\n",
skorGame, len(skorGame), cap(skorGame))
// Cara 4: Slice literal (langsung isi)
makananFavorit := []string{"rendang", "gudeg", "bakso"}
fmt.Printf("Makanan favorit: %v (panjang: %d, kapasitas: %d)\n",
makananFavorit, len(makananFavorit), cap(makananFavorit))
// Cara 5: Slice kosong tapi bukan nil
tempatWisata := []string{}
fmt.Printf("Tempat wisata: %v (panjang: %d, kapasitas: %d)\n",
tempatWisata, len(tempatWisata), cap(tempatWisata))
// Perbedaan nil slice vs empty slice
fmt.Printf("Daftar belanja nil? %t\n", daftarBelanja == nil)
fmt.Printf("Tempat wisata nil? %t\n", tempatWisata == nil)
}
Slice Operations
package main
import "fmt"
func main() {
// Membuat slice dari array (seperti memotong kue)
daftarAngka := [6]int{10, 20, 30, 40, 50, 60}
potongan1 := daftarAngka[1:4] // ambil index 1,2,3 = [20 30 40]
potongan2 := daftarAngka[:3] // dari awal sampai index 2 = [10 20 30]
potongan3 := daftarAngka[3:] // dari index 3 sampai akhir = [40 50 60]
potongan4 := daftarAngka[:] // ambil semua = [10 20 30 40 50 60]
fmt.Printf("Array asli: %v\n", daftarAngka)
fmt.Printf("Potongan 1 [1:4]: %v\n", potongan1)
fmt.Printf("Potongan 2 [:3]: %v\n", potongan2)
fmt.Printf("Potongan 3 [3:]: %v\n", potongan3)
fmt.Printf("Potongan 4 [:]: %v\n", potongan4)
// Operasi append (menambah elemen)
hobi := []string{"membaca", "menulis", "coding"}
fmt.Printf("Hobi awal: %v (panjang: %d, kapasitas: %d)\n",
hobi, len(hobi), cap(hobi))
// Tambah satu hobi
hobi = append(hobi, "menggambar")
fmt.Printf("Setelah tambah 1 hobi: %v (panjang: %d, kapasitas: %d)\n",
hobi, len(hobi), cap(hobi))
// Tambah beberapa hobi sekaligus
hobi = append(hobi, "bernyanyi", "menari", "memasak")
fmt.Printf("Setelah tambah 3 hobi: %v (panjang: %d, kapasitas: %d)\n",
hobi, len(hobi), cap(hobi))
// Menggabungkan dengan slice lain
hobiLain := []string{"berkebun", "bersepeda", "berenang"}
hobi = append(hobi, hobiLain...) // Perhatikan tiga titik
fmt.Printf("Setelah gabung slice: %v (panjang: %d, kapasitas: %d)\n",
hobi, len(hobi), cap(hobi))
}
Slice Memory Model
package main
import "fmt"
func main() {
// Memahami header slice
original := []int{1, 2, 3, 4, 5}
fmt.Printf("Original: %v (panjang: %d, kapasitas: %d)\n",
original, len(original), cap(original))
// Melakukan slicing membuat header slice baru, tapi tetap menggunakan array yang sama di belakang layar
sub := original[1:3]
fmt.Printf("Sub slice: %v (panjang: %d, kapasitas: %d)\n",
sub, len(sub), cap(sub))
// Mengubah isi sub slice akan memengaruhi original
sub[0] = 999
fmt.Printf("Setelah mengubah sub: %v\n", original)
fmt.Printf("Sub slice: %v\n", sub)
// Ekspresi slice lengkap untuk mengontrol kapasitas
aman := original[1:3:3] // [bawah:atas:maks]
fmt.Printf("Slice aman: %v (panjang: %d, kapasitas: %d)\n",
aman, len(aman), cap(aman))
// Menambahkan elemen ke slice aman tidak memengaruhi original
aman = append(aman, 777)
fmt.Printf("Setelah append ke slice aman: %v\n", original)
fmt.Printf("Slice aman: %v\n", aman)
}
Copy Function
package main
import "fmt"
func main() {
// Menggunakan fungsi copy
sumber := []int{1, 2, 3, 4, 5}
// Menyalin ke slice dengan panjang yang sama
tujuan1 := make([]int, len(sumber))
disalin := copy(tujuan1, sumber)
fmt.Printf("Sumber: %v\n", sumber)
fmt.Printf("Tujuan1: %v (disalin %d elemen)\n", tujuan1, disalin)
// Menyalin ke slice yang lebih pendek
tujuan2 := make([]int, 3)
disalin = copy(tujuan2, sumber)
fmt.Printf("Tujuan2: %v (disalin %d elemen)\n", tujuan2, disalin)
// Menyalin ke slice yang lebih panjang
tujuan3 := make([]int, 7)
disalin = copy(tujuan3, sumber)
fmt.Printf("Tujuan3: %v (disalin %d elemen)\n", tujuan3, disalin)
// Mengubah hasil salinan tidak memengaruhi slice sumber
tujuan1[0] = 999
fmt.Printf("Setelah mengubah salinan: sumber=%v, tujuan1=%v\n", sumber, tujuan1)
// Menyalin slice yang saling tumpang tindih
slice := []int{1, 2, 3, 4, 5}
copy(slice[2:], slice[1:]) // Menggeser elemen
fmt.Printf("Setelah copy tumpang tindih: %v\n", slice)
}
Advanced Slice Operations
Dynamic Growth dan Capacity Management
package main
import "fmt"
func demonstrateGrowth() {
slice := make([]int, 0, 1)
fmt.Printf("Initial: len=%d, cap=%d\n", len(slice), cap(slice))
for i := 0; i < 20; i++ {
slice = append(slice, i)
fmt.Printf("After append %d: len=%d, cap=%d\n",
i, len(slice), cap(slice))
}
}
func optimizedGrowth() {
// Pre-allocate kapasitas yang dibutuhkan
slice := make([]int, 0, 20)
fmt.Printf("Pre-allocated: len=%d, cap=%d\n", len(slice), cap(slice))
for i := 0; i < 20; i++ {
slice = append(slice, i)
}
fmt.Printf("Final: len=%d, cap=%d\n", len(slice), cap(slice))
}
func main() {
fmt.Println("=== Dynamic Growth ===")
demonstrateGrowth()
fmt.Println("\n=== Optimized Growth ===")
optimizedGrowth()
}
Slice Tricks dan Patterns
package main
import "fmt"
func main() {
slice := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
// Remove element at index
removeAt := func(s []int, index int) []int {
return append(s[:index], s[index+1:]...)
}
// Remove element 4 (index 3)
slice = removeAt(slice, 3)
fmt.Printf("After removing index 3: %v\n", slice)
// Insert element at index
insertAt := func(s []int, index int, value int) []int {
s = append(s, 0) // Extend slice
copy(s[index+1:], s[index:]) // Shift elements
s[index] = value // Insert new value
return s
}
// Insert 100 at index 2
slice = insertAt(slice, 2, 100)
fmt.Printf("After inserting 100 at index 2: %v\n", slice)
// Reverse slice
reverse := func(s []int) {
for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
s[i], s[j] = s[j], s[i]
}
}
reverse(slice)
fmt.Printf("After reverse: %v\n", slice)
// Filter slice
filter := func(s []int, predicate func(int) bool) []int {
result := s[:0] // Zero length, same capacity
for _, v := range s {
if predicate(v) {
result = append(result, v)
}
}
return result
}
// Keep only even numbers
evens := filter(slice, func(n int) bool { return n%2 == 0 })
fmt.Printf("Even numbers: %v\n", evens)
}
Multi-dimensional Slices
package main
import "fmt"
func main() {
// 2D slice
matrix := make([][]int, 3) // 3 rows
for i := range matrix {
matrix[i] = make([]int, 4) // 4 columns each
}
// Fill matrix
counter := 1
for i := range matrix {
for j := range matrix[i] {
matrix[i][j] = counter
counter++
}
}
fmt.Println("2D Matrix:")
for i, row := range matrix {
fmt.Printf("Row %d: %v\n", i, row)
}
// Jagged slice (different row lengths)
jagged := [][]int{
{1, 2},
{3, 4, 5, 6},
{7, 8, 9},
}
fmt.Println("\nJagged slice:")
for i, row := range jagged {
fmt.Printf("Row %d: %v (len: %d)\n", i, row, len(row))
}
// 3D slice
cube := make([][][]int, 2)
for i := range cube {
cube[i] = make([][]int, 3)
for j := range cube[i] {
cube[i][j] = make([]int, 4)
}
}
fmt.Printf("\n3D Cube dimensions: %dx%dx%d\n",
len(cube), len(cube[0]), len(cube[0][0]))
}
Performance Comparison
Memory Usage Analysis
package main
import (
"fmt"
"runtime"
"unsafe"
)
func getMemUsage() (float64, float64) {
var m runtime.MemStats
runtime.GC()
runtime.ReadMemStats(&m)
return float64(m.Alloc) / 1024 / 1024, float64(m.Sys) / 1024 / 1024
}
func arrayMemoryUsage() {
fmt.Println("=== Array Memory Usage ===")
before1, _ := getMemUsage()
// Create large arrays
arrays := make([1000]*[1000]int, 0)
for i := 0; i < 1000; i++ {
arr := &[1000]int{}
arrays = append(arrays, arr)
}
after1, _ := getMemUsage()
fmt.Printf("Arrays: %.2f MB\n", after1-before1)
// Size of array
var sampleArray [1000]int
fmt.Printf("Size of [1000]int: %d bytes\n", unsafe.Sizeof(sampleArray))
}
func sliceMemoryUsage() {
fmt.Println("\n=== Slice Memory Usage ===")
before2, _ := getMemUsage()
// Create large slices
slices := make([][]int, 0)
for i := 0; i < 1000; i++ {
slice := make([]int, 1000)
slices = append(slices, slice)
}
after2, _ := getMemUsage()
fmt.Printf("Slices: %.2f MB\n", after2-before2)
// Size of slice header
var sampleSlice []int
fmt.Printf("Size of slice header: %d bytes\n", unsafe.Sizeof(sampleSlice))
}
func main() {
arrayMemoryUsage()
sliceMemoryUsage()
}
Performance Benchmarks
package main
import (
"fmt"
"time"
)
func benchmarkArrayIteration() time.Duration {
arr := [10000]int{}
for i := range arr {
arr[i] = i
}
start := time.Now()
sum := 0
for i := 0; i < 1000; i++ {
for j := range arr {
sum += arr[j]
}
}
duration := time.Since(start)
fmt.Printf("Array sum: %d\n", sum)
return duration
}
func benchmarkSliceIteration() time.Duration {
slice := make([]int, 10000)
for i := range slice {
slice[i] = i
}
start := time.Now()
sum := 0
for i := 0; i < 1000; i++ {
for _, v := range slice {
sum += v
}
}
duration := time.Since(start)
fmt.Printf("Slice sum: %d\n", sum)
return duration
}
func benchmarkSliceAppend() time.Duration {
start := time.Now()
slice := make([]int, 0)
for i := 0; i < 10000; i++ {
slice = append(slice, i)
}
return time.Since(start)
}
func benchmarkSlicePrealloc() time.Duration {
start := time.Now()
slice := make([]int, 0, 10000) // Pre-allocate capacity
for i := 0; i < 10000; i++ {
slice = append(slice, i)
}
return time.Since(start)
}
func main() {
fmt.Println("=== Performance Benchmarks ===")
arrayTime := benchmarkArrayIteration()
sliceTime := benchmarkSliceIteration()
fmt.Printf("Array iteration: %v\n", arrayTime)
fmt.Printf("Slice iteration: %v\n", sliceTime)
fmt.Printf("Difference: %.2fx\n", float64(sliceTime)/float64(arrayTime))
appendTime := benchmarkSliceAppend()
preallocTime := benchmarkSlicePrealloc()
fmt.Printf("\nSlice append: %v\n", appendTime)
fmt.Printf("Slice prealloc: %v\n", preallocTime)
fmt.Printf("Prealloc is %.2fx faster\n", float64(appendTime)/float64(preallocTime))
}
Use Cases dan Best Practices
When to Use Arrays
package main
import "fmt"
// Arrays cocok untuk:
// 1. Data dengan ukuran tetap yang sudah diketahui
type WarnaRGB [3]uint8
func (w WarnaRGB) String() string {
return fmt.Sprintf("RGB(%d, %d, %d)", w[0], w[1], w[2])
}
// 2. Operasi matematika dengan dimensi tetap
type Matriks3x3 [3][3]float64
func (m Matriks3x3) Kali(lain Matriks3x3) Matriks3x3 {
var hasil Matriks3x3
for i := 0; i < 3; i++ {
for j := 0; j < 3; j++ {
for k := 0; k < 3; k++ {
hasil[i][j] += m[i][k] * lain[k][j]
}
}
}
return hasil
}
// 3. Tabel referensi dan konstanta
var hariBulan = [12]int{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
func dapatkanHariBulan(bulan int) int {
if bulan < 1 || bulan > 12 {
return 0
}
return hariBulan[bulan-1]
}
func main() {
// Contoh warna RGB
merah := WarnaRGB{255, 0, 0}
fmt.Printf("Warna merah: %v\n", merah)
// Contoh operasi matriks
identitas := Matriks3x3{
{1, 0, 0},
{0, 1, 0},
{0, 0, 1},
}
skala := Matriks3x3{
{2, 0, 0},
{0, 2, 0},
{0, 0, 2},
}
hasil := identitas.Kali(skala)
fmt.Printf("Hasil perkalian matriks: %v\n", hasil)
// Contoh tabel referensi
fmt.Printf("Hari di bulan Februari: %d\n", dapatkanHariBulan(2))
fmt.Printf("Hari di bulan Desember: %d\n", dapatkanHariBulan(12))
}
When to Use Slices
package main
import (
"fmt"
"sort"
"strings"
)
// Slices cocok untuk:
// 1. Koleksi data yang berubah-ubah
type DaftarBelanja struct {
barang []string
}
func (db *DaftarBelanja) Tambah(item string) {
db.barang = append(db.barang, item)
}
func (db *DaftarBelanja) Hapus(item string) {
for i, barang := range db.barang {
if barang == item {
db.barang = append(db.barang[:i], db.barang[i+1:]...)
break
}
}
}
func (db *DaftarBelanja) DaftarLengkap() string {
return strings.Join(db.barang, ", ")
}
// 2. Pemrosesan data bertahap
func ProsesAngka(angka []int) []int {
// Filter hanya angka positif
positif := make([]int, 0, len(angka))
for _, n := range angka {
if n > 0 {
positif = append(positif, n)
}
}
// Kuadratkan setiap angka
kuadrat := make([]int, len(positif))
for i, n := range positif {
kuadrat[i] = n * n
}
// Urutkan dari besar ke kecil
sort.Slice(kuadrat, func(i, j int) bool {
return squared[i] > squared[j]
})
return squared
}
// 3. Buffers dan streaming data
type Buffer struct {
data []byte
}
func (b *Buffer) Write(p []byte) (int, error) {
b.data = append(b.data, p...)
return len(p), nil
}
func (b *Buffer) Read(p []byte) (int, error) {
if len(b.data) == 0 {
return 0, fmt.Errorf("buffer is empty")
}
n := copy(p, b.data)
b.data = b.data[n:]
return n, nil
}
func (b *Buffer) Len() int {
return len(b.data)
}
func main() {
// Shopping list example
list := &ShoppingList{}
list.Add("milk")
list.Add("bread")
list.Add("eggs")
fmt.Printf("Shopping list: %s\n", list.List())
list.Remove("bread")
fmt.Printf("After removing bread: %s\n", list.List())
// Data processing example
numbers := []int{-5, 3, -2, 8, 1, -7, 4}
processed := ProcessNumbers(numbers)
fmt.Printf("Original: %v\n", numbers)
fmt.Printf("Processed: %v\n", processed)
// Buffer example
buffer := &Buffer{}
buffer.Write([]byte("Hello "))
buffer.Write([]byte("World!"))
output := make([]byte, 6)
n, _ := buffer.Read(output)
fmt.Printf("Read %d bytes: %s\n", n, string(output[:n]))
fmt.Printf("Remaining buffer length: %d\n", buffer.Len())
}
Common Pitfalls dan Solutions
❌ Common Mistakes
package main
import "fmt"
func main() {
// MISTAKE 1: Slice append in loop without preallocation
badAppend := func() []int {
var result []int
for i := 0; i < 10000; i++ {
result = append(result, i) // Multiple reallocations
}
return result
}
// SOLUTION: Preallocate capacity
goodAppend := func() []int {
result := make([]int, 0, 10000) // Preallocate
for i := 0; i < 10000; i++ {
result = append(result, i) // No reallocations
}
return result
}
// MISTAKE 2: Unexpected slice sharing
original := []int{1, 2, 3, 4, 5}
sub1 := original[1:3] // Shares underlying array
sub2 := original[2:4] // Also shares same array
sub1[1] = 999 // This affects sub2 and original!
fmt.Printf("Original: %v\n", original) // [1 2 999 4 5]
fmt.Printf("Sub1: %v\n", sub1) // [2 999]
fmt.Printf("Sub2: %v\n", sub2) // [999 4]
// SOLUTION: Use copy for independence
original2 := []int{1, 2, 3, 4, 5}
safeSub := make([]int, 2)
copy(safeSub, original2[1:3])
safeSub[1] = 777
fmt.Printf("Original2: %v\n", original2) // [1 2 3 4 5] - unchanged
fmt.Printf("SafeSub: %v\n", safeSub) // [2 777]
// MISTAKE 3: Slice leak dalam loop
// BAD: This can cause memory leak
processLargeData := func() []string {
largeData := make([]string, 1000000)
// ... fill with data
return largeData[:10] // Keeps entire array in memory!
}
// GOOD: Copy needed data
processLargeDataSafe := func() []string {
largeData := make([]string, 1000000)
// ... fill with data
result := make([]string, 10)
copy(result, largeData[:10])
return result // Large array can be garbage collected
}
_ = badAppend
_ = goodAppend
_ = processLargeData
_ = processLargeDataSafe
}
✅ Best Practices
package main
import "fmt"
// 1. Preallocate slices when size is known
func efficientStringBuilder(words []string) string {
totalLen := 0
for _, word := range words {
totalLen += len(word)
}
result := make([]byte, 0, totalLen)
for _, word := range words {
result = append(result, []byte(word)...)
}
return string(result)
}
// 2. Use full slice expression untuk safety
func safeSlicing(data []int, start, end int) []int {
// Full slice expression limits capacity
return data[start:end:end] // [low:high:max]
}
// 3. Nil slice vs empty slice
func demonstrateNilVsEmpty() {
var nilSlice []int // nil slice
emptySlice := []int{} // empty slice
madeSlice := make([]int, 0) // empty slice
fmt.Printf("nilSlice == nil: %t\n", nilSlice == nil) // true
fmt.Printf("emptySlice == nil: %t\n", emptySlice == nil) // false
fmt.Printf("madeSlice == nil: %t\n", madeSlice == nil) // false
// All can be appended to safely
nilSlice = append(nilSlice, 1)
emptySlice = append(emptySlice, 2)
madeSlice = append(madeSlice, 3)
fmt.Printf("After append - nil: %v, empty: %v, made: %v\n",
nilSlice, emptySlice, madeSlice)
}
// 4. Proper error handling dengan slices
func safeSliceAccess(slice []int, index int) (int, error) {
if index < 0 || index >= len(slice) {
return 0, fmt.Errorf("index %d out of range [0:%d]", index, len(slice))
}
return slice[index], nil
}
func main() {
words := []string{"hello", "world", "golang"}
result := efficientStringBuilder(words)
fmt.Printf("Concatenated: %s\n", result)
data := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
safe := safeSlicing(data, 2, 5)
fmt.Printf("Safe slice: %v (cap: %d)\n", safe, cap(safe))
demonstrateNilVsEmpty()
if val, err := safeSliceAccess(data, 5); err == nil {
fmt.Printf("Value at index 5: %d\n", val)
} else {
fmt.Printf("Error: %v\n", err)
}
}
Kesimpulan
Arrays dan Slices di Golang memiliki karakteristik yang berbeda:
Arrays:
- Fixed size yang ditentukan saat compile time
- Value types - passing by value creates copy
- Memory efficient untuk fixed-size data
- Comparable dengan operator ==
- Use cases: Matrix operations, lookup tables, konstanta
Slices:
- Dynamic size yang flexible
- Reference types - sharing underlying array
- Built-in growth dengan append function
- More common dalam Go programming
- Use cases: Collections, buffers, data processing
Best Practices:
- Prefer slices untuk most use cases
- Preallocate capacity when size is known
- Use copy untuk independence
- Be aware of slice sharing behavior
- Use full slice expression untuk safety
- Handle nil slices properly
Semoga penjelasan dan contoh di atas bisa membantumu lebih paham arrays dan slices di Golang. Pada tutorial selanjutnya, kita akan membahas Maps di Golang yang merupakan built-in hash table implementation yang powerful!
Related Articles: