日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

go int 最大值_Dig101 - Go之灵活的slice

發布時間:2025/4/5 编程问答 25 豆豆
生活随笔 收集整理的這篇文章主要介紹了 go int 最大值_Dig101 - Go之灵活的slice 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

文章首發:公眾號 newbmiao
Dig101: dig more, simplified more and know more

Slice作為go常用的數據類型,在日常編碼中非常常見。 相對于數組的定長不可變,slice使用起來就靈活了許多。

0x01 slice 到底是什么?

首先我們看下源碼中slice結構的定義

// src/runtime/slice.go type slice struct {array unsafe.Pointerlen intcap int }

slice數據結構如上,Data指向底層引用的數組內存地址, len是已用長度,cap是總容量。 為驗證如上所述,我們嘗試聲明一個slice a,獲取 a的sliceHeader頭信息,并用%p獲取&a, sh, a, a[0]的地址 看看他們的地址是否相同。

a := make([]int, 1, 3) //reflect.SliceHeader 為 slice運行時數據結構 sh := (*reflect.SliceHeader)(unsafe.Pointer(&a)) fmt.Printf("slice header: %#vnaddress of a: %p &a[0]: %p | &a: %p sh:%p ", sh, a, &a[0],&a, sh)//slice header: &reflect.SliceHeader{Data:0xc000018260, Len:1, Cap:3} //address of a: 0xc000018260 &a[0]: 0xc000018260 | &a: 0xc00000c080 sh:0xc00000c080

結果發現a和&a[0]地址相同。 這個好理解,切片指向地址即對應底層引用數組首個元素地址 而&a和sh及sh.Data指向地址相同。這個是因為這三個地址是指slice自身地址。 這里【slice自身地址不同于slice指向的底層數據結構地址】, 清楚這一點對于后邊的一些問題會更容易判斷。

這里作為一個小插曲,我們看下當fmt.Printf("%p",a)時發生了什么 內部調用鏈 fmtPointer -> Value.Pointer 然后根據Pointer方法對應slice的注釋如下

// If v's Kind is Slice, the returned pointer is to the first // element of the slice. If the slice is nil the returned value // is 0. If the slice is empty but non-nil the return value is non-zero.

發現沒,正是我們上邊說的,slice不為空時,返回了第一個元素的地址 有點迷惑性是不是,但其實作為使用slice的我們,更關心的是底層指向的數據不是么。

再一點就是,基于go中所有賦值和參數傳遞都是值傳遞,對于大數組而言,拷貝一個指向他的slice就高效多了 上一篇Go之for-range排坑指南 有過描述, 詳見 0x03 對大數組這樣遍歷有啥問題?

總結下, slice是一個有底層數組引用的結構里,有長度,有容量。

就這么簡單? 不,光這樣還不足以讓它比數組更好用。 slice還支持非常方便的切片操作和append時自動擴容,這讓他更加flexible

0x02 slice能比較么?

答案是【只能和nil比較】

s := make([]int, 5) a := s println(s == a) //invalid operation: s == a (slice can only be compared to nil)

這個也其實好理解,當你比較兩個slice,你是想比較他們自身呢?(必然不同啊,因為有值拷貝) 還是比較他們底層的數組?(那長度和容量也一起比較么) 確實沒有什么意義去做兩個slice的比較。

0x03 花樣的切片操作

slice通過三段參數來操作:x[from:len:cap] 即對x從from索引位置開始,截取len長度,cap大小的新切片返回 但是len和cap不能大于x原來的len和cap 三個參數都可省略,默認為x[0:len(x):cap(x)] 切片操作同樣適用于array 如下都是通過src[:]常規對切片(指向的底層數組)或數組的引用

s:=make([]int,5) x:=s[:]arr:=[5]int{} y:=arr[:]

配合copy和append,slice的操作還有很多,官方wikiSlice Tricks 有更豐富的例子 比如更通用的拷貝 b = append(a[:0:0], a...) 比如cut或delete時增加對不使用指針的nil標記釋放(防止內存泄露)

//Cut copy(a[i:], a[j:]) for k, n := len(a)-j+i, len(a); k < n; k++ {a[k] = nil // or the zero value of T } a = a[:len(a)-j+i]//Delete if i < len(a)-1 {copy(a[i:], a[i+1:]) } a[len(a)-1] = nil // or the zero value of T a = a[:len(a)-1]

不熟悉的話,建議好好練習一下去感受

0x04 append 時發生了什么?

總的來說,append時會按需自動擴容 - 容量足夠,無擴容則直接拷貝待 append 的數據到原 slice 底層指向的數組 之后(原slice的len之后),并返回指向該數組首地址的新slice(len改變) - 容量不夠,有擴容則拷貝原有 slice 所指向部分數據到新開辟的數組,并對待 append 的數據附加到其后,并返回新數組首地址的新slice?(底層數組,len,cap均改變)

如下代碼所示,容量不夠時觸發了擴容重新開辟底層數組,x 和 s 底層指向的數組已不是同一個

s := make([]int, 5) x := append(s, 1) fmt.Printf("x dataPtr: %p len: %d cap: %dns dataPtr: %p len: %d cap: %d", x, len(x), cap(x), s, len(s), cap(s)) // x dataPtr: 0xc000094000 len: 6 cap: 10 // s dataPtr: 0xc000092030 len: 5 cap: 5

0x05 append內部優化

具體查閱源碼,你會發現編譯時將append分為三類并優化

除按需擴容外 - x = append(y, make([]T, y)...) 使用memClr提高初始化效率 - x = append(l1, l2...) 或者 x = append(slice, string) 直接復制l2 - x = append(src, a, b, c) 確定待append數目下,直接做賦值優化

具體編譯優化如下 注釋有簡化,詳見internal/gc/walk.go: append

switch { case isAppendOfMake(r): // x = append(y, make([]T, y)...) will rewrite to// s := l1// n := len(s) + l2// if uint(n) > uint(cap(s)) {// s = growslice(T, s, n)// }// s = s[:n]// lptr := &l1[0]// sptr := &s[0]// if lptr == sptr || !hasPointers(T) {// // growslice did not clear the whole underlying array // (or did not get called)// hp := &s[len(l1)]// hn := l2 * sizeof(T)// memclr(hp, hn)// }//使用memClr提高初始化效率r = extendslice(r, init) case r.IsDDD(): // DDD is ... syntax // x = append(l1, l2...) will rewrite to// s := l1// n := len(s) + len(l2)// if uint(n) > uint(cap(s)) {// s = growslice(s, n)// }// s = s[:n]// memmove(&s[len(l1)], &l2[0], len(l2)*sizeof(T))//直接復制l2r = appendslice(r, init) // also works for append(slice, string). default: // x = append(src, a, b, c) will rewrite to// s := src// const argc = len(args) - 1// if cap(s) - len(s) < argc {// s = growslice(s, len(s)+argc)// }// n := len(s)// s = s[:n+argc]// s[n] = a// s[n+1] = b// ...//確定待append數目下,直接做賦值優化r = walkappend(r, init, n) }

這里關于append實現有幾點可以提下

擴容的策略是什么?

答案是【總的來說是至少返回要求的長度n最大則為翻倍】 具體情況是: - len<1024時2倍擴容 - 大于且未溢出時1.25倍擴容 - 溢出則直接按申請大小擴容 - 最后按mallocgc內存分配大小適配來確定len. (n-2n之間)

?擴容留出最多一倍的余量,主要還是為了減少可能的擴容頻率。? mallocgc內存適配實際是go內存管理做了內存分配的優化, 當然內部也有內存對齊的考慮。 雨痕Go學習筆記第四章內存分配,對這一塊有很詳盡的分析,值得一讀。

至于為啥要內存對齊可以參見Golang 是否有必要內存對齊?,一篇不錯的文章。

擴容判斷中uint的作用是啥?

//n為目標slice總長度,類型int,cap(s)類型也為int if uint(n) > uint(cap(s))s = growslice(T, s, n) }

答案是【為了避免溢出的擴容】

int有正負,最大值math.MaxInt64 = 1<<63 - 1 uint無負數最大值math.MaxUint64 = 1<<64 - 1 uint正值是int正值范圍的兩倍,int溢出了變為負數,uint(n)則必大于原s的cap,條件成立 到growslice內部,對于負值的n會panic,以此避免了溢出的擴容

內存清零初始化: memclrNoHeapPointers vs typedmemclr?

答案是【這個取決于待清零的內存是否已經初始化為type-safe(類型安全)狀態,及類型是否包含指針】

具體來看,memclrNoHeapPointers使用場景是 - 帶清零內存是初始化過的,且不含指針 - 帶清零內存未初始化過的,里邊內容是“垃圾值”(即非type-safe),需要初始化并清零

其他場景就是typedmemclr, 而且如果用于清零的Type(類型)包含指針,他會多一步WriteBarrier(寫屏障),用于為GC(垃圾回收)運行時標記對象的內存修改,減少STW(stop the world)

所以memclrNoHeapPointers第一個使用場景為啥不含指針就不用解釋了。

想了解更多可以看看zero-initialization-versus-zeroing 以及相關源碼的注釋memclrNoHeapPointers和typedmemclr

本文代碼見 NewbMiao/Dig101-Go

歡迎關注我,不定期深挖技術

總結

以上是生活随笔為你收集整理的go int 最大值_Dig101 - Go之灵活的slice的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。