0

Go Zero to Hero - Bài 47: Thao tác với chuỗi: Package strings

Trong quá trình xây dựng các ứng dụng backend, việc thao tác, kiểm tra và chuẩn hóa dữ liệu dạng chuỗi (string) diễn ra với tần suất chóng mặt. Thay vì tự viết các hàm xử lý thủ công tốn thời gian, Go cung cấp sẵn package chuẩn strings với một bộ công cụ cực kỳ mạnh mẽ và tối ưu.


1. Tìm kiếm và Kiểm tra (Search & Validate)

Khi nhận dữ liệu từ các thiết bị ngoại vi hoặc client, việc đầu tiên là phải kiểm tra xem chuỗi đó có đúng định dạng hay chứa từ khóa cần thiết không.

  • strings.Contains(): Kiểm tra một chuỗi con có nằm trong chuỗi lớn không.
  • strings.HasPrefix(): Kiểm tra chuỗi có BẮT ĐẦU bằng một từ khóa không.
  • strings.HasSuffix(): Kiểm tra chuỗi có KẾT THÚC bằng một từ khóa không.
package main

import (
    "fmt"
    "strings"
)

func main() {
    rawLog := "ERR_503: Máy bán vé TVM_01 mất kết nối mạng LAN"
    ticketID := "AFC-2026-998877.csv"

    // 1. Kiểm tra sự tồn tại (Contains)
    if strings.Contains(rawLog, "TVM_01") {
        fmt.Println("Phát hiện lỗi liên quan đến máy TVM_01!")
    }

    // 2. Kiểm tra tiền tố (HasPrefix)
    if strings.HasPrefix(ticketID, "AFC-") {
        fmt.Println("Đây là mã vé hợp lệ của hệ thống AFC.")
    }

    // 3. Kiểm tra hậu tố (HasSuffix)
    if strings.HasSuffix(ticketID, ".csv") {
        fmt.Println("File dữ liệu định dạng CSV.")
    }
}

2. Cắt và Nối chuỗi (Split & Join)

Đây là bộ đôi phép thuật được dùng nhiều nhất khi parse dữ liệu từ file văn bản hoặc xử lý các chuỗi query trên URL.

  • strings.Split(chuỗi, ký_tự_ngăn_cách): Băm một chuỗi dài thành một Slice chứa các chuỗi nhỏ.
  • strings.Join(slice, ký_tự_ngăn_cách): Gom một mảng các chuỗi nhỏ lại thành một chuỗi duy nhất.
func main() {
    // Kịch bản: Máy quét thẻ gửi về một chuỗi dữ liệu gộp
    payload := "101|CARD_8899|15000|SUCCESS"

    // CẮT CHUỖI
    // Tách chuỗi thành Slice dựa trên dấu "|"
    parts := strings.Split(payload, "|")
    
    fmt.Printf("Mã trạm: %s\n", parts[0])
    fmt.Printf("Mã thẻ: %s\n", parts[1])
    fmt.Printf("Số tiền: %s\n", parts[2])

    // NỐI CHUỖI
    // Tạo ra câu lệnh log mới
    tags := []string{"[CRITICAL]", "DATABASE", "TIMEOUT"}
    logPrefix := strings.Join(tags, " - ")
    
    fmt.Println(logPrefix) // Kết quả: [CRITICAL] - DATABASE - TIMEOUT
}

3. Dọn dẹp và Chuẩn hóa (Trim & Case Conversion)

Dữ liệu do User nhập vào (hoặc do thiết bị phần cứng quét bị nhiễu) thường xuyên chứa những khoảng trắng vô nghĩa ở đầu và cuối. Nếu bạn so sánh chuỗi mà không dọn dẹp, logic hệ thống sẽ chết đứng.

  • strings.TrimSpace(): Xóa sạch dấu cách (space), dấu tab (\t), hoặc dấu xuống dòng (\n) ở HAI ĐẦU chuỗi.
  • strings.ToUpper() / strings.ToLower(): Ép toàn bộ chuỗi về viết hoa hoặc viết thường để so sánh không phân biệt hoa/thường (Case-insensitive).
func main() {
    dirtyInput := "   AFC-TICKET-001 \n\t "
    
    // 1. Dọn dẹp khoảng trắng
    cleanInput := strings.TrimSpace(dirtyInput)
    fmt.Printf("Dữ liệu sạch: '%s'\n", cleanInput)

    // 2. Chuẩn hóa để so sánh
    userRole := "AdMiN"
    
    // Luôn đưa về LowerCase trước khi kiểm tra quyền
    if strings.ToLower(userRole) == "admin" {
        fmt.Println("Cấp quyền quản trị viên thành công.")
    }
}

4. Thay thế dữ liệu (Replace)

Khi cần làm ẩn dữ liệu nhạy cảm (ẩn mã thẻ tín dụng) hoặc sửa các ký tự lỗi cục bộ, bạn sẽ cần đến hàm Replace.

  • strings.ReplaceAll(chuỗi, cũ, mới): Tìm và thay thế TOÀN BỘ chuỗi cũ bằng chuỗi mới.
  • strings.Replace(chuỗi, cũ, mới, số_lượng): Giống trên nhưng cho phép giới hạn số lần thay thế.
func main() {
    sqlQuery := "SELECT * FROM tickets WHERE status = 'PENDING'"
    
    // Đổi PENDING thành PAID
    updatedQuery := strings.ReplaceAll(sqlQuery, "PENDING", "PAID")
    fmt.Println(updatedQuery)
    
    phone := "090-123-4567"
    // Chỉ thay thế dấu gạch ngang ĐẦU TIÊN
    newPhone := strings.Replace(phone, "-", " ", 1)
    fmt.Println(newPhone) // Kết quả: 090 123-4567
}

5. Tuyệt kỹ Backend: Tối ưu hiệu năng với strings.Builder

Đây là phần định đoạt đẳng cấp của kỹ sư hệ thống.

Như đã nói, chuỗi trong Go là bất biến (immutable). Nếu bạn dùng toán tử + để nối chuỗi bên trong một vòng lặp hàng ngàn lần, Go sẽ phải cấp phát RAM mới hàng ngàn lần. Việc này vắt kiệt bộ nhớ và làm CPU quá tải vì phải đi dọn rác (Garbage Collection).

Để xử lý việc nối chuỗi với cường độ cao, package strings cung cấp cấu trúc Builder. Nó hoạt động như một bộ đệm (buffer) cho phép bạn nhét thêm dữ liệu vào liên tục mà không tạo ra chuỗi mới cho đến khi hoàn tất.

package main

import (
    "fmt"
    "strings"
)

func generateReport() string {
    // Khởi tạo một Builder
    var builder strings.Builder
    
    // Giả lập vòng lặp xử lý 10,000 bản ghi
    for i := 1; i <= 5; i++ {
        // Dùng WriteString để đẩy dữ liệu vào bộ đệm cực nhanh
        builder.WriteString(fmt.Sprintf("Dữ liệu dòng thứ %d\n", i))
    }
    
    // Chỉ tạo ra chuỗi thực sự (ép kiểu sang string) ĐÚNG 1 LẦN ở cuối cùng
    return builder.String()
}

func main() {
    report := generateReport()
    fmt.Println(report)
}

Tổng kết

Package strings là chiếc dao găm (Swiss Army Knife) không thể thiếu khi bạn đụng tay vào bất kỳ luồng dữ liệu text nào:

  1. So khớp: Nhớ dùng Contains, HasPrefix, HasSuffix.
  2. Chế biến: Dùng Split để lấy mảng, Join để gộp, và ReplaceAll để vá dữ liệu.
  3. Làm sạch: Luôn dùng TrimSpace và ToLower trước khi so sánh chuỗi lấy từ Input.
  4. Tối ưu (Đặc biệt quan trọng): Tuyệt đối không dùng toán tử += trong vòng lặp lớn. Luôn sử dụng strings.Builder để bảo vệ RAM của hệ thống.

All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí