1.數(shù)組
數(shù)組是值類型
[10]int 和 [20]int是不同類型
調(diào)用func(arr [10]int)會(huì)拷貝數(shù)組
在go語言中一般不直接使用數(shù)據(jù)
package main
import "fmt"
func updateArr(arr *[5]int) {
arr[0] = 100
}
func updateArrThroughSlice(arr []int) {
arr[0] = 100
}
func main() {
//創(chuàng)建一個(gè)數(shù)據(jù)
var arr [5]int
arr2 := [5]int{1, 2, 3, 4, 5}
//長(zhǎng)度讓編譯器來數(shù)
arr3 := [...]int{1, 2, 3, 4, 5}
//[0 0 0 0 0] [1 2 3 4 5] [1 2 3 4 5]
fmt.Println(arr, arr2, arr3)
//定義二維數(shù)組 4行5列
var arr4 [4][5]int
//[[0 0 0 0 0] [0 0 0 0 0] [0 0 0 0 0] [0 0 0 0 0]]
fmt.Println(arr4)
//遍歷數(shù)據(jù)
//for i:=0;ilen(arr3);i++{
// fmt.Println(arr3[i])
//}
for num, v := range arr2 {
fmt.Printf("第%d個(gè)元素為:%d\n", num, v)
}
//數(shù)據(jù)是值類型,通過指針可以改變值的大小
fmt.Println("update before")
fmt.Println(arr2)
updateArr(arr2) //傳入arr3的地址
fmt.Println("update after")
fmt.Println(arr2)
//通過Slice改變數(shù)據(jù)
fmt.Println("update before")
fmt.Println(arr3)
updateArrThroughSlice(arr3[:]) //傳入Slice
fmt.Println("update after")
fmt.Println(arr3)
}
2.Slice(切片)
2.1 Slice的實(shí)現(xiàn)
Slice本身沒有數(shù)據(jù),是對(duì)底層array的一個(gè)view
Slice內(nèi)部有個(gè)指針(ptr)指向開頭的元素,Slice有長(zhǎng)度(len),容量(cap);cap代表從指針(ptr)開始到數(shù)組(arr)末尾的長(zhǎng)度,Slice在擴(kuò)展的時(shí)候不能超過cap.
package main
import "fmt"
func updateSlice(s []int) {
s[0] = 100
}
func main() {
arr := [...]int{0, 1, 2, 3, 4, 5, 6, 7}
//創(chuàng)建一個(gè)Slice
s1 := arr[:]
s2 := arr[2:6]
fmt.Printf("s1:%v\ns2:%v\n", s1, s2)
//改變Slice內(nèi)部元素
updateSlice(s2)
fmt.Println(s2)
//ReSlice:對(duì)Slice再進(jìn)行一次Slice操作
s3 := s1[:5]
fmt.Println(s3)
s3 = s3[:2]
fmt.Println(s3)
}
2.2 Slice的擴(kuò)展
s[i]不可以超越len(i),向后擴(kuò)展不可以超過底層數(shù)組cap(s)
package main
import "fmt"
func updateSlice(s []int) {
s[0] = 100
}
func main() {
arr := [...]int{0, 1, 2, 3, 4, 5, 6, 7}
fmt.Printf("arr=%v\n", arr)
//Extending Slice 不能超過cap(s)
s1 := arr[2:6]
fmt.Printf("s1=%v, len(s1)=%d, cap(s1)=%d\n", s1, len(s1), cap(s1))
s2 := s1[3:5]
fmt.Printf("s2=%v, len(s2)=%d, cap(s2)=%d\n", s2, len(s2), cap(s2))
// s[i]不能超過len(s)
fmt.Printf("get Slice element:%v",s2[1])
//panic: runtime error: index out of range [2] with length 2
//fmt.Printf("get Slice element:%v",s2[2])
}
2.2 Slice的其它操作
向Slice添加元素
package main
import "fmt"
//查看操作系統(tǒng)怎么擴(kuò)充Slice的cap
func printSlice(s []int) {
fmt.Printf("%v, len=%d, cap=%d\n", s, len(s), cap(s))
}
func main() {
arr := [...]int{0, 1, 2, 3, 4, 5, 6, 7}
//添加元素時(shí)如果超越cap,系統(tǒng)會(huì)重新分配更大的底層數(shù)組
//由于值傳遞的關(guān)系,必須接收append的返回值
// s = append(s,val)
s1 := arr[2:]
fmt.Printf("s1=%v\n", s1)
s2 := s1[3:5] //[s1[3], s1[4]]
fmt.Printf("s2=%v, len(s2)=%d, cap(s2)=%d\n", s2, len(s2), cap(s2))
s3 := append(s2, 10)
s4 := append(s3, 11)
s5 := append(s4, 12)
fmt.Printf("s3=%v, s4=%v, s5=%v\n", s3, s4, s5)
// s4 and s5 no longer view arr
fmt.Printf("arr=%v\n", arr)
//創(chuàng)建一個(gè)Slice
var s []int
//Zero value for slice is nil
for i := 0; i 100; i++ {
printSlice(s)
s = append(s, i*2+1)
}
fmt.Println(s)
}
Slice的copy,添加,刪除元素操作
package main
import (
"fmt"
)
//查看操作系統(tǒng)怎么擴(kuò)充Slice的cap
func printSlice(str string, s []int) {
fmt.Printf("%s=%v, len=%d, cap=%d\n", str, s, len(s), cap(s))
}
func main() {
//初始化slice
s1 := []int{2, 4, 6, 8}
fmt.Println(s1)
//[2 4 6 8]
//創(chuàng)建一個(gè)len為16的Slice
s2 := make([]int, 16)
//創(chuàng)建一個(gè)len為10,cap為32的Slice
s3 := make([]int, 10, 32)
printSlice("s2", s2)
//[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0], len=16, cap=16
printSlice("s3", s3)
//[0 0 0 0 0 0 0 0 0 0], len=10, cap=32
//拷貝Slice
fmt.Println("Copying Slice")
//dst src
copy(s2, s1)
printSlice("s2", s2)
//[2 4 6 8 0 0 0 0 0 0 0 0 0 0 0 0], len=16, cap=16
//刪除Slice中的元素
fmt.Println("Deleting element from slice")
//刪除下標(biāo)為3的元素
//通過...append s2下標(biāo)為4后的元素
s2 = append(s2[:3], s2[4:]...)
printSlice("s2", s2)
//刪除頭尾元素
fmt.Println("Popping from front")
front := s2[0]
s2 = s2[1:]
fmt.Println(front)
fmt.Println(s2)
fmt.Println("Popping from back")
tail := s2[len(s2)-1]
s2 = s2[:len(s2)-1]
fmt.Println(tail)
fmt.Println(s2)
}
3.Map
3.1 Map的操作
創(chuàng)建: make(map[string]int)
獲取元素:m[key]
key不存在時(shí),獲得Value類型的初始值
用value,ok := m[key]來判斷是否存在key
用delete刪除一個(gè)key
使用range遍歷key,或者遍歷key, value對(duì)
不保證遍歷順序,如需順序,需手動(dòng)對(duì)key排序
使用len獲得元素個(gè)數(shù)
package main
import "fmt"
func main() {
//創(chuàng)建一個(gè)map
//map中的key是無序的,是一個(gè)HashMap
m := map[string]string{
"name": "Cocktail_py",
"course": "golang",
"site": "CSDN",
"quality": "pretty well",
}
m2 := make(map[string]int) // m2 = empty map
var m3 map[string]int // m3 == nil
fmt.Println(m, m2, m3)
fmt.Println("Traversing map")
for k, v := range m {
fmt.Println(k, v)
}
//map 操作
//獲取元素:m[key]
fmt.Println("Getting values")
courseName, ok := m["course"]
fmt.Println(courseName, ok)
//當(dāng)key不存在
if courName, ok := m["courName"]; ok {
fmt.Println(courName) // Zero value
} else {
fmt.Println("key does not exist")
}
fmt.Println("Deleting values")
delete(m, "name")
name, ok := m["name"]
fmt.Println(name, ok)
}
3.2 Map的key
map使用哈希表,必須可以比較相等
除了Slice,map,function的內(nèi)建類型都可以作為key
Struct類型不包含上述字段,也可作為key
3.3 Map的例題:尋找最長(zhǎng)不含有重復(fù)字符的子串
/*
當(dāng)前一個(gè)字符串,從左往后開始掃描,只要掃描一遍就可以,如果掃到X的位置,看到一個(gè)字母X應(yīng)該怎么做呢
首先,記錄一個(gè)start表示當(dāng)前找到的最長(zhǎng)不含有重復(fù)字符的子串的開始,保證start到X之前的子串是不含有重復(fù)字符的,
之后,需要查看從start到X-1這個(gè)位置之間有沒有X,使用一個(gè)叫l(wèi)astOccurred[x]記錄X最后出現(xiàn)的位置在哪里,使用map會(huì)有三種情況:1.x重來沒有出現(xiàn)過,或者x出現(xiàn)在start之前,若x出現(xiàn)在start之前,最長(zhǎng)的子串+1; 2.lastOccurred[x]出現(xiàn)在start到X中間,更新start位置,start指向lastOccurred[x+1]的位置
*/
package main
import "fmt"
func lengthOfNonRepeatingSubStr(s string)int {
lastOccurred := make(map[byte]int)
start := 0
maxLength := 0
//遍歷字符串 go語言中char類型是使用了一種rune(32位)類型
for x, ch := range []byte(s){
//lastOccurred[ch]有可能不存在;若不存在出現(xiàn)0,會(huì)影響運(yùn)算
if lastl, ok:= lastOccurred[ch];ok lastl >= start{
start = lastl + 1
}
//stat到i結(jié)束
if x-start + 1 > maxLength{
maxLength = x -start + 1
}
lastOccurred[ch] = x
}
return maxLength
}
func main() {
fmt.Println(lengthOfNonRepeatingSubStr("hellohello"))
}
4.rune
rune相當(dāng)于go的char
使用range遍歷pos,rune對(duì)
使用utf8.RuneCountlnString獲得字符數(shù)量
使用len獲得字節(jié)長(zhǎng)度
使用[]byte獲得字節(jié)
package main
import (
"fmt"
"unicode/utf8"
)
func main() {
//英文占一個(gè)字節(jié),中文占三個(gè)字節(jié)
s := "yes我愛CSDN!"
fmt.Println(len(s)) // 14
//%X十六進(jìn)制,大寫字符,每個(gè)字節(jié)兩個(gè)字符
//796573E68891E788B14353444E21
fmt.Printf("%X\n",[]byte(s))
//%T 相應(yīng)值的類型
//使用for range遍歷字符串時(shí),會(huì)默認(rèn)將byte(int8)類型轉(zhuǎn)化為rune(int32)類型,因?yàn)間o采用UTF-8編碼 可變長(zhǎng)的編碼
for _,b := range s{
fmt.Printf("%T %X\n",b,b)
}
for _,b := range []byte(s){
fmt.Printf("%T %X\n",b,b)
}
//打印字符的個(gè)數(shù)
fmt.Println("Rune count:",utf8.RuneCountInString(s))
bytes := []byte(s)
fmt.Println(bytes)
for len(bytes) > 0{
ch,size := utf8.DecodeRune(bytes)
bytes = bytes[size:]
//相應(yīng)Unicode碼點(diǎn)所表示的字符
fmt.Printf("%c",ch)
}
//獲取第幾個(gè)字符是誰
for i, ch := range []rune(s) {
fmt.Printf("(%d %c) ", i, ch)
}
fmt.Println()
}
4.1 Map的例題:尋找最長(zhǎng)不含有重復(fù)字符的子串(國(guó)際版)
//國(guó)際版
func lengthOfNonRepeatingSubStr(s string) int {
lastOccurred := make(map[rune]int)
start := 0
maxLength := 0
//遍歷字符串 go語言中char類型是使用了一種rune(32位)
//for i, ch := range s{
for i, ch := range []rune(s) {
//lastOccurred[ch]有可能不存在;若不存在出現(xiàn)0,會(huì)影響運(yùn)算
if lastI, ok := lastOccurred[ch]; ok lastI >= start {
start = lastI + 1
}
//start到i結(jié)束
if i-start+1 > maxLength {
maxLength = i - start + 1
}
lastOccurred[ch] = i
}
return maxLength
}
補(bǔ)充:Golang 容器的學(xué)習(xí)與實(shí)踐
Golang 提供了幾個(gè)簡(jiǎn)單的容器供我們使用,本文在介紹幾種 Golang 容器的基礎(chǔ)上,實(shí)現(xiàn)一個(gè)基于 Golang 容器的LRU算法。
容器介紹
Golang 容器位于 container 包下,提供了三種包供我們使用,heap、list、ring. 下面我們分別學(xué)習(xí)。
heap
heap 是一個(gè)堆的實(shí)現(xiàn)。一個(gè)堆正常保證了獲取/彈出最大(最?。┰氐臅r(shí)間為log n、插入元素的時(shí)間為 log n.
Golang堆實(shí)現(xiàn)接口如下:
// src/container/heap.go
type Interface interface {
sort.Interface
Push(x interface{}) // add x as element Len()
Pop() interface{} // remove and return element Len() - 1.
}
heap 是基于 sort.Interface 實(shí)現(xiàn)的。
// src/sort/
type Interface interface {
Len() int
Less(i, j int) bool
Swap(i, j int)
}
因此,如果要使用官方提供的 heap,需要我們實(shí)現(xiàn)如下幾個(gè)接口:
Len() int {} // 獲取元素個(gè)數(shù)
Less(i, j int) bool {} // 比較方法
Swap(i, j int) // 元素交換方法
Push(x interface{}){} // 在末尾追加元素
Pop() interface{} // 返回末尾元素
然后在使用時(shí),我們可以使用如下幾種方法:
// 初始化一個(gè)堆
func Init(h Interface){}
// push一個(gè)元素倒堆中
func Push(h Interface, x interface{}){}
// pop 堆頂元素
func Pop(h Interface) interface{} {}
// 刪除堆中某個(gè)元素,時(shí)間復(fù)雜度 log n
func Remove(h Interface, i int) interface{} {}
// 調(diào)整i位置的元素位置(位置I的數(shù)據(jù)變更后)
func Fix(h Interface, i int){}
list 鏈表
list 實(shí)現(xiàn)了一個(gè)雙向鏈表,鏈表不需要實(shí)現(xiàn) heap 類似的接口,可以直接使用。
鏈表的構(gòu)造:
// 返回一個(gè)鏈表對(duì)象
func New() *List {}
官方提供了豐富的方法供我們操作列表,方法如下:
// 返回鏈表的長(zhǎng)度
func (l *List) Len() int {}
// 返回鏈表中的第一個(gè)元素
func (l *List) Front() *Element {}
// 返回鏈表中的末尾元素
func (l *List) Back() *Element {}
// 移除鏈表中的某個(gè)元素
func (l *List) Remove(e *Element) interface{} {}
// 在表頭插入值為 v 的元素
func (l *List) PushFront(v interface{}) *Element {}
// 在表尾插入值為 v 的元素
func (l *List) PushBack(v interface{}) *Element {}
// 在mark之前插入值為v 的元素
func (l *List) InsertBefore(v interface{}, mark *Element) *Element {}
// 在mark 之后插入值為 v 的元素
func (l *List) InsertAfter(v interface{}, mark *Element) lement {}
// 移動(dòng)e某個(gè)元素到表頭
func (l *List) MoveToFront(e *Element) {}
// 移動(dòng)e到隊(duì)尾
func (l *List) MoveToBack(e *Element) {}
// 移動(dòng)e到mark之前
func (l *List) MoveBefore(e, mark *Element) {}
// 移動(dòng)e 到mark 之后
func (l *List) MoveAfter(e, mark *Element) {}
// 追加到隊(duì)尾
func (l *List) PushBackList(other *List) {}
// 將鏈表list放在隊(duì)列前
func (l *List) PushFrontList(other *List) {}
我們可以通過 Value 方法訪問 Element 中的元素。除此之外,我們還可以用下面方法做鏈表遍歷:
// 返回下一個(gè)元素
func (e *Element) Next() *Element {}
// 返回上一個(gè)元素
func (e *Element) Prev() *Element {}
下面是隊(duì)列的遍歷的例子:
// l 為隊(duì)列,
for e := l.Front(); e != nil; e = e.Next() {
//通過 e.Value 做數(shù)據(jù)訪問
}
ring 循環(huán)列表
container 中的循環(huán)列表是采用鏈表實(shí)現(xiàn)的。
// 構(gòu)造一個(gè)包含N個(gè)元素的循環(huán)列表
func New(n int) *Ring {}
// 返回列表下一個(gè)元素
func (r *Ring) Next() *Ring {}
// 返回列表上一個(gè)元素
func (r *Ring) Prev() *Ring {}
// 移動(dòng)n個(gè)元素 (可以前移,可以后移)
func (r *Ring) Move(n int) *Ring {}
// 把 s 鏈接到 r 后面。如果s 和r 在一個(gè)ring 里面,會(huì)把r到s的元素從ring 中刪掉
func (r *Ring) Link(s *Ring) *Ring {}
// 刪除n個(gè)元素 (內(nèi)部就是ring 移動(dòng)n個(gè)元素,然后調(diào)用Link)
func (r *Ring) Unlink(n int) *Ring {}
// 返回Ring 的長(zhǎng)度,時(shí)間復(fù)雜度 n
func (r *Ring) Len() int {}
// 遍歷Ring,執(zhí)行 f 方法 (不建議內(nèi)部修改ring)
func (r *Ring) Do(f func(interface{})) {}
訪問 Ring 中元素,直接 Ring.Value 即可。
容器的使用
下面,我們通過 map 和 官方包中的雙向鏈表實(shí)現(xiàn)一個(gè)簡(jiǎn)單的 lru 算法,用來熟悉golang 容器的使用。
LRU 算法 (Least Recently Used),在做緩存置換時(shí)用的比較多。逐步淘汰最近未使用的 cache,而使我們的緩存中持續(xù)保持著最近使用的數(shù)據(jù)。
package main
import "fmt"
import "container/list"
// lru 中的數(shù)據(jù)
type Node struct {
K, V interface{}
}
// 鏈表 + map
type LRU struct {
list *list.List
cacheMap map[interface{}]*list.Element
Size int
}
// 初始化一個(gè)LRU
func NewLRU(cap int) *LRU {
return LRU{
Size: cap,
list: list.New(),
cacheMap: make(map[interface{}]*list.Element, cap),
}
}
// 獲取LRU中數(shù)據(jù)
func (lru *LRU) Get(k interface{}) (v interface{}, ret bool) {
// 如果存在,則把數(shù)據(jù)放到鏈表最前面
if ele, ok := lru.cacheMap[k]; ok {
lru.list.MoveToFront(ele)
return ele.Value.(*Node).V, true
}
return nil, false
}
// 設(shè)置LRU中數(shù)據(jù)
func (lru *LRU) Set(k, v interface{}) {
// 如果存在,則把數(shù)據(jù)放到最前面
if ele, ok := lru.cacheMap[k]; ok {
lru.list.MoveToFront(ele)
ele.Value.(*Node).V = v // 更新數(shù)據(jù)值
return
}
// 如果數(shù)據(jù)是滿的,先刪除數(shù)據(jù),后插入
if lru.list.Len() == lru.Size {
last := lru.list.Back()
node := last.Value.(*Node)
delete(lru.cacheMap, node.K)
lru.list.Remove(last)
} ele := lru.list.PushFront(Node{K: k, V: v})
lru.cacheMap[k] = ele
}
注意事項(xiàng)
上述的容器都不是 goroutines 安全的
1、上面的lr 也不是 goroutines 安全的
2、Ring 中不建議在 Do 方法中修改 Ring 的指針,行為是未定義的
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教。
您可能感興趣的文章:- Golang中switch語句和select語句的用法教程
- Golang 編譯成DLL文件的操作
- golang調(diào)用c實(shí)現(xiàn)的dll接口細(xì)節(jié)分享
- Golang如何調(diào)用windows下的dll動(dòng)態(tài)庫中的函數(shù)
- golang實(shí)踐-第三方包為私有庫的配置方案
- 完美解決golang go get私有倉庫的問題
- golang gopm get -g -v 無法獲取第三方庫的解決方案
- golang switch語句的靈活寫法介紹