【golang】數組和切片底層原理

數組類型的值(以下簡稱數組)的長度是固定的,而切片類型的值(以下簡稱切片)是可變長的。

數組的長度在聲明它的時候就必須給定,并且之后不會再改變。可以說,數組的長度是其類型的一部分。比如,[1]string和[2]string就是兩個不同的數組類型。

切片的類型字面量中只有元素的類型,而沒有長度。切片的長度可以自動地隨著其中元素數量給的增長而增長,但不會隨著元素數量的減少而減小。

image.png

我們其實可以把切片看做是對數組的一層簡單的封裝,因為在每個切片的底層數據結構中,一定會包含一個數組。數組可以被叫做切片的底層數組,而切片也可以被看作是對數組的某個連續片段的引用。

也正因為如此,Go 語言的切片類型屬于引用類型,同屬引用類型的還有字典類型、通道類型、函數類型等;而 Go 語言的數組類型則屬于值類型,同屬值類型的有基礎數據類型以及結構體類型。

注意,Go 語言里不存在像 Java 等編程語言中令人困惑的“傳值或傳引用”問題。在 Go 語言中,我們判斷所謂的“傳值”或者“傳引用”只要看被傳遞的值的類型就好了。

如果傳遞的值是引用類型的,那么就是“傳引用”。如果傳遞的值是值類型的,那么就是“傳值”。從傳遞成本的角度講,引用類型的值往往要比值類型的值低很多

我們在數組和切片之上都可以應用索引表達式,得到的都會是某個元素。我們在它們之上也都可以應用切片表達式,也都會得到一個新的切片。

我們通過調用內建函數len,得到數組和切片的長度。通過調用內建函數cap,我們可以得到它們的容量。

數組的容量永遠等于其長度,都是不可變的。切片的容量卻不是這樣,并且它的變化是有規律可尋的。

怎樣正確估算切片的長度和容量?

package mainimport "fmt"func main() {
// 示例 1。s1 := make([]int, 5)fmt.Printf("The length of s1: %d\n", len(s1))fmt.Printf("The capacity of s1: %d\n", cap(s1))fmt.Printf("The value of s1: %d\n", s1)s2 := make([]int, 5, 8)fmt.Printf("The length of s2: %d\n", len(s2))fmt.Printf("The capacity of s2: %d\n", cap(s2))fmt.Printf("The value of s2: %d\n", s2)
}

首先,我用內建函數make聲明了一個[]int類型的變量s1。我傳給make函數的第二個參數是5,從而指明了該切片的長度。我用幾乎同樣的方式聲明了切片s2,只不過多傳入了一個參數8以指明該切片的容量。

那切片s1和s2的容量都是多少?

答案:切片s1和s2的容量分別是5和8。

問題解析

s1的容量為什么是5呢?

因為我在聲明s1的時候把它的長度設置成了5。當我們用make函數初始化切片時,如果不指明其容量,那么它就會和長度一致。如果在初始化時指明了容量,那么切片的實際容量也就是它了。這也正是s2的容量是8的原因。

上述內容提到過,可以把切片看做是對數組的一層簡單的封裝,因為在每個切片的底層數據結構中,一定會包含一個數組。數組可以被叫做切片的底層數組,而切片也可以被看做是對數組的某個連續片段的引用。

在這種情況下,切片的容量實際上代表了它的底層數組的長度,這里是8。

可以這樣想:有一個窗口,你可以通過這個窗口看到一個數組,但是不一定能看到該數組中的所有元素,有時候只能看到連續的一部分元素。
image.png

現在,這個數組就是切片s2的底層數組,而這個窗口就是切片s2本身。s2的長度實際上指明的就是這個窗口的寬度,決定了你透過s2,可以看到其底層數組中的哪幾個連續的元素。

由于s2的長度是5,所以你可以看到底層數組中的第 1 個元素到第 5 個元素,對應的底層數組的索引范圍是 [0, 4]。

切片代表的窗口也會被劃分成一個一個的小格子,就像我們家里的窗戶那樣。每個小格子都對應著其底層數組中的某一個元素。

我們繼續拿s2為例,這個窗口最左邊的那個小格子對應的正好是其底層數組中的第一個元素,即索引為0的那個元素。因此可以說,s2中的索引從0到4所指向的元素恰恰就是其底層數組中索引從0到4代表的那 5 個元素。

請記住,當我們用make函數或切片值字面量(比如[]int{1, 2, 3})初始化一個切片時,該窗口最左邊的那個小格子總是會對應其底層數組中的第 1 個元素。

但是當我們通過切片表達式基于某個數組或切片生成新切片的時候,情況就變得復雜起來了。

s3 := []int{1, 2, 3, 4, 5, 6, 7, 8}
s4 := s3[3:6]
fmt.Printf("The length of s4: %d\n", len(s4))
fmt.Printf("The capacity of s4: %d\n", cap(s4))
fmt.Printf("The value of s4: %d\n", s4)

切片s3中有 8 個元素,分別是從1到8的整數。s3的長度和容量都是8。然后,我用切片表達式s3[3:6]初始化了切片s4。問題是,這個s4的長度和容量分別是多少?

這并不難,用減法就可以搞定。首先你要知道,切片表達式中的方括號里的那兩個整數都代表什么。我換一種表達方式你也許就清楚了,即:[3, 6)。

這是數學中的區間表示法,常用于表示取值范圍。由此可知,[3:6]要表達的就是透過新窗口能看到的s3中元素的索引范圍是從3到5(注意,不包括6)。

這里的3可被稱為起始索引,6可被稱為結束索引。那么s4的長度就是6減去3,即3。因此可以說,s4中的索引從0到2指向的元素對應的是s3及其底層數組中索引從3到5的那 3 個元素。

image.png

前面提到過,切片的容量代表了它的底層數組的長度,但這僅限于使用make函數或者切片值字面量初始化切片的情況。

更通用的規則是:一個切片的容量可以被看作是透過這個窗口最多可以看到的底層數組中元素的個數。

由于s4是通過在s3上施加切片操作得來的,所以s3的底層數組就是s4的底層數組。

又因為,在底層數組不變的情況下,切片代表的窗口可以向右擴展,直至其底層數組的末尾。

所以,s4的容量就是其底層數組的長度8,減去上述切片表達式中的那個起始索引3,即5。

注意,切片代表的窗口是無法向左擴展的。也就是說,我們永遠無法透過s4看到s3中最左邊的那3個元素。

最后,順便提一下把切片的窗口向右擴展到最大的方法。對于s4來說,切片表達式s4[0:cap(s4)]就可以做到。我想你應該能看懂。該表達式的結果值(即一個新的切片)會是[]int{4, 5, 6, 7, 8},其長度和容量都是5。

怎樣估算切片容量的增長?

一旦一個切片無法容納更多的元素,Go 語言就會想辦法擴容。但它并不會改變原來的切片,而是會生成一個容量更大的切片,然后將把原有的元素和新元素一并拷貝到新切片中。在一般的情況下,你可以簡單地認為新切片的容量(以下簡稱新容量)將會是原切片容量(以下簡稱原容量)的 2 倍

但是,當原切片的長度(以下簡稱原長度)大于或等于1024時,Go 語言將會以原容量的1.25倍作為新容量的基準(以下新容量基準)。新容量基準會被調整(不斷地與1.25相乘),直到結果不小于原長度與要追加的元素數量之和(以下簡稱新長度)。最終,新容量往往會比新長度大一些,當然,相等也是可能的。

另外,如果我們一次追加的元素過多,以至于使新長度比原容量的 2 倍還要大,那么新容量就會以新長度為基準。注意,與前面那種情況一樣,最終的新容量在很多時候都要比新容量基準更大一些。更多細節可參見runtime包中 slice.go 文件里的growslice及相關函數的具體實現。

切片的底層數組什么時候會被替換?

確切地說,一個切片的底層數組永遠不會被替換。為什么?雖然在擴容的時候 Go 語言一定會生成新的底層數組,但是它也同時生成了新的切片。

它只是把新的切片作為了新底層數組的窗口,而沒有對原切片,及其底層數組做任何改動。
請記住,在無需擴容時,append函數返回的是指向原底層數組的新切片,而在需要擴容時,append函數返回的是指向新底層數組的新切片。所以,嚴格來講,“擴容”這個詞用在這里雖然形象但并不合適。不過鑒于這種稱呼已經用得很廣泛了,我們也沒必要另找新詞了。

只要新長度不會超過切片的原容量,那么使用append函數對其追加元素的時候就不會引起擴容。這只會使緊鄰切片窗口右邊的(底層數組中的)元素被新的元素替換掉。

文章學習自郝林老師的《Go語言36講》

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/40220.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/40220.shtml
英文地址,請注明出處:http://en.pswp.cn/news/40220.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Spring學習筆記之Spring IoC注解式開發

文章目錄 聲明Bean的注解Component注解Controller注解Service注解Repository Spring注解的使用選擇性實例化Bean負責注入的注解ValueAutowired與QuaifierResource 全注解式開發 注解的存在主要是為了簡化XML的配置。Spring6倡導全注解開發 注解怎么定義,注解中的屬性…

深入探索JavaEE單體架構、微服務架構與云原生架構

課程鏈接: 鏈接: https://pan.baidu.com/s/1xSI1ofwYXfqOchfwszCZnA?pwd4s99 提取碼: 4s99 復制這段內容后打開百度網盤手機App,操作更方便哦 --來自百度網盤超級會員v4的分享 課程介紹: 🔍【00】模塊零:開營直播&a…

ARM-M0內核MCU,內置24bit ADC,采樣率4KSPS,傳感器、電子秤、體脂秤專用,國產IC

ARM-M0內核MCU 內置24bit ADC ,采樣率4KSPS flash 64KB,SRAM 32KB 適用于傳感器,電子秤,體脂秤等等

[BitSail] Connector開發詳解系列三:SourceReader

更多技術交流、求職機會,歡迎關注字節跳動數據平臺微信公眾號,回復【1】進入官方交流群 Source Connector 本文將主要介紹負責數據讀取的組件SourceReader: SourceReader 每個SourceReader都在獨立的線程中執行,只要我們保證Sou…

Jmeter進階使用:BeanShell實現接口前置和后置操作

一、背景 我們使用Jmeter做壓力測試或者接口測試時,除了最簡單的直接對接口發起請求,很多時候需要對接口進行一些前置操作:比如提前生成測試數據,以及一些后置操作:比如提取接口響應內容中的某個字段的值。舉個最常用…

c語言——拷貝數組

這段代碼是一個簡單的數組拷貝示例。它的功能是將一個原始數組 original 的內容拷貝到另一個數組 copied 中,并輸出兩個數組的元素。 代碼執行過程如下: 首先,在 main() 函數中定義了一個整型數組 original,并初始化了它的元素。…

【ARM 嵌入式 編譯 Makefile 系列 15 - Makefile define 宏與調用宏函數詳細介紹】

文章目錄 Makefile define 宏與調用宏函數帶參數的宏函數帶返回值的宏函數Makefile define 宏與調用宏函數 在Makefile中,可以通過define關鍵字來定義一個多行的宏(也稱為變量)。這種宏定義通常用于定義一個復雜的命令序列,然后在其他地方調用。 以下是定義一個宏的例子:…

物聯網在制造業中的應用

制造業目前正在經歷第四次工業革命,物聯網、人工智能和機器人等技術進步正在推動行業的發展。研究表明,到2024年,全球制造商將在物聯網解決方案上投資700億美元,許多制造商正在實施物聯網設備,以利用預測性維護和復雜的…

接口測試工具——Postman測試工具 Swagger接口測試+SpringBoot整合 JMeter高并發測試工具

目錄 Postman測試工具接口測試工具swaggerKnife4j1.引入依賴2.配置3.常用注解4.接口測試 JMeter什么是JMeter?JMeter安裝配置1.官網下載2.下載后解壓3.漢語設置 JMeter的使用方法1.新建線程組2.設置參數3.添加取樣器4.設置參數:協議,ip,端口…

SDK是什么,SDK和API有什么區別

SDK(Software Development Kit)是一種開發工具包,通常由軟件開發公司或平臺提供,用于幫助開發人員構建、測試和集成特定平臺或軟件的應用程序。SDK 包含一系列的庫、工具、示例代碼和文檔,旨在簡化開發過程并提供所需的…

基于Mysql+Vue+Django的協同過濾和內容推薦算法的智能音樂推薦系統——深度學習算法應用(含全部工程源碼)+數據集

目錄 前言總體設計系統整體結構圖系統流程圖 運行環境Python 環境MySQL環境VUE環境 模塊實現1. 數據請求和儲存2. 數據處理計算歌曲、歌手、用戶相似度計算用戶推薦集 3. 數據存儲與后臺4. 數據展示 系統測試工程源代碼下載其它資料下載 前言 本項目以豐富的網易云音樂數據為基…

SQLSERVER 查詢語句加with (NOLOCK) 報ORDER BY 報錯 除非另外還指定了 TOP、OFFSET 或 FOR XML

最近有一個項目在客戶使用時發現死鎖問題,用的數據庫是SQLSERVER ,死鎖的原因是有的客戶經常去點報表,報表查詢時間又慢,然后又有人在做單導致了死鎖,然后主管要我們用SQLSERVER查詢時要加with (NOLOCK),但是我在加完 …

YOLOv5模型訓練流程

此文章只是記錄使用,以便后續查看,不作為教程,剛接觸,可能有錯誤 YOLOv5模型訓練流程 一、數據集的準備 1.在源碼根目錄新建mydata文件夾,在此文件夾下新建images和labels文件夾 目錄樹如下: ├───…

鏈表---

題目描述 一個學校里老師要將班上 N 個同學排成一列,同學被編號為 1~N,他采取如下的方法: 先將 11 號同學安排進隊列,這時隊列中只有他一個人; 2~N 號同學依次入列,編號為 i 的同學入列方式為&#xff…

2023骨傳導耳機推薦,適合運動骨傳導耳機推薦

相信很多人跟我一樣,隨著現在五花八門的耳機品種增多,選耳機的時候真是眼花繚亂,尤其還是網購,只能看,不能試,所以選擇起來比較困難, 作為一個運動達人,為了讓大家在購買耳機時少走彎…

〔012〕Stable Diffusion 之 中文提示詞自動翻譯插件 篇

? 目錄 🎈 翻譯插件🎈 下載谷歌翻譯🎈 谷歌翻譯使用方法🎈 谷歌翻譯使用效果 🎈 翻譯插件 在插件列表中搜索 Prompt Translator可以看到有2個插件選項:一個是基于谷歌翻譯 〔推薦〕、一個基于百度和deepl…

jvm從入門到精通

jvm 1.jvm與java體系結構???????

奧威BI財務數據分析方案:借BI之利,成就智能財務分析

隨著智能技術的發展,各行各業都走上借助智能技術高效運作道路,財務數據分析也不例外。借助BI商業智能技術能夠讓財務數據分析更高效、便捷、直觀立體,也更有助于發揮財務數據分析作為企業經營管理健康晴雨表的作用。隨著BI財務數據分析經驗的…

【RP2040】香瓜樹莓派RP2040之新建工程

本文最后修改時間:2022年09月05日 11:02 一、本節簡介 本節介紹如何新建一個自己的工程。 二、實驗平臺 1、硬件平臺 1)樹莓派pico開發板 ①樹莓派pico開發板*2 ②micro usb數據線*2 2)電腦 2、軟件平臺 1)VS CODE 三、版…

【C++】一文帶你初識C++繼承

食用指南:本文在有C基礎的情況下食用更佳 🍀本文前置知識: C類 ??今日夜電波:napori—Vaundy 1:21 ━━━━━━?💟──────── 3:23 …