Godagi satr (string) boshqa istalgan tildagi satr tipiga o'xshab ko'rinadi — toki uni indekslamaguningizcha; o'sha zahoti u sizga belgi o'rniga jimgina bitta bayt tutqazadi, va o'sha bayt xitoy belgisining uchdan biri bo'lib chiqqan birinchi safarda butun model joyiga tushadi. Go satrlari o'zgarmas (immutable), UTF-8 bilan kodlangan baytlar ketma-ketligi — belgilar emas, kod nuqtalari emas, aynan baytlar — va ular bilan ishlashdagi deyarli barcha g'ayrioddiy jihatlar shu ta'rifni so'zma-so'z qabul qilishdan kelib chiqadi.
Go satrlari — o'zgarmas, UTF-8 bilan kodlangan baytlar ketma-ketligi: belgilar emas, kod nuqtalari emas, aynan baytlar.
UTF-8 har bir Unicode kod nuqtasini bir baytdan to'rt baytgacha kodlaydi: oddiy ASCII (kod nuqtalari 0–127, ya'ni asosiy ingliz alifbosi, raqamlar va odatiy tinish belgilarini qamrab oladi) aynan bitta bayt oladi va oddiy ASCII matn bilan bir xil bo'ladi — UTF-8 o'nlab yillik faqat ASCII'ni tushunadigan dasturiy ta'minot bilan orqaga qarab mos kelishining sababi ham shu. é kabi kengaytirilgan lotin belgilari odatda ikki bayt oladi, aksariyat CJK (xitoy/yapon/koreys) belgilari uch bayt oladi, kamroq uchraydigan kod nuqtalari — ba'zi emojilar, ba'zi tarixiy yozuvlar — to'rt bayt oladi. Oldinda kutayotgan barcha kutilmagan holatlarning manbai aynan shu o'zgaruvchan kenglik: satrning bayt uzunligi va belgilar soni faqat har bir belgi tasodifan ASCII bo'lgandagina bir xil bo'ladi; bu test ma'lumotlarida odatiy hol va aynan shu sababli chalg'ituvchi.
Satrning bayt uzunligi va belgilar soni faqat barcha belgilar ASCII bo'lgandagina bir xil bo'ladi.
s := "Hello"
fmt.Println(len(s)) // 5 — har bir belgiga bitta bayt, hammasi ASCII
s = "世界"
fmt.Println(len(s)) // 6 — ikkita 3 baytli belgi
s = "Hello, 世界"
fmt.Println(len(s)) // 13 — 5 + 1 (vergul) + 1 (bo'shliq) + 6
len(s) doim baytlarni sanaydi. Belgilarni (aniqrog'i, Go rune deb ataydigan Unicode kod nuqtalarini) sanash uchun yo rune slice'iga o'tkazing, yoki to'g'ridan-to'g'ri unicode/utf8 paketidan so'rang — ikkinchisi odatda afzal, chunki faqat sonni bilish uchun yangi slice ajratmaydi:
s := "Hello, 世界"
fmt.Println(utf8.RuneCountInString(s)) // 8 — ajratish (allocation) yo'q
fmt.Println(len([]rune(s))) // 8 — javob o'sha, lekin ajratadi
Go matnli ma'lumotga oid uchta o'zaro bog'liq, lekin alohida ko'rinishni beradi, va ish uchun to'g'risini tanlash ham to'g'rilik, ham unumdorlik uchun muhim.
string — sukut bo'yicha siz murojaat qiladigan tip: o'zgarmas, UTF-8 baytlar, uzatish samarali (satr qiymati shunchaki pointer-va-uzunlikdan iborat sarlavha, shuning uchun satrni nusxalash hech qachon asosiy baytlarni nusxalamaydi) va standart kutubxonadagi har bir matn funksiyasi aynan shu tipni kutadi. Uning o'zgarmasligini kelishuv emas, kompilyatorning o'zi ta'minlaydi — xohlasangiz ham s[0] = 'h' deb yozib bo'lmaydi:
s := "Hello"
s[0] = 'h' // kompilyatsiya xatosi: s[0] ga qiymat berib bo'lmaydi
[]byte — o'zgaruvchan, izohlanmagan hamkasbi: xom baytlar slice'i, ularning hatto yaroqli UTF-8 ekani haqida ham hech qanday taxmin yo'q. Bu ikkilik ma'lumotlar, tarmoq kirish-chiqishi (I/O) va tarkibni joyida o'zgartirish kerak bo'lgan har qanday joy uchun kerakli tip:
data := []byte("Hello")
data[0] = 'h' // yaxshi — []byte o'zgaruvchan
s := string(data) // "hello"
[]rune UTF-8 ni alohida Unicode kod nuqtalariga dekodlaydi, ularning har biri int32 sifatida saqlanadi; bu sizga belgi darajasida indekslash va o'zgartirish imkonini beradi, buning evaziga butun satr oldindan dekodlanadi:
runelar := []rune("Hello, 世界")
runelar[7] = '国' // to'g'ri — bayt bo'yicha emas, belgi bo'yicha indekslash
s := string(runelar) // "Hello, 国界"
Bu uch ko'rinish orasidagi har bir o'tkazish — string(byteSlice), []byte(s), []rune(s), string(runeSlice) — asosiy ma'lumotni to'liq nusxalaydi, shuning uchun tezkor sikllarda ularni sababsiz oldinga-orqaga o'giraverish unumdorlikka sezilarli ta'sir qiladi.
Bu uch ko'rinish orasidagi har bir o'tkazish xotirada ma'lumotning yangi nusxasini yaratadi.
Satrni s[i] bilan indekslash i o'rindagi baytni beradi, va ASCII bo'lmagan matn bor har qanday satr uchun baytlarning aksariyat siljishlari umuman biror belgining boshiga to'g'ri kelmaydi:
s := "Hello, 世界"
fmt.Println(s[0]) // 72 — 'H', ASCII bayt
fmt.Println(s[7]) // 228 — '世' ni tashkil etuvchi uch baytning BIRINCHISI, belgining o'zi emas
Aksincha, satr ustida range yurgizish borgan sari UTF-8 ni dekodlaydi va sizga (baytSiljishi, rune) juftliklarini beradi, ko'p baytli ketma-ketliklardan to'g'ri o'tib ketadi:
for i, r := range s {
fmt.Printf("bayt siljishi %d: %c\n", i, r)
}
range siklidagi indeks ko'p baytli belgidan o'tganda birdan ko'proqqa sakraydi — u baribir bayt siljishi, faqat ketma-ket sanoq emas — va aynan shu sababli "satr belgilari ustidan yurish" uchun range to'g'ri standart tanlov, xom indekslash esa faqat sizga aynan baytlar kerak bo'lgandagina to'g'ri (masalan, satr ichiga joylashtirilgan ikkilik formatni tahlil qilishda).
"Satr belgilari ustidan yurish" uchun
rangeto'g'ri standart tanlov; xom indekslash faqat aynan baytlar kerak bo'lgandagina to'g'ri.
Siklda += — boshlovchining klassik unumdorlik xatosi, va uni shunchaki yodlab olib chetlab o'tishdan ko'ra, aynan nima uchun ekanini tushunish foydali. Satrlar o'zgarmas bo'lgani uchun s += "x" s ga qo'shmaydi — u eski mazmun plyus "x" dan iborat butunlay yangi satr ajratadi va eskisini GC ga tashlab ketadi. Buni ming marta qilsangiz, o'sib boruvchi o'lchamdagi taxminan mingta satr ajratib tashlagan bo'lasiz, jami ish O(n²) bo'ladi:
s += "x"sga qo'shmaydi — u butunlay yangi satr ajratadi va eskisini GC ga tashlab ketadi.
var s string
for i := 0; i < 1000; i++ {
s += "x" // har iteratsiyada yangi ajratish
}
strings.Builder buni bitta o'sadigan buferga yozish orqali va faqat oxirida, .String() ni chaqirganingizda, bir marta string hosil qilish orqali bartaraf etadi:
var b strings.Builder
for i := 0; i < 1000; i++ {
b.WriteString("x")
}
s := b.String()
Yakuniy o'lchamni taxminan bilsangiz, b.Grow(n) shuncha sig'imni oldindan ajratadi va bufer o'sayotganda uning o'z ichki qayta ajratishlaridan qochadi — bu slice'ni make([]T, 0, n) bilan oldindan o'lchamlash bilan bir xil g'oya, va yakuniy uzunlik oldindan ma'lum bo'lsa, har doim qilish foydali. bytes.Buffer deyarli xuddi shu ishni bajaradi va strings.Builder dan oldinroq paydo bo'lgan (strings.Builder Go 1.10 da aynan shu andozani ozroq samaraliroq, faqat yozish uchun mo'ljallangan API bilan rasmiylashtirish uchun qo'shilgan); yangi kodda strings.Builder dan foydalaning, faqat sizga bytes.Buffer ning io.Reader/io.Writer ga yaqinroq kengroq interface'i aynan kerak bo'lgandagina bundan chetga chiqing. fmt.Sprintf qulay bo'lsa-da, formatlangan chiqish uchun mos, sof qo'shish uchun esa ikkalasidan o'lchab ko'rsa bo'ladigan darajada sekinroq, chunki u ish vaqtida format satrini tahlil qilishi kerak — uni formatlash kerak bo'lganda ishlating, umumiy qo'shish vositasi sifatida emas.
Yakuniy uzunlik oldindan ma'lum bo'lsa,
b.Grow(n)bilan sig'imni oldindan ajrating.
Satrni kesish (s[0:5]) haqiqatan arzon amal — u o'sha asosiy bayt massiviga qaraydigan yangi satr sarlavhasini hosil qiladi, ma'lumotlarni umuman nusxalamaydi. Bu satrlarning o'zgarmasligining (immutability) yana bir afzalligidir: ulashilgan baytlarni hech kim o'zgartira olmagani uchun aliasing (xotiradagi ma'lumotlarni tasodifan o'zgartirib yuborish) xavfi mutlaqo yo'q:
s := "Hello, World"
sub := s[0:5] // "Hello" — s ning asosiy baytlarini ulashadi, nusxalash yo'q
[]rune ga va orqaga o'tkazish esa, aksincha, doim yangi xotira ajratib nusxalaydi — avval yangi rune slice'iga dekodlaydi, so'ng yangi satrga qayta kodlaydi. Shu sababli unga faqat belgi darajasidagi o'zgartirishlar chindan zarur bo'lgandagina murojaat qiling:
runelar := []rune(s)
sub := string(runelar[0:5]) // haqiqiy yangi nusxa
Kompilyator unumdorlik uchun alohida holat sifatida qaraydigan bitta aniq istisno bor: []byte ni string ga faqat map kaliti sifatida ishlatish uchun o'tkazish, yoki string ni []byte ga faqat w.Write() kabi narsaga uzatish uchun o'tkazish ba'zan nusxalashdan qochishi mumkin — lekin bu kompilyator taniydigan muayyan, tor andozalarning amalga oshirish tafsiloti, umumiy kafolat emas, va kodni shunga tayanib loyihalash kerak emas.
Kesish xotirani ushlab turish jihatidan ham bilish foydali bo'lgan tarzda o'zaro ta'sir qiladi: juda katta satrning bir bo'lagi, garchi bo'lakning o'zi juda kichik bo'lsa ham, u mavjud ekan, butun asosiy massivni tirik ushlab turadi, chunki kesish xotirani nusxalamaydi, ulashadi.
Juda katta satrning kichik bo'lagi ham, u mavjud ekan, butun asosiy massivni tirik ushlab turadi.
func firstLine(s string) string {
return s[:strings.IndexByte(s, '\n')] // s ning hammasini tirik ushlab turadi, qanchalik katta bo'lmasin
}
Agar s juda katta bo'lsa-yu, sizga uzoq muddatga undan faqat kichik bir bo'lak kerak bo'lsa, asl satr GC tomonidan yig'ib olinishi uchun bo'lakni aniq nusxalang:
func firstLine(s string) string {
end := strings.IndexByte(s, '\n')
return string([]byte(s[:end])) // haqiqiy nusxa — endi s ning o'zini yig'ib olish mumkin
}
strings paketi kundalik matn ishlarining aksariyat qismini qoplaydi. Qidirish: strings.Contains, strings.HasPrefix, strings.HasSuffix, strings.Index, strings.LastIndex. O'zgartirish: strings.Replace (sanoq bilan) va strings.ReplaceAll, strings.Trim/TrimSpace/TrimPrefix/TrimSuffix, strings.Split va strings.Join, strings.ToUpper/ToLower (e'tibor bering, strings.Title rasman eskirgan (deprecated) — mahalliy qoidalarni hisobga oladigan to'g'ri bosh harflashtirish uchun golang.org/x/text/cases paketidan foydalaning, chunki sodda yondashuv ko'plab haqiqiy ismlar va iboralarda buziladi).
strings.Contains("hello", "ell") // true
strings.HasPrefix("hello", "he") // true
s := strings.ReplaceAll("hello hello", "l", "L") // "heLLo heLLo"
parts := strings.Split("a,b,c", ",") // []string{"a", "b", "c"}
strings.Join(parts, "-") // "a-b-c"
strings.Split ning Go 1.24 dan beri iteratorga asoslangan hamkasbi bor va uni bilish slices/maps ning iteratorli funksiyalarini bilish bilan bir xil sababga ko'ra foydali: strings.SplitSeq(s, sep) va strings.SplitAfterSeq(s, sep) butun []string ni oldindan qurmasdan, qismlarni for range orqali birma-bir beradi, strings.FieldsSeq(s)/strings.FieldsFuncSeq(s, f) esa bo'shliq va predikat asosidagi bo'lish uchun xuddi shunday qiladi. Har bir bo'lakka faqat bir marta qarash kerak bo'lgan juda katta satr uchun — masalan, katta log faylning qatorlarini har bir qatorni bir vaqtda xotirada ushlab turmasdan skanerlash uchun — iteratorli shakl kiritish o'lchamiga qarab o'sadigan ajratishdan qochadi:
for line := range strings.SplitSeq(hugeCSVLine, ",") {
process(line) // hech qachon barcha maydonlarning []string i bir vaqtda mavjud bo'lmaydi
}
strings.Lines(s) — xuddi shu g'oyaning yangi qatorlar bo'yicha bo'lishning keng tarqalgan holati uchun ixtisoslashtirilgan ko'rinishi: u qatorlarning to'liq slice'ini oldindan ajratmasdan, har bir yangi qator bilan tugaydigan qator ustidan yuradi. Oddiy, o'rtacha o'lchamdagi satrlar uchun strings.Split va natija ustidan oddiy for range mutlaqo idiomatik bo'lib qoladi — Seq shakllariga esa kirish shunchalik kattaki, har bir bo'lakni oldindan hosil qilish kodning aynan qochish foydali bo'lgan qismi bo'lgandagina murojaat qiling. Go 1.27 yana strings.CutLast(s, sep) ni qo'shdi: u ajratgichning oxirgi uchrashi atrofida kesadi — masalan, o'zida nuqtalar bo'lishi mumkin bo'lgan fayl nomidan kengaytmani ajratib olish uchun foydali, bunda strings.Cut (birinchi uchrashuv bo'yicha bo'ladi) juda erta to'xtab qolardi.
Oddiy ASCII taxminlaridan tashqari, Unicode'ni hisobga oladigan ishlov uchun unicode belgi sinflari testlarini (unicode.IsLetter, IsDigit, IsSpace va ularning registrni hisobga oladigan qarindoshlari) beradi, unicode/utf8 esa range va []rune o'tkazishlari ichki tomondan tayanadigan quyi darajadagi kodlash/dekodlash/tekshirish primitivlarini beradi — ayniqsa utf8.ValidString dasturingizning ishonch chegarasidan tashqaridan kelgan har qanday matnda chaqirish foydali, chunki noto'g'ri shakllangan bayt ketma-ketliklari yaroqli []byte bo'ladi, lekin yaroqli matn emas:
if !utf8.ValidString(input) {
return fmt.Errorf("yaroqsiz UTF-8 kiritish")
}
Goda satrlarni solishtirish (s1 == s2) to'g'ridan-to'g'ri bayt-bayt taqqoslashdir; bu tez, lekin har doim ham inson "bir xil matn" deb ataydigan narsaga mos kelmaydi: Unicode ba'zi belgilarni bir necha, baytlari jihatidan farqli usullar bilan tasvirlashga ruxsat beradi. é belgisi bitta oldindan tuzilgan (precomposed) kod nuqtasi bo'lishi mumkin, yoki e va undan keyingi alohida birlashtiruvchi urg'u belgisi bo'lishi mumkin — tashqi ko'rinishi bir xil, ma'nosi bir xil, lekin bayt ketma-ketliklari farqli:
s1 := "café" // é bitta oldindan tuzilgan kod nuqtasi sifatida
s2 := "café" // e + birlashtiruvchi o'tkir urg'u belgisi
fmt.Println(s1 == s2) // false — baytlar boshqa, ko'rsatiladigan matn bir xil
Bu sizning dasturingiz o'zi hosil qilgan ma'lumotlar bilan kamdan-kam muammo tug'diradi, lekin foydalanuvchi kiritishi, fayl nomlari va tashqi tizimlardan kelgan matn bilan haqiqiy xavf: agar ishonchsiz manbalardan kelgan matnni solishtirayotgan yoki takrorlanishlarini olib tashlayotgan bo'lsangiz, avval golang.org/x/text/unicode/norm bilan normallashtirish qo'shimcha bog'liqlikni oqlaydi.
Ishonchsiz manbalardan kelgan matnni solishtirishdan yoki takrorlanishlarini olib tashlashdan oldin uni normallashtiring.
Bir nechtasini qo'lda bajarib ko'rish bayt/rune farqini o'zlashtirishning, u haqda istalgancha o'qishdan ko'ra, yaxshiroq usuli. Satrni to'g'ri teskari aylantiradigan funksiya yozing — bayt-bayt emas, belgi-belgi, shunda ko'p baytli belgilar butun qoladi. Satrdagi so'zlarni bo'shliq bo'yicha ajratib sanang. Satr palindrom ekanini registr va harf-raqam bo'lmagan belgilarni e'tiborsiz qoldirib tekshiring. Satrni ko'p baytli ketma-ketlikni yarmida bo'lib qo'ymasdan, ko'pi bilan N ta belgigacha (bayt emas) qisqartiring — bu satrni qisqartirish kodidagi haqiqiy keng tarqalgan xato bo'lib, faqat ASCII bo'lmagan kiritishda namoyon bo'ladi. Elektron pochta manzilidan domenni ajratib oling. Va qiyinroq mashq sifatida, satrdan takrorlanuvchi belgilarni ularning birinchi uchrash tartibini saqlagan holda olib tashlang.
Satrlar oddiy ko'rinadi, chunki sintaksis oddiy — bir juft qo'shtirnoq, standart kutubxonaning bir nechta funksiyasi. Nozikligi to'liq bayt/rune/belgi farqida, va bu farq ikkinchi tabiatga aylangach, Goning matn bilan ishlash API'sining qolgan qismi o'z-o'zidan joyiga tushadi. Keyingisi — map'lar, va ularda ham xuddi shunday bitta o'tkir jihat bor, uni erta o'rganish foydali: chindan yo'q kalit bilan nol qiymatli holda mavjud kalit orasidagi farq.