GoDasturchi
Under the Hood

Under the Hood

Ichki tuzilishi

Slice aslida nima

Slice — ichki tomondan massivdan foydalanadigan kichik ma'lumotlar tuzilmasi. Uning uchta maydoni bor:

  • pointer: tub massiv qayerda joylashgani
  • length: slice nechta element ushlab turgani (len)
  • capacity: tub massivda nechta joy (slot) borligi (cap)

make sizga uzunlik va sig'imni oldindan belgilash imkonini beradi:

example.go
// make([]string, uzunlik, sig'im)
// 2 joyli tub massiv ajratadi, slice bo'sh boshlanadi
logBuffer := make([]string, 0, 2)

fmt.Println(len(logBuffer)) // 0
fmt.Println(cap(logBuffer)) // 2

logBuffer = append(logBuffer, "boot")

fmt.Println(len(logBuffer)) // 1
fmt.Println(cap(logBuffer)) // 2: bir xil tub massiv, 1 joy bo'sh

cap - lenzaxira joy (headroom): tub massivda nechta bo'sh joy qolgani. "boot" zaxira joyga sig'di, shuning uchun Go uni mavjud massivga yozdi va faqat uzunlik o'zgardi.

Append nima qiladi

Zaxira joydan oshib ketib append qilsangiz, boshqacha narsa sodir bo'ladi:

example.go
logBuffer = append(logBuffer, "ready", "listening")
fmt.Println(cap(logBuffer)) // 4

"listening" sig'madi, shuning uchun Go kattaroq massiv ajratdi (eski sig'imni taxminan ikki barobar qildi: 2 dan 4 ga), mavjud qatorlarni nusxaladi va yangi massivga ishora qiluvchi slicani qaytardi. Shuning uchun appendni qaytadan belgilaysiz, va qayta ajratish (reallocation) sodir bo'lganini shundan bilasiz: cap o'zgargan.

Qayta ajratish — bu ajratish plyus to'liq nusxalash. Qancha ma'lumot kelishini bilsangiz, buferni oldindan o'lchamlang, shunda har bir append zaxira joyga tushadi:

example.go
batch := make([]string, 0, 1024) // bitta ajratish, 1024 qator uchun joy

Butun slicani qo'shish

append alohida qiymatlarni qabul qiladi, slicani emas. Bir slicening har bir elementini boshqasiga qo'shish uchun uni ... bilan yoying:

example.go
logBuffer = append(logBuffer, newLines...)

Nuqtalarsiz, Go newLinesni bitta element sifatida qo'shishga urinadi va kompilyatsiya qilishdan bosh tortadi.

Qayta ajratish ulashishni buzadi

Slice Surgery darsi sub-slicelar tub massivni ulashishini ko'rsatdi. Qayta ajratish aynan shu ulashishni buzadi.

Bir xil massiv ustidagi sub-slicedan boshlaymiz:

example.go
buffer := []string{"boot", "ready", "listening"} // len 3, cap 3
recent := buffer[:2]                              // [boot ready], bir xil massiv, len 2, cap 3

recent 1-indeksda tugaydi, lekin uning tub massivida uchinchi joy bor: "listening"ni ushlab turgan joy. recentga append qilish o'sha joyga yozadi va uni qayta yozib qo'yadi:

example.go
recent = append(recent, "request")
fmt.Println(buffer) // [boot ready request]

Endi recent to'lgan. Yana bitta append uning sig'imidan oshib ketadi, shuning uchun Go recent uchun alohida yangi massiv ajratadi. Shu paytdan boshlab, recent yangi massivga, buffer esa hali ham eskisiga ishora qiladi:

example.go
recent = append(recent, "shutdown")
recent[0] = "restart"
fmt.Println(buffer) // [boot ready request], o'zgarishsiz

Qayta ajratishdan oldin, recent orqali append qilish bufferning ma'lumotini qayta yozib qo'yardi. Undan keyin, hatto recent[0]ga to'g'ridan-to'g'ri yozish ham bufferga tegmaydi. Ko'chish sodir bo'lganda hech qanday ogohlantirish bo'lmaydi: o'zgargan cap — yagona belgi.

>_ Exercise

newLinesni logBufferga qo'shing va tub massivga nima bo'lganini xabar qiling.

example.go
func AddLogs(logBuffer []string, newLines []string) (oldCap, newCap int, resized bool)

Qaytaring:

  • oldCap: append qilishdan oldingi sig'im
  • newCap: append qilishdan keyingi sig'im
  • resized: agar Go yangi tub massiv ajratgan bo'lsa true

Agar newLines bo'sh bo'lsa, cap(logBuffer), cap(logBuffer), false qaytaring.

Misollar:

Joy yetarli, qayta ajratish yo'q:

example.go
spacious := make([]string, 0, 10)
AddLogs(spacious, []string{"boot", "ready", "listening"})    (10, 10, false)

Sig'im tugadi, Go kattaroq massiv ajratdi:

example.go
tight := make([]string, 0, 2)
AddLogs(tight, []string{"boot", "ready", "listening"})    (2, 4, true)

Aniq newCap qiymati Go'ning runtime'iga bog'liq. Muhimi — uning oldCapdan kattaligi, shundan qayta ajratish sodir bo'lganini bilasiz.

Stuck? Reveal a hint to help you.

Hints (0/3)

Key Takeaway

Key Takeaway:

Zaxira joyga sig'adigan append mavjud massivga yozadi va faqat lenni o'zgartiradi. Sig'imdan oshib ketadigan append Go'ga yangi massiv ajratib, hammasini nusxalashga majbur qiladi, va o'zgargan cap shundan dalolat beradi.

NEXT UP

The Lookup Table

OUTPUT

$ go run main.go
Kodingizni ishga tushiring

Under the Hood

Ichki tuzilishi

Slice aslida nima

Slice — ichki tomondan massivdan foydalanadigan kichik ma'lumotlar tuzilmasi. Uning uchta maydoni bor:

  • pointer: tub massiv qayerda joylashgani
  • length: slice nechta element ushlab turgani (len)
  • capacity: tub massivda nechta joy (slot) borligi (cap)

make sizga uzunlik va sig'imni oldindan belgilash imkonini beradi:

example.go
// make([]string, uzunlik, sig'im)
// 2 joyli tub massiv ajratadi, slice bo'sh boshlanadi
logBuffer := make([]string, 0, 2)

fmt.Println(len(logBuffer)) // 0
fmt.Println(cap(logBuffer)) // 2

logBuffer = append(logBuffer, "boot")

fmt.Println(len(logBuffer)) // 1
fmt.Println(cap(logBuffer)) // 2: bir xil tub massiv, 1 joy bo'sh

cap - lenzaxira joy (headroom): tub massivda nechta bo'sh joy qolgani. "boot" zaxira joyga sig'di, shuning uchun Go uni mavjud massivga yozdi va faqat uzunlik o'zgardi.

Append nima qiladi

Zaxira joydan oshib ketib append qilsangiz, boshqacha narsa sodir bo'ladi:

example.go
logBuffer = append(logBuffer, "ready", "listening")
fmt.Println(cap(logBuffer)) // 4

"listening" sig'madi, shuning uchun Go kattaroq massiv ajratdi (eski sig'imni taxminan ikki barobar qildi: 2 dan 4 ga), mavjud qatorlarni nusxaladi va yangi massivga ishora qiluvchi slicani qaytardi. Shuning uchun appendni qaytadan belgilaysiz, va qayta ajratish (reallocation) sodir bo'lganini shundan bilasiz: cap o'zgargan.

Qayta ajratish — bu ajratish plyus to'liq nusxalash. Qancha ma'lumot kelishini bilsangiz, buferni oldindan o'lchamlang, shunda har bir append zaxira joyga tushadi:

example.go
batch := make([]string, 0, 1024) // bitta ajratish, 1024 qator uchun joy

Butun slicani qo'shish

append alohida qiymatlarni qabul qiladi, slicani emas. Bir slicening har bir elementini boshqasiga qo'shish uchun uni ... bilan yoying:

example.go
logBuffer = append(logBuffer, newLines...)

Nuqtalarsiz, Go newLinesni bitta element sifatida qo'shishga urinadi va kompilyatsiya qilishdan bosh tortadi.

Qayta ajratish ulashishni buzadi

Slice Surgery darsi sub-slicelar tub massivni ulashishini ko'rsatdi. Qayta ajratish aynan shu ulashishni buzadi.

Bir xil massiv ustidagi sub-slicedan boshlaymiz:

example.go
buffer := []string{"boot", "ready", "listening"} // len 3, cap 3
recent := buffer[:2]                              // [boot ready], bir xil massiv, len 2, cap 3

recent 1-indeksda tugaydi, lekin uning tub massivida uchinchi joy bor: "listening"ni ushlab turgan joy. recentga append qilish o'sha joyga yozadi va uni qayta yozib qo'yadi:

example.go
recent = append(recent, "request")
fmt.Println(buffer) // [boot ready request]

Endi recent to'lgan. Yana bitta append uning sig'imidan oshib ketadi, shuning uchun Go recent uchun alohida yangi massiv ajratadi. Shu paytdan boshlab, recent yangi massivga, buffer esa hali ham eskisiga ishora qiladi:

example.go
recent = append(recent, "shutdown")
recent[0] = "restart"
fmt.Println(buffer) // [boot ready request], o'zgarishsiz

Qayta ajratishdan oldin, recent orqali append qilish bufferning ma'lumotini qayta yozib qo'yardi. Undan keyin, hatto recent[0]ga to'g'ridan-to'g'ri yozish ham bufferga tegmaydi. Ko'chish sodir bo'lganda hech qanday ogohlantirish bo'lmaydi: o'zgargan cap — yagona belgi.

>_ Exercise

newLinesni logBufferga qo'shing va tub massivga nima bo'lganini xabar qiling.

example.go
func AddLogs(logBuffer []string, newLines []string) (oldCap, newCap int, resized bool)

Qaytaring:

  • oldCap: append qilishdan oldingi sig'im
  • newCap: append qilishdan keyingi sig'im
  • resized: agar Go yangi tub massiv ajratgan bo'lsa true

Agar newLines bo'sh bo'lsa, cap(logBuffer), cap(logBuffer), false qaytaring.

Misollar:

Joy yetarli, qayta ajratish yo'q:

example.go
spacious := make([]string, 0, 10)
AddLogs(spacious, []string{"boot", "ready", "listening"})    (10, 10, false)

Sig'im tugadi, Go kattaroq massiv ajratdi:

example.go
tight := make([]string, 0, 2)
AddLogs(tight, []string{"boot", "ready", "listening"})    (2, 4, true)

Aniq newCap qiymati Go'ning runtime'iga bog'liq. Muhimi — uning oldCapdan kattaligi, shundan qayta ajratish sodir bo'lganini bilasiz.

Stuck? Reveal a hint to help you.

Hints (0/3)

Key Takeaway

Key Takeaway:

Zaxira joyga sig'adigan append mavjud massivga yozadi va faqat lenni o'zgartiradi. Sig'imdan oshib ketadigan append Go'ga yangi massiv ajratib, hammasini nusxalashga majbur qiladi, va o'zgargan cap shundan dalolat beradi.

NEXT UP

The Lookup Table