網站首頁 編程語言 正文
數組
go開發者在日常的工作中slice算是用的比較多的了,在介紹slice之前,我們先了解下數組,數組相信大家都不陌生,數組的數據結構比較簡單,它在內存中是連續的。以一個存了10個數字的數組為例來說:
a:=[10]int{0,1,2,3,4,5,6,7,8,9}
它在內存中大概是這樣的:
得益于連續性,所以數組的特點就是:
- 大小固定
- 訪問快,復雜度為O(1);
- 插入和刪除元素因為要移動元素,所以相比查詢會慢。 當我們要訪問一個越界的元素的元素時,go甚至編輯都不通過:
a := [10]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} fmt.Println(a[10]) // invalid array index 10 (out of bounds for 10-element array)
切片
相比數組,go的slice(切片)要相對靈活些,比較大的不同點就是slice的長度可以不固定,創建的時候不用指明長度,在go中slice是一種設計過的數據結構:
type slice struct { array unsafe.Pointer //指針 len int //長度 cap int //容量 }
slice的底層其實還是數組,通過指針指向它底層的數組,len是slice的長度,cap是slice的容量,slice添加元素時,且cap容量不足時,會根據策略擴容。
切片的創建
直接聲明
var s []int
通過直接聲明的slice,它是個nil
slice,它的長度和容量都是0,且不指向任何底層數組,nil切片和空切片是不一樣的,接下來會介紹。
new方式初始化
s:=*new([]int)
new的方式和直接聲明的方式區別不大,最終產出的都是一個nil的slice。
字面量
s1 := []int{0, 1, 2} s2 := []int{0, 1, 2, 4: 4} s3 := []int{0, 1, 2, 4: 4, 5, 6, 9: 9} fmt.Println(s1, len(s1), cap(s1)) //[0 1 2] 3 3 fmt.Println(s2, len(s2), cap(s2)) //[0 1 2 0 4] 5 5 fmt.Println(s3, len(s3), cap(s3)) //[0 1 2 0 4 5 6 0 0 9] 10 10
字面量創建的slice,默認長度和容量是相等的,需要注意的是如果我們單獨指明了某個索引的值,那么在這個索引值前面的元素如果未聲明的話,就會是slice的類型的默認值。
make方式
s := make([]int, 5, 6) fmt.Println(s, len(s), cap(s)) //[0 0 0 0 0] 5 6
通過make可以指定slice的長度和容量。
截取方式
切片可以從數組或者其他切片中截取獲得,這時新的切片會和老的數組或切片共享一個底層數組,不管誰修改了數據,都會影響到底層的數組,但是如果新的切片發生了擴容,那么底層的數組就不是同一個。
s[:]
a := []int{0, 1, 2, 3, 4} b := a[:] fmt.Println(b, len(b), cap(b)) //[0 1 2 3 4] 5 5
通過:
獲取 [0,len(a)-1]
的切片,等同于整個切片的引用。
s[i:]
a := []int{0, 1, 2, 3, 4} b := a[1:] fmt.Println(b, len(b), cap(b)) //[1 2 3 4] 4 4
通過指定切片的開始位置來獲取切片,它是左閉的包含左邊的元素,此時它的容量cap(b)=cap(a)-i
。這里要注意界限問題,a[5:]
的話,相當于走到數組的尾巴處,什么元素也沒了,此時就是個空切片,但是如果你用a[6:]
的話,那么就會報錯,超出了數組的界限。
a := []int{0, 1, 2, 3, 4} b := a[5:] //[] c := a[6:] //runtime error: slice bounds out of range [6:5]
c雖然報錯了,但是它只是運行時報錯,編譯還是能通過的。
s[:j]
a := []int{0, 1, 2, 3, 4} b := a[:4] fmt.Println(b, len(b), cap(b)) //[0 1 2 3] 4 5
獲取[0-j)
的數據,注意右邊是開區間,不包含j,同時它的cap和j沒關系,始終是cap(b) = cap(a)
,同樣注意不要越界。
s[i:j]
a := []int{0, 1, 2, 3, 4} b := a[2:4] fmt.Println(b, len(b), cap(b)) //[2 3] 2 3
獲取[i-j)
的數據,注意右邊是開區間,不包含j,它的cap(b) = cap(a)-i
。
s[i:j:x]
a := []int{0, 1, 2, 3, 4} b := a[1:2:3] fmt.Println(b, len(b), cap(b)) //[1] 1 2
通過上面的例子,我們可以發現切片b的cap其實和j沒什么關系,和i存在關聯,不管j是什么,始終是cap(b)=cap(a)-i
,x
的出現可以修改b的容量,當我們設置x后,cap(b) = x-i
而不再是cap(a)-i
了。
看個例子
s0 := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} s1 := s0[3:6] //[3 4 5] 3 7
s1是對s0的切片,所以它們大概是這樣:
s2 := s1[1:3:4]
這時指定個s2,s2是對s1的切片,并且s2的len=2,cap=3,所以大概長這樣:
s1[1] = 40 fmt.Println(s0, s1, s2)// [0 1 2 3 40 5 6 7 8 9] [3 40 5] [40 5]
這時把s1[1]修改成40,因為沒有涉及到擴容,s0、s1、s2重疊部分都指向同一個底層數組,所以最終發現s0、s2對應的位置都變成了40。
s2 = append(s2, 10) fmt.Println(s2, len(s2), cap(s2)) //[40 5 10] 3 3
再向s2中添加一個元素,因為s2還有一個空間,所以不用發生擴容。
s2 = append(s2, 11) fmt.Println(s2, len(s2), cap(s2)) //[40 5 10 11] 4 6
繼續向s2中添加一個元素,此時s2已經沒有空間了,所以會觸發擴容,擴容后指向一個新的底層數據,和原來的底層數組解耦了。
此時無論怎么修改s2都不會影響到s1和s2。
切片的擴容
slice的擴容主要通過growslice
函數上來處理的:
func growslice(et *_type, old slice, cap int) slice { .... newcap := old.cap doublecap := newcap + newcap if cap > doublecap { newcap = cap } else { if old.len < 1024 { newcap = doublecap } else { // Check 0 < newcap to detect overflow // and prevent an infinite loop. for 0 < newcap && newcap < cap { newcap += newcap / 4 } // Set newcap to the requested cap when // the newcap calculation overflowed. if newcap <= 0 { newcap = cap } } } .... return slice{p, old.len, newcap} }
入參說明下:
-
et
是slice的類型。 -
old
是老的slice。 -
cap
是擴容后的最低容量,比如原來是4,append加了一個,那么cap就是5。 所以上面的代碼解釋為: - 如果擴容后的最低容量大于老的slice的容量的2倍,那么新的容量等于擴容后的最低容量。
- 如果老的slice的長度小于1024,那么新的容量就是老的slice的容量的2倍
- 如果老的slice的長度大于等于1024,那么新的容量就等于老的容量不停的1.25倍,直至大于擴容后的最低容量。 這里需要說明下關于slice的擴容網上很多文章都說小于1024翻倍擴容,大于1024每次1.25倍擴容,其實就是基于這段代碼,但其實這不全對,我們來看個例子:
a := []int{1, 2} fmt.Println(len(a), cap(a)) //2 2 a = append(a, 2, 3, 4) fmt.Println(len(a), cap(a)) // 5 6
按照規則1,這時的cap應該是5,結果是6。
a := make([]int, 1280, 1280) fmt.Println(len(a), cap(a)) //1280 1280 a = append(a, 1) fmt.Println(len(a), cap(a), 1280*1.25) //1281 1696 1600
按照規則3,這時的cap應該是原來的1.25倍,即1600,結果是1696。
內存對齊
其實上面兩個擴容,只能說不是最終的結果,go還會做一些內存對齊的優化,通過內存對齊可以提升讀取的效率。
// 內存對齊 capmem, overflow = math.MulUintptr(et.size, uintptr(newcap)) capmem = roundupsize(capmem) newcap = int(capmem / et.size)
空切片和nil切片
空切片:slice的指針不為空,len和cap都是0
nil切片:slice的指針不指向任何地址即array=0,len和cap都是0
nil | 空 |
---|---|
var a []int | a:=make([]int,0) |
a:=*new([]int) | a:=[]int{} |
空切片雖然地址不為空,但是這個地址也不代表任何底層數組的地址,空切片在初始化的時候會指向一個叫做zerobase
的地址,
var zerobase uintptr if size == 0 { return unsafe.Pointer(&zerobase) }
所有空切片的地址都是一樣的。
var a1 []int a2:=*new([]int) a3:=make([]int,0) a4:=[]int{} fmt.Println(*(*[3]int)(unsafe.Pointer(&a1))) //[0 0 0] fmt.Println(*(*[3]int)(unsafe.Pointer(&a2))) //[0 0 0] fmt.Println(*(*[3]int)(unsafe.Pointer(&a3))) //[824634101440 0 0] fmt.Println(*(*[3]int)(unsafe.Pointer(&a4))) //[824634101440 0 0]
數組是值傳遞,切片是引用傳遞?
func main() { array := [10]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} slice := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} changeArray(array) fmt.Println(array) //[0 1 2 3 4 5 6 7 8 9] changeSlice(slice) fmt.Println(slice) //[1 1 2 3 4 5 6 7 8 9] } func changeArray(a [10]int) { a[0] = 1 } func changeSlice(a []int) { a[0] = 1 }
- 定義一個數組和一個切片
- 通過changeArray改變數組下標為0的值
- 通過changeSlice改變切片下標為0的值
- 原數組值未被修改,原切片的值已經被修改 這個表象看起來像是slice是指針傳遞似的,但是如果我們這樣呢:
func main() { slice := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} changeSlice(slice)//[0 1 2 3 4 5 6 7 8 9] } func changeSlice(a []int) { a = append(a, 99) }
會發現原slice的值并沒有被改變,這是因為我們用了append,append之后,原slice的容量已經不夠了,這時候會copy出一個新的數組。其實go的函數參數傳遞,只有值傳遞,沒有引用傳遞,當slice的底層數據沒有改變的時候,怎么修改都會影響原底層數組,當slice發生擴容時,擴容后就是新的數組,那么怎么修改這個新的數組都不會影響原來的數組。
數組和slice能不能比較
只有長度相同,類型也相同的數組才能比較
a:=[2]int{1,2} b:=[2]int{1,2} fmt.Println(a==b) true a:=[2]int{1,2} b:=[3]int{1,2,3} fmt.Println(a==b) //invalid operation: a == b (mismatched types [2]int and [3]int) a:=[2]int{1,2} b:=[2]int8{1,2} fmt.Println(a==b) //invalid operation: a == b (mismatched types [2]int and [2]int8)
slice只能和nil做比較,其余的都不能比較
a:=[]int{1,2} b:=[]int{1,2} fmt.Println(a==b)//invalid operation: a == b (slice can only be compared to nil)
但是需要注意的是,兩個都是nil的slice也不能進行比較,它只能和nil對比,這里的nil是真真實實的nil。
var a []int var b []int fmt.Println(a == b) //invalid operation: a == b (slice can only be compared to nil) fmt.Println(a == nil) //true
原文鏈接:https://juejin.cn/post/7121628307040403487
相關推薦
- 2022-08-21 python深度學習tensorflow安裝調試教程_python
- 2022-12-22 Nginx配置之main?events塊使用示例詳解_nginx
- 2021-12-10 Android?Activity活動頁面跳轉與頁面傳值_Android
- 2022-09-16 Python利用LyScript插件實現批量打開關閉進程_python
- 2022-08-04 python調用pymssql包操作SqlServer數據庫的實現_python
- 2021-12-03 C/C++表格組件Qt?TableWidget應用詳解_C 語言
- 2022-04-28 淺析python中特殊文件和特殊函數_python
- 2023-05-15 shell參數換行與shell輸出換行的方法實例_linux shell
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支