Array dalam Bahasa Go
1.1 Definisi dan Deklarasi Array
Array adalah urutan elemen berukuran tetap dengan tipe yang sama. Dalam bahasa Go, panjang array dianggap sebagai bagian dari tipe array. Hal ini berarti bahwa array dengan panjang yang berbeda dianggap sebagai tipe yang berbeda.
Sintaks dasar untuk mendeklarasikan sebuah array adalah sebagai berikut:
var arr [n]T
Di sini, var
adalah kata kunci untuk deklarasi variabel, arr
adalah nama array, n
mewakili panjang array, dan T
mewakili tipe elemen dalam array.
Sebagai contoh, untuk mendeklarasikan sebuah array yang berisi 5 bilangan bulat:
var myArray [5]int
Pada contoh ini, myArray
adalah array yang dapat berisi 5 bilangan bulat dengan tipe int
.
1.2 Inisialisasi dan Penggunaan Array
Inisialisasi array dapat dilakukan langsung pada saat deklarasi atau dengan memberikan nilai menggunakan indeks. Terdapat beberapa metode untuk inisialisasi array:
Inisialisasi Langsung
var myArray = [5]int{10, 20, 30, 40, 50}
Juga memungkinkan untuk membiarkan kompiler untuk menginfer panjang array berdasarkan jumlah nilai yang diinisialisasi:
var myArray = [...]int{10, 20, 30, 40, 50}
Di sini, ...
mengindikasikan bahwa panjang array dihitung oleh kompiler.
Inisialisasi Menggunakan Indeks
var myArray [5]int
myArray[0] = 10
myArray[1] = 20
// Elemen-elemen yang tersisa diinisialisasi menjadi 0, karena nilai nol dari int adalah 0
Penggunaan array juga sederhana, dan elemen-elemennya dapat diakses menggunakan indeks:
fmt.Println(myArray[2]) // Mengakses elemen ketiga
1.3 Traversal Array
Dua metode umum untuk traversal array adalah menggunakan loop for
tradisional dan menggunakan range
.
Traversal Menggunakan Loop for
for i := 0; i < len(myArray); i++ {
fmt.Println(myArray[i])
}
Traversal Menggunakan range
for index, value := range myArray {
fmt.Printf("Indeks: %d, Nilai: %d\n", index, value)
}
Kelebihan menggunakan range
adalah bahwa ia mengembalikan dua nilai: posisi indeks saat ini dan nilai pada posisi tersebut.
1.4 Karakteristik dan Batasan Array
Dalam bahasa Go, array adalah tipe nilai, yang berarti bahwa saat sebuah array dilewatkan sebagai parameter ke sebuah fungsi, salinan dari array tersebut dilewatkan. Oleh karena itu, jika modifikasi terhadap array asli diperlukan dalam sebuah fungsi, biasanya digunakan slice atau pointer ke array.
2 Slice dalam Bahasa Go
2.1 Konsep Slice
Dalam bahasa Go, slice adalah sebuah abstraksi atas array. Ukuran array dalam Go tidak dapat diubah, yang membatasi penggunaannya dalam beberapa skenario. Slice dalam Go dirancang untuk lebih fleksibel, menyediakan antarmuka yang nyaman, fleksibel, dan kuat untuk melakukan serialisasi struktur data. Slice sendiri tidak menyimpan data; mereka hanyalah referensi ke array yang mendasarinya. Sifat dinamisnya secara umum ditandai oleh beberapa poin berikut:
- Ukuran Dinamis: Berbeda dengan array, panjang dari sebuah slice bersifat dinamis, memungkinkannya untuk tumbuh atau menyusut secara otomatis sesuai kebutuhan.
-
Fleksibilitas: Elemen-elemen dapat dengan mudah ditambahkan ke sebuah slice dengan menggunakan fungsi bawaan
append
. - Tipe Referensi: Slice mengakses elemen-elemen dalam array yang mendasarinya secara referensi, tanpa membuat salinan data.
2.2 Deklarasi dan Inisialisasi Slice
Sintaks untuk mendeklarasikan sebuah slice mirip dengan mendeklarasikan sebuah array, tetapi tidak perlu menentukan jumlah elemen saat mendeklarasikan. Sebagai contoh, cara untuk mendeklarasikan sebuah slice dari bilangan bulat adalah sebagai berikut:
var slice []int
Anda dapat menginisialisasi sebuah slice menggunakan literal slice:
slice := []int{1, 2, 3}
Variabel slice
di atas akan diinisialisasi sebagai slice yang berisi tiga bilangan bulat.
Anda juga dapat menginisialisasi sebuah slice menggunakan fungsi make
, yang memungkinkan Anda untuk menentukan panjang dan kapasitas dari slice tersebut:
slice := make([]int, 5) // Membuat sebuah slice dari bilangan bulat dengan panjang dan kapasitas 5
Jika diperlukan kapasitas yang lebih besar, Anda dapat melewatkan kapasitas sebagai parameter ketiga ke fungsi make
:
slice := make([]int, 5, 10) // Membuat sebuah slice dari bilangan bulat dengan panjang 5 dan kapasitas 10
2.3 Hubungan Antara Slice dan Array
Slice dapat dibuat dengan menentukan segmen dari sebuah array, membentuk referensi ke segmen tersebut. Sebagai contoh, dengan menggunakan array berikut:
array := [5]int{10, 20, 30, 40, 50}
Kita dapat membuat sebuah slice sebagai berikut:
slice := array[1:4]
Slice slice
ini akan merujuk pada elemen-elemen dalam array array
mulai dari indeks 1 hingga indeks 3 (inklusif pada indeks 1, namun eksklusif pada indeks 4).
Penting untuk dicatat bahwa slice tidak benar-benar menyalin nilai dari array; ia hanya menunjuk pada segmen kontinu dari array asli. Oleh karena itu, modifikasi pada slice juga akan mempengaruhi array yang mendasarinya, dan sebaliknya. Memahami hubungan referensi ini merupakan hal penting untuk menggunakan slice dengan efektif.
2.4 Operasi Dasar pada Slice
2.4.1 Pengindeksan
Slice mengakses elemennya menggunakan indeks, mirip dengan array, dengan indeks dimulai dari 0. Sebagai contoh:
slice := []int{10, 20, 30, 40}
// Mengakses elemen pertama dan ketiga
fmt.Println(slice[0], slice[2])
2.4.2 Panjang dan Kapasitas
Slice memiliki dua properti: panjang (len
) dan kapasitas (cap
). Panjang adalah jumlah elemen dalam slice, dan kapasitas adalah jumlah elemen dari elemen pertama slice hingga akhir dari array yang mendasarinya.
slice := []int{10, 20, 30, 40}
// Mencetak panjang dan kapasitas dari slice
fmt.Println(len(slice), cap(slice))
2.4.3 Penambahan Elemen
Fungsi append
digunakan untuk menambahkan elemen ke dalam slice. Ketika kapasitas slice tidak mencukupi untuk menampung elemen-elemen baru, fungsi append
secara otomatis memperluas kapasitas slice.
slice := []int{10, 20, 30}
// Menambahkan satu elemen
slice = append(slice, 40)
// Menambahkan beberapa elemen
slice = append(slice, 50, 60)
fmt.Println(slice)
Penting untuk dicatat bahwa ketika menggunakan append
untuk menambahkan elemen, ini dapat mengembalikan sebuah slice baru. Jika kapasitas dari array yang mendasarinya tidak mencukupi, operasi append
akan membuat slice merujuk pada array baru yang lebih besar.
2.5 Perluasan dan Penyalinan Slice
Fungsi copy
dapat digunakan untuk menyalin elemen-elemen dari sebuah slice ke slice lainnya. Slice yang dituju harus sudah mengalokasikan cukup ruang untuk menampung elemen-elemen yang disalin, dan operasi ini tidak akan mengubah kapasitas dari slice yang dituju.
2.5.1 Menggunakan Fungsi copy
Berikut adalah contoh penggunaan copy
:
src := []int{1, 2, 3}
dst := make([]int, 3)
// Menyalin elemen ke dalam slice yang dituju
disalin := copy(dst, src)
fmt.Println(dst, disalin)
Fungsi copy
mengembalikan jumlah elemen yang disalin, dan tidak akan melebihi panjang dari slice yang dituju atau panjang dari slice sumber, mana yang lebih kecil.
2.5.2 Pertimbangan
Ketika menggunakan fungsi copy
, jika elemen-elemen baru ditambahkan untuk disalin namun slice yang dituju tidak memiliki cukup ruang, hanya elemen-elemen yang dapat diakomodasi oleh slice yang dituju akan disalin.
2.6 Slice Multi-dimensi
Slice multi-dimensi adalah slice yang berisi beberapa slice. Ini mirip dengan array multi-dimensi, namun karena panjang variabel dari slice, slice multi-dimensi lebih fleksibel.
2.6.1 Membuat Slice Multi-dimensi
Membuat slice dua dimensi (slice dari slice):
twoD := make([][]int, 3)
for i := 0; i < 3; i++ {
twoD[i] = make([]int, 3)
for j := 0; j < 3; j++ {
twoD[i][j] = i + j
}
}
fmt.Println("Slice dua dimensi: ", twoD)
2.6.2 Menggunakan Slice Multi-dimensi
Menggunakan slice multi-dimensi mirip dengan menggunakan slice satu dimensi, diakses dengan indeks:
// Mengakses elemen dari slice dua dimensi
nilai := twoD[1][2]
fmt.Println(nilai)
3 Perbandingan Aplikasi Array dan Slice
3.1 Perbandingan Skenario Penggunaan
Array dan slice dalam Go keduanya digunakan untuk menyimpan koleksi data dengan tipe yang sama, namun mereka memiliki perbedaan yang jelas dalam skenario penggunaan.
Array:
- Panjang array telah ditetapkan pada saat deklarasi, membuatnya cocok untuk menyimpan jumlah elemen yang sudah diketahui dan tetap.
- Ketika diperlukan wadah dengan ukuran tetap, seperti merepresentasikan matriks berukuran tetap, array adalah pilihan terbaik.
- Array dapat dialokasikan di stack, memberikan kinerja yang lebih baik ketika ukuran array tidak terlalu besar.
Slice:
- Sebuah slice adalah penjabaran dari array dinamis, dengan panjang yang dapat berubah, cocok untuk menyimpan jumlah yang tidak diketahui atau koleksi elemen yang dapat berubah secara dinamis.
- Ketika diperlukan array dinamis yang dapat tumbuh atau menyusut sesuai kebutuhan, seperti untuk menyimpan input pengguna yang tidak pasti, slice adalah pilihan yang lebih cocok.
- Tata letak memori dari sebuah slice memungkinkan untuk referensi yang mudah dari sebagian atau seluruh array, umumnya digunakan untuk pemrosesan substring, membagi isi file, dan skenario lainnya.
Secara keseluruhan, array cocok untuk skenario dengan kebutuhan ukuran tetap, mencerminkan fitur manajemen memori statis Go, sementara slice lebih fleksibel, berfungsi sebagai perluasan abstrak dari array, nyaman untuk menangani koleksi dinamis.
3.2 Pertimbangan Kinerja
Ketika kita perlu memilih antara menggunakan array atau slice, kinerja adalah faktor penting yang perlu dipertimbangkan.
Array:
- Kecepatan akses cepat, karena memiliki memori yang kontinu dan indeks yang tetap.
- Alokasi memori di stack (jika ukuran array diketahui dan tidak terlalu besar), tanpa melibatkan overhed memori heap tambahan.
- Tidak ada memori tambahan untuk menyimpan panjang dan kapasitas, yang dapat bermanfaat untuk program yang sensitif terhadap memori.
Slice:
- Pertumbuhan atau penyusutan dinamis dapat menyebabkan overhed kinerja: pertumbuhan dapat mengakibatkan alokasi memori baru dan menyalin elemen lama, sementara penyusutan dapat memerlukan penyesuaian pointer.
- Operasi slice sendiri cepat, namun penambahan atau penghapusan elemen yang sering dapat menyebabkan fragmentasi memori.
- Meskipun akses slice menimbulkan overhead tidak langsung yang kecil, umumnya tidak memiliki dampak signifikan pada kinerja kecuali dalam kode yang sangat sensitif terhadap kinerja.
Oleh karena itu, jika kinerja adalah pertimbangan utama dan ukuran data diketahui sebelumnya, maka menggunakan array lebih cocok. Namun, jika fleksibilitas dan kenyamanan diperlukan, maka disarankan untuk menggunakan slice, terutama untuk menangani kumpulan data yang besar.
4 Masalah Umum dan Solusinya
Dalam proses menggunakan array dan slice dalam bahasa Go, para pengembang dapat menghadapi masalah umum berikut.
Masalah 1: Array di Luar Batas
- Array di luar batas merujuk pada mengakses indeks yang melampaui panjang array. Hal ini akan mengakibatkan kesalahan saat runtime.
- Solusi: Selalu periksa apakah nilai indeks berada dalam rentang valid dari array sebelum mengakses elemen array. Ini dapat dicapai dengan membandingkan indeks dan panjang array.
var arr [5]int
index := 10 // Anggap indeks di luar jangkauan
if index < len(arr) {
fmt.Println(arr[index])
} else {
fmt.Println("Indeks di luar jangkauan array.")
}
Masalah 2: Memory Leak dalam Slice
- Slice mungkin secara tidak sengaja menyimpan referensi ke bagian atau seluruh array asli, bahkan jika hanya sebagian kecil yang diperlukan. Hal ini dapat menyebabkan kebocoran memori jika array asli cukup besar.
- Solusi: Jika diperlukan slice sementara, pertimbangkan untuk membuat slice baru dengan menyalin bagian yang diperlukan.
original := make([]int, 1000000)
smallSlice := make([]int, 10)
copy(smallSlice, original[:10]) // Hanya menyalin bagian yang diperlukan
// Dengan cara ini, smallSlice tidak merujuk ke bagian lain dari original, membantu GC untuk mendapatkan kembali memori yang tidak perlu
Masalah 3: Kesalahan Data yang Disebabkan oleh Penggunaan Ulang Slice
- Karena slice berbagi referensi ke array yang sama, memungkinkan untuk melihat dampak modifikasi data pada slice yang berbeda, yang dapat menyebabkan kesalahan yang tidak terduga.
- Solusi: Untuk menghindari situasi ini, lebih baik membuat salinan slice baru.
sliceA := []int{1, 2, 3, 4, 5}
sliceB := make([]int, len(sliceA))
copy(sliceB, sliceA)
sliceB[0] = 100
fmt.Println(sliceA[0]) // Output: 1
fmt.Println(sliceB[0]) // Output: 100
Di atas hanyalah beberapa masalah umum dan solusi yang mungkin muncul saat menggunakan array dan slice dalam bahasa Go. Ada banyak detail lain yang perlu diperhatikan dalam pengembangan yang sebenarnya, namun mengikuti prinsip dasar ini dapat membantu menghindari banyak kesalahan umum.