Chuỗi (string) trong Go là một dãy byte bất biến, thường là văn bản UTF-8. Các toán tử làm việc trực tiếp trên chuỗi (+, ==, <, truy cập theo chỉ số, cắt chuỗi), còn mọi thứ khác nằm trong package chuẩn strings.
Tách chuỗi
| Hàm | Tách tại | Ghi chú |
|---|---|---|
Split(s, sep) | mọi sep | giữ phần rỗng; Split("", ",") trả về [""], một chuỗi rỗng |
SplitN(s, sep, n) | tối đa n phần | phần cuối chứa toàn bộ phần còn lại |
SplitAfter(s, sep) | mọi sep | giữ dấu phân cách ở cuối mỗi phần |
Fields(s) | các đoạn khoảng trắng | không bao giờ trả về phần rỗng |
FieldsFunc(s, f) | các ký tự mà f trả về true | dấu phân cách tùy chỉnh |
Cut(s, sep) | chỉ sep đầu tiên | trả về phần trước, phần sau, có tìm thấy hay không (Go 1.18) |
Trường hợp Split("", ",") hay làm người ta vấp: đầu vào rỗng cho ra slice độ dài 1, không phải 0. Khi tách dữ liệu người dùng nhập, dùng Fields hoặc kiểm tra s == "" trước để tránh một phần tử rỗng ma.
strings.Cut là cách gọn nhất để tách key=value, user@host và các cặp tương tự. Go 1.24 còn thêm phiên bản iterator, strings.SplitSeq và strings.FieldsSeq, trả về từng phần một mà không dựng slice.
Tìm kiếm
| Hàm | Trả về |
|---|---|
Contains(s, sub) | true nếu sub xuất hiện trong s |
ContainsAny(s, chars) | true nếu có ký tự bất kỳ của chars xuất hiện |
HasPrefix(s, p) / HasSuffix(s, p) | true nếu s bắt đầu hoặc kết thúc bằng p |
Index(s, sub) | vị trí byte của kết quả khớp đầu tiên, hoặc -1 |
LastIndex(s, sub) | vị trí byte của kết quả khớp cuối cùng, hoặc -1 |
Count(s, sub) | số kết quả khớp không chồng lên nhau |
EqualFold(a, b) | so sánh bằng nhau không phân biệt hoa thường |
Mọi phép tìm kiếm đều phân biệt hoa thường, trừ EqualFold. Để tìm không phân biệt hoa thường, hãy đổi cả hai bên sang chữ thường hoặc dùng EqualFold khi so sánh cả chuỗi.
Biến đổi chuỗi
Một cái bẫy: Trim, TrimLeft và TrimRight nhận một tập ký tự, không phải tiền tố. strings.TrimLeft("abcab", "ab") bỏ mọi a và b ở đầu và trả về "cab". Để bỏ đúng một tiền tố hay hậu tố, hãy dùng TrimPrefix và TrimSuffix.
Nối chuỗi và strings.Builder
+ nối các chuỗi, còn fmt.Sprintf định dạng các giá trị thành một chuỗi. Cả hai đều cấp phát chuỗi mới mỗi lần, vì chuỗi là bất biến. Với vài thao tác thì không sao, nhưng trong vòng lặp sẽ chậm, vì mỗi lần += sao chép mọi thứ đã ghép được. Hãy dùng strings.Builder:
Một strings.Builder ở giá trị zero là dùng được ngay. Nó cài đặt io.Writer, nên fmt.Fprintf ghi thẳng vào nó. Gọi b.Grow(n) trước nếu bạn biết đại khái kết quả lớn cỡ nào. Đừng sao chép một Builder sau khi đã ghi vào nó; hãy truyền con trỏ.
Chuỗi nhiều dòng và chuỗi raw
Go có hai loại literal chuỗi:
Chuỗi trong dấu nháy kép xử lý ký tự thoát (\n, \t, \", \\, \u00e9) và phải nằm trên một dòng. Chuỗi raw trong dấu backquote giữ nguyên mọi thứ, kể cả xuống dòng, nên đây là cách viết chuỗi nhiều dòng, biểu thức chính quy, JSON và đường dẫn Windows. Chuỗi raw không thể chứa dấu backquote, và ký tự carriage return bên trong bị loại bỏ. Lưu ý câu truy vấn ở trên bắt đầu bằng một dòng trống, vì literal bắt đầu ngay sau dấu backquote mở.
Độ dài, chỉ số và chuỗi con
len(s) là số byte. s[i] là một byte, và s[i:j] là chuỗi con theo vị trí byte:
ï và é mỗi ký tự chiếm hai byte, nên chuỗi dài 12 byte và có 10 ký tự. Cắt theo byte là an toàn miễn là vị trí lấy từ strings.Index hoặc hàm tương tự, vốn trả về vị trí byte nằm đúng ranh giới ký tự. Để lấy "n ký tự đầu tiên", hãy chuyển sang []rune trước. Trang rune và byte giải thích UTF-8 chi tiết.
Chuỗi là bất biến
Bạn không thể sửa một byte tại chỗ: s[0] = 'H' là lỗi biên dịch (cannot assign to s[0] (neither addressable nor a map index expression)). Mọi hàm trong strings đều trả về chuỗi mới. Để sửa, hãy chuyển sang []byte hoặc []rune, thay đổi slice, rồi chuyển ngược lại:
b := []byte("hello")
b[0] = 'H'
s := string(b) // "Hello"
Tính bất biến cũng là lý do chuỗi con rất rẻ: s[2:5] dùng chung bộ nhớ với s thay vì sao chép. Mặt trái là một chuỗi con nhỏ của một chuỗi khổng lồ sẽ giữ cả chuỗi khổng lồ đó trong bộ nhớ. Hãy dùng strings.Clone (Go 1.20) khi bạn giữ lại một đoạn ngắn của một đầu vào lớn.
So sánh chuỗi
== và != so sánh nội dung, còn <, > so sánh từng byte, tức là theo thứ tự bảng chữ cái với ASCII. strings.Compare có tồn tại nhưng hiếm khi cần; slices.Sort trên []string dùng <. Để sắp xếp văn bản không phải tiếng Anh theo quy tắc ngôn ngữ, hãy dùng golang.org/x/text/collate.
Chuyển đổi từ kiểu khác
Số được chuyển thành chuỗi bằng strconv.Itoa, strconv.FormatFloat hoặc fmt.Sprintf, còn chuỗi thành số bằng strconv.Atoi và strconv.ParseFloat. Đừng viết string(42): nó tạo ra ký tự có code point 42, tức "*". Trang về chuyển đổi kiểu có bảng đầy đủ.
Câu hỏi thường gặp
Làm sao tách chuỗi trong Go?
Dùng strings.Split(s, sep): strings.Split("a,b,c", ",") trả về ["a" "b" "c"]. Để tách theo mọi khoảng trắng liên tiếp và bỏ phần rỗng, dùng strings.Fields(s). strings.SplitN giới hạn số phần, còn strings.Cut tách một lần quanh dấu phân cách đầu tiên.
Làm sao viết chuỗi nhiều dòng trong Go?
Dùng literal chuỗi raw trong dấu backquote:
query := `SELECT id, name
FROM users
WHERE active = true`
Mọi thứ giữa hai dấu backquote được giữ nguyên văn, kể cả xuống dòng và dấu gạch chéo ngược. Chuỗi raw không thể chứa dấu backquote.
Làm sao kiểm tra chuỗi có chứa chuỗi con trong Go?
strings.Contains(s, substr) trả về một bool. Các hàm liên quan: strings.HasPrefix, strings.HasSuffix, strings.Index (vị trí hoặc -1), strings.ContainsAny (bất kỳ ký tự nào trong một tập), và strings.EqualFold để so sánh bằng nhau không phân biệt hoa thường.
Cách nối chuỗi nhanh nhất trong Go là gì?
Với vài chuỗi, + là đủ. Khi ghép chuỗi trong vòng lặp, hãy dùng strings.Builder: gọi WriteString nhiều lần và String() một lần ở cuối. += lặp lại trong vòng lặp sao chép toàn bộ chuỗi mỗi lần, nên càng chậm khi chuỗi càng dài. Để nối một slice với dấu phân cách, dùng strings.Join.
Vì sao len trả về độ dài chuỗi sai?
len(s) trả về số byte, không phải số ký tự. Chuỗi Go là UTF-8, trong đó ký tự ngoài ASCII chiếm từ 2 đến 4 byte, nên len("héllo") là 6. Dùng utf8.RuneCountInString(s) để lấy số code point Unicode.