Go語言標準庫中提供了sort包對整型,浮點型,字符串型切片進行排序,檢查一個切片是否排好序,使用二分法搜索函數在一個有序切片中搜索一個元素等功能。
創(chuàng)新互聯公司堅持“要么做到,要么別承諾”的工作理念,服務領域包括:網站設計、成都做網站、企業(yè)官網、英文網站、手機端網站、網站推廣等服務,滿足客戶于互聯網時代的官渡網站設計、移動媒體設計的需求,幫助企業(yè)找到有效的互聯網解決方案。努力成為您成熟可靠的網絡建設合作伙伴!
關于sort包內的函數說明與使用,請查看
在這里簡單講幾個sort包中常用的函數
在Go語言中,對字符串的排序都是按照字節(jié)排序,也就是說在對字符串排序時是區(qū)分大小寫的。
二分搜索算法
Go語言中提供了一個使用二分搜索算法的sort.Search(size,fn)方法:每次只需要比較㏒?n個元素,其中n為切片中元素的總數。
sort.Search(size,fn)函數接受兩個參數:所處理的切片的長度和一個將目標元素與有序切片的元素相比較的函數,該函數是一個閉包,如果該有序切片是升序排列,那么在判斷時使用 有序切片的元素 = 目標元素。該函數返回一個int值,表示與目標元素相同的切片元素的索引。
在切片中查找出某個與目標字符串相同的元素索引
[5]int 是數組,而 []int 是切片。二者看起來相似,實則是根本上不同的數據結構。
切片的數據結構中,包含一個指向數組的指針 array ,當前長度 len ,以及最大容量 cap 。在使用 make([]int, len) 創(chuàng)建切片時,實際上還有第三個可選參數 cap ,也即 make([]int, len, cap) 。在不聲明 cap 的情況下,默認 cap=len 。當切片長度沒有超過容量時,對切片新增數據,不會改變 array 指針的值。
當對切片進行 append 操作,導致長度超出容量時,就會創(chuàng)建新的數組,這會導致和原有切片的分離。在下例中
由于 a 的長度超出了容量,所以切片 a 指向了一個增長后的新數組,而 b 仍然指向原來的老數組。所以之后對 a 進行的操作,對 b 不會產生影響。
試比較
本例中, a 的容量為6,因此在 append 后并未超出容量,所以 array 指針沒有改變。因此,對 a 進行的操作,對 b 同樣產生了影響。
下面看看用 a := []int{} 這種方式來創(chuàng)建切片會是什么情況。
可以看到,空切片的容量為0,但后面向切片中添加元素時,并不是每次切片的容量都發(fā)生了變化。這是因為,如果增大容量,也即需要創(chuàng)建新數組,這時還需要將原數組中的所有元素復制到新數組中,開銷很大,所以GoLang設計了一套擴容機制,以減少需要創(chuàng)建新數組的次數。但這導致無法很直接地判斷 append 時是否創(chuàng)建了新數組。
如果一次添加多個元素,容量又會怎樣變化呢?試比較下面兩個例子:
那么,是不是說,當向一個空切片中插入 2n-1 個元素時,容量就會被設置為 2n 呢?我們來試試其他的數據類型。
可以看到,根據切片對應數據類型的不同,容量增長的方式也有很大的區(qū)別。相關的源碼包括: src/runtime/msize.go , src/runtime/mksizeclasses.go 等。
我們再看看切片初始非空的情形。
可以看到,與剛剛向空切片添加5個int的情況一致,向有3個int的切片中添加2個int,容量增長為6。
需要注意的是, append 對切片擴容時,如果容量超過了一定范圍,處理策略又會有所不同??梢钥纯聪旅孢@個例子。
具體為什么會是這樣的變化過程,還需要從 源碼 中尋找答案。下面是 src/runtime/slice.go 中的 growslice 函數中的核心部分。
GoLang中的切片擴容機制,與切片的數據類型、原本切片的容量、所需要的容量都有關系,比較復雜。對于常見數據類型,在元素數量較少時,大致可以認為擴容是按照翻倍進行的。但具體情況需要具體分析。
Go 中的分片數組,實際上有點類似于Java中的ArrayList,是一個可以擴展的數組,但是Go中的切片由比較靈活,它和數組很像,也是基于數組,所以在了解Go切片前我們先了解下數組。
數組簡單描述就由相同類型元素組成的數據結構, 在創(chuàng)建初期就確定了長度,是不可變的。
但是Go的數組類型又和C與Java的數組類型不一樣, NewArray 用于創(chuàng)建一個數組,從源碼中可以看出最后返回的是 Array{}的指針,并不是第一個元素的指針,在Go中數組屬于值類型,在進行傳遞時,采取的是值傳遞,通過拷貝整個數組。Go語言的數組是一種有序的struct。
Go 語言的數組有兩種不同的創(chuàng)建方式,一種是顯示的初始化,一種是隱式的初始化。
注意一定是使用 [...]T 進行創(chuàng)建,使用三個點的隱式創(chuàng)建,編譯器會對數組的大小進行推導,只是Go提供的一種語法糖。
其次,Go中數組的類型,是由數值類型和長度兩個一起確定的。[2]int 和 [3]int 不是同一個類型,不能進行傳參和比較,把數組理解為類型和長度兩個屬性的結構體,其實就一目了然了。
Go中的數組屬于值類型,通常應該存儲于棧中,局部變量依然會根據逃逸分析確定存儲棧還是堆中。
編譯器對數組函數中做兩種不同的優(yōu)化:
在靜態(tài)區(qū)完成賦值后復制到棧中。
總結起來,在不考慮逃逸分析的情況下,如果數組中元素的個數小于或者等于 4 個,那么所有的變量會直接在棧上初始化,如果數組元素大于 4 個,變量就會在靜態(tài)存儲區(qū)初始化然后拷貝到棧上。
由于數組是值類型,那么賦值和函數傳參操作都會復制整個數組數據。
不管是賦值或函數傳參,地址都不一致,發(fā)生了拷貝。如果數組的數據較大,則會消耗掉大量內存。那么為了減少拷貝我們可以主動的傳遞指針呀。
地址是一樣的,不過傳指針會有一個弊端,從打印結果可以看到,指針地址都是同一個,萬一原數組的指針指向更改了,那么函數里面的指針指向都會跟著更改。
同樣的我們將數組轉換為切片,通過傳遞切片,地址是不一樣的,數組值相同。
切片是引用傳遞,所以它們不需要使用額外的內存并且比使用數組更有效率。
所以,切片屬于引用類型。
通過這種方式可以將數組轉換為切片。
中間不加三個點就是切片,使用這種方式創(chuàng)建切片,實際上是先創(chuàng)建數組,然后再通過第一種方式創(chuàng)建。
使用make創(chuàng)建切片,就不光編譯期了,make創(chuàng)建切片會涉及到運行期。1. 切片的大小和容量是否足夠?。?/p>
切片是否發(fā)生了逃逸,最終在堆上初始化。如果切片小的話會先在?;蜢o態(tài)區(qū)進行創(chuàng)建。
切片有一個數組的指針,len是指切片的長度, cap指的是切片的容量。
cap是在初始化切片是生成的容量。
發(fā)現切片的結構體是數組的地址指針array unsafe.Pointer,而Go中數組的地址代表數組結構體的地址。
slice 中得到一塊內存地址,array[0]或者unsafe.Pointer(array[0])。
也可以通過地址構造切片
nil切片:指的unsafe.Pointer 為nil
空切片:
創(chuàng)建的指針不為空,len和cap為空
當一個切片的容量滿了,就需要擴容了。怎么擴,策略是什么?
如果原來數組切片的容量已經達到了最大值,再想擴容, Go 默認會先開一片內存區(qū)域,把原來的值拷貝過來,然后再執(zhí)行 append() 操作。這種情況對現數組的地址和原數組地址不相同。
從上面結果我們可以看到,如果用 range 的方式去遍歷一個切片,拿到的 Value 其實是切片里面的值拷貝,即淺拷貝。所以每次打印 Value 的地址都不變。
由于 Value 是值拷貝的,并非引用傳遞,所以直接改 Value 是達不到更改原切片值的目的的,需要通過 slice[index] 獲取真實的地址。
1、值接收者和指針接收者
所謂指針接收者和值接收者這兩個概念,用GO寫了一陣子代碼的人都了解了,這里只做簡要說明一下,也就是對于一個給定結構,咱們對結構進行方法包裝的時候,固定必傳的參數,用來指向這個對象結構自身的一個參數,在go中也就是形式如下:
我們對結構體testStruct進行了包裝,提供了兩個方法,sum和modify,其中sum的方法接收者為a testStruct,這個就是值接收者,而modify的接收者為a *testStruct就是指針接收者,也就是說固定對象指針,一個傳遞的是指針地址,而另外一個直接傳遞的是結構值拷貝了
對指針有一定了解的,都可以知道,指針傳遞過去的,可以直接修改結構內部內容,而值傳遞過去的,無論如何修改這個接收者的數據,不會對原對象結構產生影響。而對于咱們包裝結構對象的時候,到底是使用指針還是使用值接收者,這個實際上沒有太大的定論,就我個人的觀點來說,如果結構體占有的內存空間不大(kb級別),而又不需要修改內部的,同時結構對象內部沒有同步對象比如(sync包中的mutex,rwlock,waitgroup等之類的結構的話,可以直接值傳遞,實際上值copy也沒有咱們想象的那么慢,很多時候,都用指針,最后的gc回收掃描可能都比咱們這個傳遞copy的消耗大) p="" /kb級別),而又不需要修改內部的,同時結構對象內部沒有同步對象比如(sync包中的mutex,rwlock,waitgroup等之類的結構的話,可以直接值傳遞,實際上值copy也沒有咱們想象的那么慢,很多時候,都用指針,最后的gc回收掃描可能都比咱們這個傳遞copy的消耗大)
2、實現接口的值接收者和指針接收者有啥區(qū)別
也就是比如定義如下
這里面的值接收者和指針接收者有什么區(qū)別,這里咱來寫一個測試
通過這個測試用例可以發(fā)現,指針接收者實現的接口可以同時支持轉移到值接收者接口和指針接收者接口,而用值接收者實現的接口,則無法轉移到使用指針接收者實現的接口,為啥子呢?目前網上或者各類資料上都是給的一個很官方很官方,而且很書面話難以理解的說明,大致意思如下:
這是目前網絡或者各種資料上都是差不多是這樣說的,看似講了,實際上就說了一個結果,根本就沒說出來一個為什么。這樣的總結出來,一個初學者的角度來看,是很不好理解的,初學者要么就是死記硬背,要么就是生搬硬套,甚至直到寫了好多好多代碼了,都還沒有搞明白一個為啥子,只是會用了而已,從長遠來說這是不利于自身提高的。
有這兩個本質點,咱們自己來思考一下,如果你來實現這個編譯器的時候,用指針接收的時候,指針接收者,默認就能直接獲取支持,而值接收者實現接口的咱們可以直接來一個解指針就變成了值,就能匹配上值接收者實現的接口了,反過來說,如果值接收者,此時要匹配指針接收者,如何匹配呢,取一個地址就變成了指針了,此時數據類型確實是匹配了,但是,地址指向的數據區(qū)不對了,因為我們剛剛說了值接收者拷貝了一個新值之后是完全的一個新的對象,這個新對象和原始對象一點關系都沒有,咱們取地址,取的也是這個新對象地址,對這個地址進行操作,也是這個新對象的內部數據,和原始數據內部沒有任何關系,所以由此就能推斷出,這個是為啥子值接收者不能匹配上指針接收者,而指針接收者卻可以匹配上值接收者了。
1、在某個作用域內部,所有定義的字符串的數據區(qū)相同
這個很好驗證,代碼如下:
2、字符串相加會產生一個新串
這個也很好驗證
3、字符串真的是不可變的嗎
實際上從字符串的結構
從這個結構,就能大致的推斷出來,字符串設計成這樣就不具備直接擴容+來增加新數據,而如果咱們直接使用string[index] = 'a',用這種方式,就不能編譯通過,官方也確定說字符串是不可變的。那么真的是不可變的嗎?
通過上面的結構,在加上go的slice切片的數據結構
由此可見,咱們可以將字符串通過指針方式強轉為一個byte數組指針,然后通過byte切片來修改,試試
編譯通過,運行報錯
unexpected fault address 0xae2e27
fatal error: fault
這個錯誤,基本上就是一個內存的保護錯誤,是寫異常,所以說明了,這個肯定做了內存寫保護,那么直接修改一下內存區(qū)的屬性,去掉他的寫保護,就能寫了
以下代碼都是在Win平臺,Go1.18,Win上修改內存權限屬性,使用VirtualProtect,代碼如下
此時運行,就能發(fā)現tstr的內容被咱們變了,這種情況實際上在實際開發(fā)中不具有實際意義,因為本身在語言層面,已經做了層層限制,咱們這是屬于非法強制的操作方式,是流氓行為,那么是否有比較溫和一點的操作方式呢?答案是有的,且往下看。
通過上面,我們已經用到了字符串結構,切片結構,要想字符串內容可變,那么咱們自己構造字符串的數據內容區(qū)域,且讓這個數據區(qū)木有內存寫保護不就行了,內容區(qū)可變,GO原生態(tài)的byte數組不就行嘛,所以咱們自己構造一下
此時我們直接修改buffer的內容,就是直接修改了str的數據內容了。而又不會像前面的一樣遇到內存寫保護
4、字符串轉換優(yōu)化時可能碰到的坑
通過前面討論的字符串的可變性的方法,咱們可以知道,很多時候,[]byte到字符串的轉變,可以直接構造其結構,而共享數據,從而達到減少數據內存copy的方式來進行優(yōu)化,再使用這些優(yōu)化的時候,一定需要注意,字符串或者數組的生命周期,是否會存在被改寫的情況,從而導致前后不一致的問題。
比如下面這段代碼:
大家可以猜想一下,這個最后里面的數據mmp中,"test"的value是多少,"abcd"的value是多少,然后想想為什么,且等端午之后,再來分解
對于切片的順序遍歷,一般使用 range 就可以了。
這里有一個問題需要注意一下,如果這里的切片nums不是基本數據類型而是結構體。range遍歷出來的value值是拷貝值而并非原結構體,修改value中的值不會改變原切片中的值。如果要遍歷修改,可以將切片的結構體改為指針,或都索引來取值。
一般情況下逆序遍歷思路就是for size-1到0.
二般的也可以使用range來遍歷
1、基本數據類型
bool
string
int int8 int16 int32 int64
uint uint8 uint16 uint32 uint64 uintptr
byte // alias for int8
rune // alias for int32,represents a Unicode code point
float32 float64
complex64 complex128
常量定義
2、類型轉換
(1)Go語言不允許隱式類型轉換(不支持小位數類型向大位數類型轉)
(2)別名和原有類型也不能進行隱式類型轉換(type MyInt int64 = int64)
3、類型的預定義值
1.math.MaxInt64
2.math.MaxFloat64
3.math.MaxUInt32
4、指針類型
(1)不支持指針運算
(2)string是值類型,其默認的初始化值為空字符串,而不是nil
5、算術運算符
+ - * / % ++ --(不支持前置++ --)
6、比較運算符
#== != = =
(1)比較數組
相同維數且含有形同個數元素的數組才可以比較
每個元素都相同的才相等
7、位運算符
| ^
^ (按位置零) a (^b)
1 ^ 0 1
1 ^ 1 0
0 ^ 1 0
0 ^ 0 0
8、條件與循環(huán)
(1)循環(huán)
Go 語?僅?持循環(huán)關鍵字 for
(2)條件
9、數組和切片
數組截取,索引下標從0開始計數
a[開始索引(包含), 結束索引(不包含)]
a := [...]int{1, 2, 3, 4, 5}
a[1:2] //2
a[1:3] //2,3
a[1:len(a)] //2,3,4,5
a[1:] //2,3,4,5
a[:3] //1,2,3
切片內部結構
9、Map
9、字符串
Unicode UTF8
常?字符串函數
網站名稱:go語言數據結構切片,go 字節(jié)切片
本文來源:http://chinadenli.net/article24/heshce.html
成都網站建設公司_創(chuàng)新互聯,為您提供定制開發(fā)、微信小程序、電子商務、App開發(fā)、企業(yè)網站制作、搜索引擎優(yōu)化
聲明:本網站發(fā)布的內容(圖片、視頻和文字)以用戶投稿、用戶轉載內容為主,如果涉及侵權請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網站立場,如需處理請聯系客服。電話:028-86922220;郵箱:631063699@qq.com。內容未經允許不得轉載,或轉載時需注明來源: 創(chuàng)新互聯