Strings #

Manipulasi teks adalah salah satu kebutuhan paling mendasar dalam hampir setiap program. Go menyediakan package strings di standard library yang berisi lebih dari 40 fungsi siap pakai untuk bekerja dengan string — mulai dari pencarian sederhana, transformasi huruf, pemisahan teks, hingga penggantian konten secara massal. Memahami package ini dengan baik akan menghindarkan kamu dari menulis ulang logika yang sudah ada, sekaligus memastikan kode kamu idiomatik dan performan. Artikel ini membahas seluruh kelompok fungsi dalam strings, kapan menggunakan masing-masing, dan jebakan umum yang perlu dihindari.

Import dan Penggunaan Dasar #

Package strings adalah bagian dari standard library Go — tidak perlu instalasi, cukup import dan langsung gunakan. Semua fungsinya bekerja dengan tipe string bawaan Go yang bersifat immutable (tidak bisa diubah di tempat), sehingga setiap fungsi selalu mengembalikan string baru.

import (
    "fmt"
    "strings"
)

func main() {
    s := "Halo, Golang!"

    fmt.Println(strings.ToUpper(s))       // "HALO, GOLANG!"
    fmt.Println(strings.Contains(s, "Go")) // true
    fmt.Println(strings.Count(s, "l"))     // 2
}
Semua fungsi dalam strings aman digunakan secara concurrent — tidak ada state internal yang dimodifikasi. Kamu bisa memanggil fungsi-fungsi ini dari banyak goroutine sekaligus tanpa risiko race condition.

Pengecekan Konten String #

Kelompok fungsi ini digunakan untuk memeriksa apakah sebuah string mengandung, diawali, atau diakhiri dengan teks tertentu. Ini adalah operasi yang paling sering dibutuhkan, misalnya saat memvalidasi input, memeriksa ekstensi file, atau memfilter data.

Contains dan ContainsAny #

strings.Contains memeriksa apakah sebuah substring ada di dalam string. strings.ContainsAny memeriksa apakah string mengandung setidaknya satu karakter dari sekumpulan karakter yang diberikan.

s := "pemrograman golang"

// Contains — cek substring
fmt.Println(strings.Contains(s, "golang"))     // true
fmt.Println(strings.Contains(s, "python"))     // false
fmt.Println(strings.Contains(s, ""))           // true (string kosong selalu ada)

// ContainsAny — cek salah satu karakter
fmt.Println(strings.ContainsAny(s, "aeiou"))   // true  (mengandung vokal)
fmt.Println(strings.ContainsAny(s, "xyz"))     // false

// ContainsRune — cek satu rune spesifik
fmt.Println(strings.ContainsRune(s, 'g'))      // true

HasPrefix dan HasSuffix #

Dua fungsi ini sangat berguna untuk memeriksa awalan dan akhiran — misalnya mengecek protokol URL, ekstensi file, atau format pesan.

url := "https://api.contoh.com/v1/users"
filename := "laporan_2024.pdf"

fmt.Println(strings.HasPrefix(url, "https://"))  // true
fmt.Println(strings.HasPrefix(url, "http://"))   // false
fmt.Println(strings.HasSuffix(filename, ".pdf")) // true
fmt.Println(strings.HasSuffix(filename, ".csv")) // false

// Pola umum: validasi dan proses
func prosesURL(url string) error {
    if !strings.HasPrefix(url, "https://") {
        return fmt.Errorf("hanya mendukung HTTPS, dapat: %s", url)
    }
    // lanjutkan proses...
    return nil
}

EqualFold — Perbandingan Case-Insensitive #

Untuk membandingkan dua string tanpa memperhatikan huruf besar/kecil, gunakan EqualFold — jauh lebih efisien daripada mengkonversi keduanya ke lowercase lalu membandingkan.

// ANTI-PATTERN: konversi tidak perlu sebelum perbandingan
if strings.ToLower(input) == strings.ToLower("admin") {
    // ...
}

// BENAR: gunakan EqualFold langsung
if strings.EqualFold(input, "admin") {
    // ...
}

fmt.Println(strings.EqualFold("Go", "go"))     // true
fmt.Println(strings.EqualFold("Go", "GO"))     // true
fmt.Println(strings.EqualFold("Go", "Java"))   // false

Pencarian dan Posisi #

Ketika kamu perlu tahu bukan hanya apakah sebuah substring ada, tapi di mana posisinya, gunakan kelompok fungsi Index. Fungsi-fungsi ini mengembalikan indeks byte (bukan karakter Unicode) dari kemunculan pertama atau terakhir substring.

Index, LastIndex, dan IndexAny #

s := "go adalah bahasa go yang cepat"

// Index — posisi kemunculan pertama
fmt.Println(strings.Index(s, "go"))       // 0
fmt.Println(strings.Index(s, "python"))   // -1 (tidak ditemukan)

// LastIndex — posisi kemunculan terakhir
fmt.Println(strings.LastIndex(s, "go"))   // 18

// IndexAny — posisi karakter pertama yang cocok dari sekumpulan karakter
fmt.Println(strings.IndexAny(s, "aeiou")) // 3 (karakter 'a' di posisi 3)

// IndexByte — cari byte tunggal (lebih cepat dari Index untuk satu karakter)
fmt.Println(strings.IndexByte(s, 'a'))    // 3

// IndexRune — cari rune Unicode spesifik
fmt.Println(strings.IndexRune(s, 'ā'))    // -1

Pola: Parsing Sederhana dengan Index #

Kombinasi Index dan slicing string adalah pola yang sangat umum untuk parsing format sederhana tanpa regex.

func parseKV(kv string) (string, string, bool) {
    idx := strings.Index(kv, "=")
    if idx == -1 {
        return "", "", false
    }
    return kv[:idx], kv[idx+1:], true
}

key, val, ok := parseKV("nama=golang")
fmt.Println(key, val, ok) // "nama" "golang" true

key, val, ok = parseKV("invalid-format")
fmt.Println(key, val, ok) // "" "" false

Transformasi Huruf dan Whitespace #

Transformasi teks yang paling umum — mengubah kapitalisasi dan membersihkan whitespace — semuanya tersedia langsung di package strings.

Kapitalisasi #

s := "  Halo Dunia golang  "

fmt.Println(strings.ToUpper(s))   // "  HALO DUNIA GOLANG  "
fmt.Println(strings.ToLower(s))   // "  halo dunia golang  "
fmt.Println(strings.Title(s))     // Deprecated di Go 1.18+

// Untuk title case yang benar secara Unicode, gunakan golang.org/x/text
// strings.Title masih berfungsi tapi tidak menangani Unicode dengan sempurna
strings.Title sudah deprecated sejak Go 1.18 karena tidak menangani aturan title case Unicode dengan benar. Untuk aplikasi yang membutuhkan internasionalisasi, gunakan package golang.org/x/text/cases sebagai gantinya.

Trim — Menghapus Karakter di Tepi String #

Go menyediakan beberapa varian Trim untuk kebutuhan yang berbeda:

FungsiPerilaku
strings.TrimSpace(s)Hapus whitespace (spasi, tab, newline) di kiri dan kanan
strings.Trim(s, cutset)Hapus karakter dalam cutset di kiri dan kanan
strings.TrimLeft(s, cutset)Hapus karakter cutset hanya di kiri
strings.TrimRight(s, cutset)Hapus karakter cutset hanya di kanan
strings.TrimPrefix(s, prefix)Hapus prefix jika string diawali dengan itu
strings.TrimSuffix(s, suffix)Hapus suffix jika string diakhiri dengan itu
strings.TrimFunc(s, f)Hapus karakter di tepi yang memenuhi fungsi f
s := "   golang   "

// TrimSpace — paling umum digunakan
fmt.Println(strings.TrimSpace(s))           // "golang"

// Trim dengan cutset karakter
fmt.Println(strings.Trim("***golang***", "*"))    // "golang"
fmt.Println(strings.TrimLeft("***golang***", "*")) // "golang***"

// TrimPrefix dan TrimSuffix — hanya menghapus jika benar-benar cocok
path := "/api/v1/users"
fmt.Println(strings.TrimPrefix(path, "/api")) // "/v1/users"
fmt.Println(strings.TrimSuffix(path, "users")) // "/api/v1/"

// ANTI-PATTERN: menggunakan Trim untuk menghapus prefix/suffix spesifik
// Ini bisa menghasilkan hasil yang tidak terduga karena Trim bekerja per-karakter
fmt.Println(strings.Trim("/api/v1/", "/"))  // "api/v1" — semua '/' di tepi terhapus
// ✓ Gunakan TrimPrefix / TrimSuffix untuk prefix/suffix yang pasti

Map — Transformasi Per Karakter #

strings.Map memungkinkan kamu mentransformasi setiap karakter dalam string menggunakan fungsi kustom. Mengembalikan -1 dari fungsi akan menghapus karakter tersebut.

// Hapus semua angka dari string
hapusAngka := func(r rune) rune {
    if r >= '0' && r <= '9' {
        return -1  // hapus karakter ini
    }
    return r
}
fmt.Println(strings.Map(hapusAngka, "go1.21.0"))  // "go.."

// Enkripsi sederhana — geser setiap huruf satu posisi
rot1 := func(r rune) rune {
    switch {
    case r >= 'a' && r <= 'z':
        return 'a' + (r-'a'+1)%26
    case r >= 'A' && r <= 'Z':
        return 'A' + (r-'A'+1)%26
    }
    return r
}
fmt.Println(strings.Map(rot1, "Hello")) // "Ifmmp"

Pemisahan String (Split) #

Memecah string berdasarkan delimiter adalah salah satu operasi yang paling sering digunakan — membaca baris CSV, memproses argumen CLI, parsing konfigurasi, dan banyak lagi.

Split dan SplitN #

data := "apel,jeruk,mangga,pisang"

// Split — pecah berdasarkan separator, hasilkan semua bagian
parts := strings.Split(data, ",")
fmt.Println(parts)        // ["apel" "jeruk" "mangga" "pisang"]
fmt.Println(len(parts))   // 4

// SplitN — pecah maksimal N bagian
parts2 := strings.SplitN(data, ",", 2)
fmt.Println(parts2)  // ["apel" "jeruk,mangga,pisang"]

// Split dengan string kosong — pecah menjadi individual rune
chars := strings.Split("golang", "")
fmt.Println(chars)   // ["g" "o" "l" "a" "n" "g"]

// Edge case: separator tidak ada
fmt.Println(strings.Split("golang", ",")) // ["golang"] — slice satu elemen

SplitAfter — Pisah Tapi Pertahankan Separator #

Berbeda dari Split, SplitAfter menyertakan separator di akhir setiap elemen hasil. Berguna saat kamu ingin mempertahankan delimiter sebagai bagian dari token.

kalimat := "Ini kalimat pertama. Ini kalimat kedua. Ini kalimat ketiga."
bagian := strings.SplitAfter(kalimat, ". ")
for _, b := range bagian {
    fmt.Printf("%q\n", b)
}
// "Ini kalimat pertama. "
// "Ini kalimat kedua. "
// "Ini kalimat ketiga."

Fields — Split Berdasarkan Whitespace #

strings.Fields adalah cara paling mudah memecah string berdasarkan whitespace (termasuk spasi ganda, tab, newline) — hasil sudah bersih tanpa elemen kosong.

// ANTI-PATTERN: Split dengan " " tidak menangani spasi ganda
parts := strings.Split("  golang  adalah  keren  ", " ")
fmt.Println(parts) // ["" "" "golang" "" "adalah" "" "keren" "" ""]
// Ada banyak string kosong yang tidak diinginkan

// BENAR: gunakan Fields untuk whitespace
words := strings.Fields("  golang  adalah  keren  ")
fmt.Println(words) // ["golang" "adalah" "keren"]
fmt.Println(len(words)) // 3

// Sangat berguna untuk memproses input pengguna
func parsePerintah(input string) []string {
    return strings.Fields(strings.TrimSpace(input))
}

FieldsFunc — Split dengan Kondisi Kustom #

// Pecah berdasarkan karakter non-huruf
isNotLetter := func(r rune) bool {
    return !((r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z'))
}
words := strings.FieldsFunc("golang,adalah;bahasa.terbaik!", isNotLetter)
fmt.Println(words) // ["golang" "adalah" "bahasa" "terbaik"]

Penggabungan String (Join dan Repeat) #

Kebalikan dari Split adalah Join. Untuk membuat string dari potongan-potongan kecil secara berulang, Go menyediakan fungsi yang efisien.

Join #

strings.Join menggabungkan slice of string dengan separator. Ini adalah cara yang direkomendasikan untuk membangun string dari banyak bagian.

buah := []string{"apel", "jeruk", "mangga"}

// Join dengan separator
fmt.Println(strings.Join(buah, ", "))  // "apel, jeruk, mangga"
fmt.Println(strings.Join(buah, " - ")) // "apel - jeruk - mangga"
fmt.Println(strings.Join(buah, ""))    // "apeljerukmangga"

// Pola umum: bangun query atau path
segmen := []string{"api", "v1", "users", "123"}
path := "/" + strings.Join(segmen, "/")
fmt.Println(path) // "/api/v1/users/123"

Repeat #

strings.Repeat mengulang string sebanyak N kali. Berguna untuk membuat padding, separator visual, atau template sederhana.

fmt.Println(strings.Repeat("=", 40))      // "========================================"
fmt.Println(strings.Repeat("Go! ", 3))    // "Go! Go! Go! "
fmt.Println(strings.Repeat("-", 0))       // "" (nol pengulangan = string kosong)

// Pola: buat padding untuk output yang rapi
func cetakJudul(judul string) {
    lebar := 50
    batas := strings.Repeat("=", lebar)
    fmt.Println(batas)
    fmt.Println(judul)
    fmt.Println(batas)
}

Penggantian Konten (Replace dan ReplaceAll) #

Mengganti substring dalam teks adalah operasi yang sangat umum — sanitasi input, template sederhana, normalisasi data, dan sebagainya.

Replace dan ReplaceAll #

s := "golang golang golang adalah pilihan golang terbaik"

// Replace — ganti N kemunculan pertama
fmt.Println(strings.Replace(s, "golang", "Go", 1))  // "Go golang golang adalah pilihan golang terbaik"
fmt.Println(strings.Replace(s, "golang", "Go", 2))  // "Go Go golang adalah pilihan golang terbaik"
fmt.Println(strings.Replace(s, "golang", "Go", -1)) // ganti semua (sama dengan ReplaceAll)

// ReplaceAll — ganti semua kemunculan (lebih ekspresif)
fmt.Println(strings.ReplaceAll(s, "golang", "Go"))
// "Go Go Go adalah pilihan Go terbaik"

// ReplaceAll dengan string kosong = hapus substring
bersih := strings.ReplaceAll("<b>golang</b>", "<b>", "")
bersih = strings.ReplaceAll(bersih, "</b>", "")
fmt.Println(bersih) // "golang"

NewReplacer — Ganti Banyak Pasangan Sekaligus #

Untuk mengganti banyak pola berbeda dalam satu string, strings.NewReplacer jauh lebih efisien daripada memanggil ReplaceAll berulang kali karena hanya melakukan satu kali scan.

// ANTI-PATTERN: banyak ReplaceAll = banyak scan string
template := "Halo, {nama}! Selamat datang di {kota}."
hasil := strings.ReplaceAll(template, "{nama}", "Budi")
hasil = strings.ReplaceAll(hasil, "{kota}", "Jakarta")

// BENAR: gunakan NewReplacer untuk banyak penggantian
r := strings.NewReplacer(
    "{nama}", "Budi",
    "{kota}", "Jakarta",
    "{tahun}", "2024",
)
fmt.Println(r.Replace(template))
// "Halo, Budi! Selamat datang di Jakarta."

// Replacer bisa digunakan kembali (thread-safe)
pesan1 := r.Replace("Selamat datang, {nama}!")
pesan2 := r.Replace("{nama} dari {kota}")
flowchart LR
    A[String Input] --> B{Berapa banyak\npola diganti?}
    B -- "1 pola" --> C[ReplaceAll]
    B -- "2+ pola berbeda" --> D[NewReplacer]
    C --> E[String Output]
    D --> E

Menghitung dan Mengulang #

Count #

strings.Count menghitung berapa kali sebuah substring muncul dalam string — non-overlapping (tidak tumpang tindih).

s := "golang adalah bahasa yang bagus untuk backend"

fmt.Println(strings.Count(s, "a"))       // 9
fmt.Println(strings.Count(s, "golang"))  // 1
fmt.Println(strings.Count(s, ""))        // len(s)+1 = jumlah rune + 1

// Menghitung baris dalam teks
teks := "baris pertama\nbaris kedua\nbaris ketiga"
jumlahBaris := strings.Count(teks, "\n") + 1
fmt.Println(jumlahBaris) // 3

// Cek apakah string mengandung tepat N kemunculan
func tepat(s, sub string, n int) bool {
    return strings.Count(s, sub) == n
}

strings.Builder — Membangun String Secara Efisien #

Saat kamu perlu membangun string secara iteratif (misalnya dalam loop), jangan gunakan operator + atau += berulang kali karena setiap operasi membuat alokasi string baru.

// ANTI-PATTERN: konkatenasi dalam loop = banyak alokasi
hasil := ""
for i := 0; i < 1000; i++ {
    hasil += fmt.Sprintf("baris %d\n", i)  // 1000 alokasi string baru!
}

// BENAR: gunakan strings.Builder
var sb strings.Builder
for i := 0; i < 1000; i++ {
    fmt.Fprintf(&sb, "baris %d\n", i)  // tulis ke buffer internal
}
hasil := sb.String()  // baru konversi ke string di akhir

API strings.Builder #

var sb strings.Builder

// Menulis ke builder
sb.WriteString("Halo")
sb.WriteRune(',')
sb.WriteByte(' ')
sb.WriteString("Golang!")

fmt.Println(sb.String()) // "Halo, Golang!"
fmt.Println(sb.Len())    // 13

// Reset untuk digunakan ulang
sb.Reset()
fmt.Println(sb.Len()) // 0

// Dengan Grow — pre-alokasi jika tahu ukuran akhir
var sb2 strings.Builder
sb2.Grow(512) // alokasikan 512 byte di awal
sequenceDiagram
    participant Code
    participant Builder
    participant Memory

    Code->>Builder: sb.Grow(512)
    Builder->>Memory: Alokasi buffer 512 byte

    Code->>Builder: sb.WriteString("Halo")
    Builder->>Memory: Tulis ke buffer (tanpa alokasi baru)

    Code->>Builder: sb.WriteString(", Golang!")
    Builder->>Memory: Tulis ke buffer (tanpa alokasi baru)

    Code->>Builder: sb.String()
    Builder-->>Code: String final (satu konversi)

strings.Reader — Membaca String sebagai io.Reader #

strings.NewReader mengubah string menjadi io.Reader. Ini sangat berguna saat sebuah fungsi mengharapkan io.Reader sebagai input, misalnya untuk testing atau passing data ke API yang membutuhkan stream.

import (
    "io"
    "strings"
    "fmt"
)

func prosesReader(r io.Reader) {
    data, _ := io.ReadAll(r)
    fmt.Println(string(data))
}

// Ubah string langsung menjadi Reader
reader := strings.NewReader("ini adalah konten string")
prosesReader(reader) // "ini adalah konten string"

// Berguna untuk testing fungsi yang menerima io.Reader
func TestUpload(t *testing.T) {
    body := strings.NewReader(`{"nama": "golang", "versi": "1.21"}`)
    // kirim body ke fungsi yang membaca io.Reader...
}

// Reader juga mendukung Seek
reader.Seek(0, io.SeekStart) // kembali ke awal

Pola Penggunaan Nyata #

Memahami fungsi satu per satu itu penting, tapi mengetahui bagaimana menggabungkannya dalam konteks nyata jauh lebih berharga. Berikut beberapa pola yang sangat umum ditemukan di aplikasi produksi.

Normalisasi Input Pengguna #

func normalisasiInput(input string) string {
    // 1. Hilangkan whitespace di tepi
    s := strings.TrimSpace(input)
    // 2. Ubah ke lowercase untuk konsistensi
    s = strings.ToLower(s)
    // 3. Ganti spasi ganda menjadi spasi tunggal
    s = strings.Join(strings.Fields(s), " ")
    return s
}

fmt.Println(normalisasiInput("  HALO   DUNIA  ")) // "halo dunia"
fmt.Println(normalisasiInput("\tGolang\n"))         // "golang"

Parsing Header HTTP Sederhana #

func parseHeader(header string) (string, string) {
    // "Content-Type: application/json"
    idx := strings.Index(header, ": ")
    if idx == -1 {
        return header, ""
    }
    return strings.TrimSpace(header[:idx]),
           strings.TrimSpace(header[idx+2:])
}

key, val := parseHeader("Content-Type: application/json")
fmt.Printf("Key: %q, Value: %q\n", key, val)
// Key: "Content-Type", Value: "application/json"

Membuat Slug URL dari Judul #

func buatSlug(judul string) string {
    // Lowercase dulu
    s := strings.ToLower(judul)
    // Ganti spasi dengan tanda hubung
    s = strings.ReplaceAll(s, " ", "-")
    // Hapus karakter yang tidak diinginkan
    var sb strings.Builder
    for _, r := range s {
        if (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9') || r == '-' {
            sb.WriteRune(r)
        }
    }
    // Hapus tanda hubung ganda
    result := sb.String()
    for strings.Contains(result, "--") {
        result = strings.ReplaceAll(result, "--", "-")
    }
    return strings.Trim(result, "-")
}

fmt.Println(buatSlug("Panduan Lengkap Go 1.21!"))
// "panduan-lengkap-go-121"

Membangun Query String #

func buatQueryString(params map[string]string) string {
    parts := make([]string, 0, len(params))
    for k, v := range params {
        parts = append(parts, k+"="+v)
    }
    return strings.Join(parts, "&")
}

qs := buatQueryString(map[string]string{
    "page":  "1",
    "limit": "10",
    "sort":  "created_at",
})
fmt.Println(qs) // "limit=10&page=1&sort=created_at" (urutan map tidak dijamin)

Kapan Beralih ke Alternatif #

Package strings sudah sangat lengkap untuk kebutuhan sehari-hari. Namun ada situasi di mana kamu perlu beralih ke pendekatan lain:

Tetap gunakan package strings jika:
  ✓ Mencari, memecah, atau menggabungkan string dengan pola tetap
  ✓ Transformasi huruf dan pembersihan whitespace
  ✓ Penggantian substring dengan pola literal
  ✓ Membangun string secara iteratif (gunakan strings.Builder)
  ✓ Mengkonversi string ke io.Reader untuk testing

Pertimbangkan package regexp jika:
  ✗ Pola pencarian kompleks dan dinamis (seperti validasi email, format tanggal)
  ✗ Penggantian berdasarkan kelompok capture (misal: ubah "2024-01-15" ke "15/01/2024")
  ✗ Pencarian dengan kondisi yang tidak bisa diekspresikan sebagai string literal

Pertimbangkan golang.org/x/text jika:
  ✗ Perlu menangani Unicode secara mendalam (normalisasi, collation)
  ✗ Title case yang benar untuk berbagai bahasa
  ✗ Perbandingan string yang peka terhadap aturan bahasa (locale-aware)

Pertimbangkan strconv jika:
  ✗ Konversi antara string dan tipe numerik (int, float, bool)
  ✗ Parsing dan formatting angka dengan format tertentu

Ringkasan #

  • strings.Contains, HasPrefix, HasSuffix — pengecekan keberadaan substring; gunakan EqualFold untuk perbandingan case-insensitive tanpa konversi manual.
  • strings.Index dan LastIndex — mengembalikan posisi byte pertama atau terakhir substring; kembalikan -1 jika tidak ditemukan.
  • strings.TrimSpace dan strings.Fields — dua fungsi terpenting untuk membersihkan dan memecah input pengguna; Fields otomatis menangani spasi ganda.
  • strings.Split vs strings.Fields — gunakan Split jika separator tetap dan pasti; gunakan Fields jika memisahkan berdasarkan whitespace apapun.
  • strings.NewReplacer — lebih efisien dari banyak ReplaceAll beruntun karena hanya satu kali scan; aman digunakan concurrent.
  • strings.Builder — wajib digunakan saat membangun string dalam loop; hindari konkatenasi + berulang yang membuat banyak alokasi memori.
  • strings.NewReader — cara idiomatik mengadaptasi string menjadi io.Reader; sangat berguna untuk testing fungsi yang menerima stream.
  • Semua fungsi strings bersifat immutable — tidak ada yang memodifikasi string asli; setiap fungsi selalu mengembalikan nilai baru.

← Sebelumnya: Standard Library   Berikutnya: IO →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact