Golang: 内建容器的用法


Posted in Golang onMay 05, 2021

1.数组

数组是值类型

[10]int 和 [20]int是不同类型

调用func(arr [10]int)会拷贝数组

在go语言中一般不直接使用数据

package main
import "fmt"
func updateArr(arr *[5]int) {
	arr[0] = 100
}
func updateArrThroughSlice(arr []int)  {
	arr[0] = 100
}
func main() {
	//创建一个数据
	var arr [5]int
	arr2 := [5]int{1, 2, 3, 4, 5}
	//长度让编译器来数
	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)
	//定义二维数组 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)
	//遍历数据
	//for i:=0;i<len(arr3);i++{
	//	fmt.Println(arr3[i])
	//}
	for num, v := range arr2 {
		fmt.Printf("第%d个元素为:%d\n", num, v)
	}
	//数据是值类型,通过指针可以改变值的大小
	fmt.Println("update before")
	fmt.Println(arr2)
	updateArr(&arr2) //传入arr3的地址
	fmt.Println("update after")
	fmt.Println(arr2)
	
	//通过Slice改变数据
	fmt.Println("update before")
	fmt.Println(arr3)
	updateArrThroughSlice(arr3[:]) //传入Slice
	fmt.Println("update after")
	fmt.Println(arr3)
}

2.Slice(切片)

2.1 Slice的实现

Slice本身没有数据,是对底层array的一个view

Golang: 内建容器的用法

Slice内部有个指针(ptr)指向开头的元素,Slice有长度(len),容量(cap);cap代表从指针(ptr)开始到数组(arr)末尾的长度,Slice在扩展的时候不能超过cap.

package main
import "fmt"
func updateSlice(s []int) {
	s[0] = 100
}
func main() {
	arr := [...]int{0, 1, 2, 3, 4, 5, 6, 7}
	//创建一个Slice
	s1 := arr[:]
	s2 := arr[2:6]
	fmt.Printf("s1:%v\ns2:%v\n", s1, s2)
	//改变Slice内部元素
	updateSlice(s2)
	fmt.Println(s2)
	//ReSlice:对Slice再进行一次Slice操作
	s3 := s1[:5]
	fmt.Println(s3)
	s3 = s3[:2]
	fmt.Println(s3)
}

2.2 Slice的扩展

s[i]不可以超越len(i),向后扩展不可以超过底层数组cap(s)

Golang: 内建容器的用法

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"
//查看操作系统怎么扩充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}
	//添加元素时如果超越cap,系统会重新分配更大的底层数组
	//由于值传递的关系,必须接收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)
	//创建一个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"
)
//查看操作系统怎么扩充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]
	//创建一个len为16的Slice
	s2 := make([]int, 16)
	//创建一个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")
	//删除下标为3的元素
	//通过...append s2下标为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的操作

创建: make(map[string]int)

获取元素:m[key]

key不存在时,获得Value类型的初始值

用value,ok := m[key]来判断是否存在key

用delete删除一个key

使用range遍历key,或者遍历key, value对

不保证遍历顺序,如需顺序,需手动对key排序

使用len获得元素个数

package main
import "fmt"
func main() {
	//创建一个map
	//map中的key是无序的,是一个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)
	
	//当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的内建类型都可以作为key

Struct类型不包含上述字段,也可作为key

3.3 Map的例题:寻找最长不含有重复字符的子串

Golang: 内建容器的用法

/*
当前一个字符串,从左往后开始扫描,只要扫描一遍就可以,如果扫到X的位置,看到一个字母X应该怎么做呢
首先,记录一个start表示当前找到的最长不含有重复字符的子串的开始,保证start到X之前的子串是不含有重复字符的,
之后,需要查看从start到X-1这个位置之间有没有X,使用一个叫lastOccurred[x]记录X最后出现的位置在哪里,使用map会有三种情况:1.x重来没有出现过,或者x出现在start之前,若x出现在start之前,最长的子串+1; 2.lastOccurred[x]出现在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]有可能不存在;若不存在出现0,会影响运算
		if lastl, ok:= lastOccurred[ch];ok && lastl >= start{
			start = lastl + 1
		}
		//stat到i结束
		if x-start + 1 > maxLength{
			maxLength = x -start + 1
		}
		lastOccurred[ch] = x
	}
	return maxLength
}
func main() {
	fmt.Println(lengthOfNonRepeatingSubStr("hellohello"))
}

4.rune

rune相当于go的char

使用range遍历pos,rune对

使用utf8.RuneCountlnString获得字符数量

使用len获得字节长度

使用[]byte获得字节

package main
import (
	"fmt"
	"unicode/utf8"
)
func main() {
	//英文占一个字节,中文占三个字节
	s := "yes我爱CSDN!"
	fmt.Println(len(s)) // 14
	//%X十六进制,大写字符,每个字节两个字符
	//796573E68891E788B14353444E21
	fmt.Printf("%X\n",[]byte(s))
	//%T 相应值的类型
	//使用for range遍历字符串时,会默认将byte(int8)类型转化为rune(int32)类型,因为go采用UTF-8编码 可变长的编码
	for _,b := range s{
		fmt.Printf("%T %X\n",b,b)
	}
	for _,b := range []byte(s){
		fmt.Printf("%T %X\n",b,b)
	}
	//打印字符的个数
	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:]
		//相应Unicode码点所表示的字符
		fmt.Printf("%c",ch)
	}
	//获取第几个字符是谁
	for i, ch := range []rune(s) {
		fmt.Printf("(%d %c) ", i, ch)
	}
	fmt.Println()
}

4.1 Map的例题:寻找最长不含有重复字符的子串(国际版)

//国际版
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]有可能不存在;若不存在出现0,会影响运算
		if lastI, ok := lastOccurred[ch]; ok && lastI >= start {
			start = lastI + 1
		}
		//start到i结束
		if i-start+1 > maxLength {
			maxLength = i - start + 1
		}
		lastOccurred[ch] = i
	}
	return maxLength
}

补充:Golang 容器的学习与实践

Golang 提供了几个简单的容器供我们使用,本文在介绍几种 Golang 容器的基础上,实现一个基于 Golang 容器的LRU算法。

容器介绍

Golang 容器位于 container 包下,提供了三种包供我们使用,heap、list、ring. 下面我们分别学习。

heap

heap 是一个堆的实现。一个堆正常保证了获取/弹出最大(最小)元素的时间为log n、插入元素的时间为 log n.

Golang堆实现接口如下:

// 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 实现的。

// src/sort/
type Interface interface {
 Len() int
 Less(i, j int) bool
 Swap(i, j int)
}

因此,如果要使用官方提供的 heap,需要我们实现如下几个接口:

Len() int {} // 获取元素个数
Less(i, j int) bool  {} // 比较方法
Swap(i, j int) // 元素交换方法
Push(x interface{}){} // 在末尾追加元素
Pop() interface{} // 返回末尾元素

然后在使用时,我们可以使用如下几种方法:

// 初始化一个堆
func Init(h Interface){}
// push一个元素倒堆中
func Push(h Interface, x interface{}){}
// pop 堆顶元素
func Pop(h Interface) interface{} {}
// 删除堆中某个元素,时间复杂度 log n
func Remove(h Interface, i int) interface{} {}
// 调整i位置的元素位置(位置I的数据变更后)
func Fix(h Interface, i int){}

list 链表

list 实现了一个双向链表,链表不需要实现 heap 类似的接口,可以直接使用。

链表的构造:

// 返回一个链表对象
  func New() *List {}

官方提供了丰富的方法供我们操作列表,方法如下:

// 返回链表的长度
func (l *List) Len() int {}
// 返回链表中的第一个元素
func (l *List) Front() *Element {}
// 返回链表中的末尾元素
func (l *List) Back() *Element {}
// 移除链表中的某个元素
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 {}
// 移动e某个元素到表头
func (l *List) MoveToFront(e *Element) {}
// 移动e到队尾
func (l *List) MoveToBack(e *Element) {}
// 移动e到mark之前
func (l *List) MoveBefore(e, mark *Element) {}
// 移动e 到mark 之后
func (l *List) MoveAfter(e, mark *Element) {}
// 追加到队尾
func (l *List) PushBackList(other *List) {}
// 将链表list放在队列前
func (l *List) PushFrontList(other *List) {}

我们可以通过 Value 方法访问 Element 中的元素。除此之外,我们还可以用下面方法做链表遍历:

// 返回下一个元素
func (e *Element) Next() *Element {}
// 返回上一个元素
func (e *Element) Prev() *Element {}
下面是队列的遍历的例子:
// l 为队列,
for e := l.Front(); e != nil; e = e.Next() {
  //通过 e.Value 做数据访问
}

ring 循环列表

container 中的循环列表是采用链表实现的。

// 构造一个包含N个元素的循环列表
func New(n int) *Ring {}
// 返回列表下一个元素
func (r *Ring) Next() *Ring {}
// 返回列表上一个元素
func (r *Ring) Prev() *Ring {}
// 移动n个元素 (可以前移,可以后移)
func (r *Ring) Move(n int) *Ring {}
// 把 s 链接到 r 后面。如果s 和r 在一个ring 里面,会把r到s的元素从ring 中删掉
func (r *Ring) Link(s *Ring) *Ring {}
// 删除n个元素 (内部就是ring 移动n个元素,然后调用Link)
func (r *Ring) Unlink(n int) *Ring {}
// 返回Ring 的长度,时间复杂度 n
func (r *Ring) Len() int {}
// 遍历Ring,执行 f 方法 (不建议内部修改ring)
func (r *Ring) Do(f func(interface{})) {}

访问 Ring 中元素,直接 Ring.Value 即可。

容器的使用

下面,我们通过 map 和 官方包中的双向链表实现一个简单的 lru 算法,用来熟悉golang 容器的使用。

LRU 算法 (Least Recently Used),在做缓存置换时用的比较多。逐步淘汰最近未使用的 cache,而使我们的缓存中持续保持着最近使用的数据。

package main 
import "fmt"
import "container/list"
 
// lru 中的数据
type Node struct {
  K, V interface{}
}
 
// 链表 + map
type LRU struct {
  list *list.List
  cacheMap map[interface{}]*list.Element
  Size int
}
 
// 初始化一个LRU
func NewLRU(cap int) *LRU {
  return &LRU{
    Size: cap,
    list: list.New(),
    cacheMap: make(map[interface{}]*list.Element, cap),
  }
}
 
// 获取LRU中数据
func (lru *LRU) Get(k interface{}) (v interface{}, ret bool) {
  // 如果存在,则把数据放到链表最前面
  if ele, ok := lru.cacheMap[k]; ok {
    lru.list.MoveToFront(ele)
    return ele.Value.(*Node).V, true
  }
  return nil, false
}
// 设置LRU中数据
func (lru *LRU) Set(k, v interface{}) {
  // 如果存在,则把数据放到最前面
  if ele, ok := lru.cacheMap[k]; ok {
    lru.list.MoveToFront(ele)
    ele.Value.(*Node).V = v // 更新数据值
    return
  }
  // 如果数据是满的,先删除数据,后插入
  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
}

注意事项

上述的容器都不是 goroutines 安全的

1、上面的lr 也不是 goroutines 安全的

2、Ring 中不建议在 Do 方法中修改 Ring 的指针,行为是未定义的

以上为个人经验,希望能给大家一个参考,也希望大家多多支持三水点靠木。如有错误或未考虑完全的地方,望不吝赐教。

Golang 相关文章推荐
goland 清除所有的默认设置操作
Apr 28 Golang
golang goroutine顺序输出方式
Apr 29 Golang
使用Golang的channel交叉打印两个数组的操作
Apr 29 Golang
golang协程池模拟实现群发邮件功能
May 02 Golang
go类型转换及与C的类型转换方式
May 05 Golang
golang 实现时间戳和时间的转化
May 07 Golang
Golang 实现获取当前函数名称和文件行号等操作
May 08 Golang
Go 语言下基于Redis分布式锁的实现方式
Jun 28 Golang
Go语言安装并操作redis的go-redis库
Apr 14 Golang
Golang map映射的用法
Apr 22 Golang
Go gorilla securecookie库的安装使用详解
Aug 14 Golang
Go标准容器之Ring的使用说明
May 05 #Golang
go语言中GOPATH GOROOT的作用和设置方式
go设置多个GOPATH的方式
May 05 #Golang
浅谈golang 中time.After释放的问题
May 05 #Golang
golang 定时任务方面time.Sleep和time.Tick的优劣对比分析
May 05 #Golang
golang日志包logger的用法详解
May 05 #Golang
golang elasticsearch Client的使用详解
May 05 #Golang
You might like
PHP生成网页快照 不用COM不用扩展.
2010/02/11 PHP
PHP var_dump遍历对象属性的函数与应用代码
2010/06/04 PHP
jquery easyui的tabs使用时的问题
2010/03/23 Javascript
js实现图片旋转的三种方法
2014/04/10 Javascript
node.js应用后台守护进程管理器Forever安装和使用实例
2014/06/01 Javascript
使用AngularJS创建自定义的过滤器的方法
2015/06/18 Javascript
JS数组操作(数组增加、删除、翻转、转字符串、取索引、截取(切片)slice、剪接splice、数组合并)
2016/05/20 Javascript
JavaScript数组合并的多种方法
2016/05/22 Javascript
浅析Javascript中bind()方法的使用与实现
2016/05/30 Javascript
AngularJS基础 ng-model-options 指令简单示例
2016/08/02 Javascript
JavaScript实现数组降维详解
2017/01/05 Javascript
详解Vue项目在其他电脑npm run dev运行报错的解决方法
2018/10/29 Javascript
jquery 验证用户名是否重复代码实例
2019/05/14 jQuery
JavaScript实现随机五位数验证码
2019/09/27 Javascript
jQuery实现form表单基于ajax无刷新提交方法实例代码
2019/11/04 jQuery
利用React高阶组件实现一个面包屑导航的示例
2020/08/23 Javascript
Python字符遍历的艺术
2008/09/06 Python
Python实现读取目录所有文件的文件名并保存到txt文件代码
2014/11/22 Python
python实现决策树、随机森林的简单原理
2018/03/26 Python
pycharm重置设置,恢复默认设置的方法
2018/10/22 Python
python实现鸢尾花三种聚类算法(K-means,AGNES,DBScan)
2019/06/27 Python
详解python中__name__的意义以及作用
2019/08/07 Python
使用jupyter Nodebook查看函数或方法的参数以及使用情况
2020/04/14 Python
Python持续监听文件变化代码实例
2020/07/22 Python
css3实现冲击波效果的示例代码
2018/01/11 HTML / CSS
PHP如何调用MYSQL存储过程
2014/05/30 面试题
中级会计职业生涯规划范文
2014/01/16 职场文书
十八届三中全会宣传方案
2014/02/21 职场文书
青年文明号服务承诺
2014/03/31 职场文书
2014年人事部工作总结
2014/12/03 职场文书
精神文明建设先进个人事迹材料
2014/12/24 职场文书
国庆节慰问信
2015/02/15 职场文书
2015年反腐倡廉工作总结
2015/05/14 职场文书
2016年暑期社会实践活动总结报告
2016/04/06 职场文书
详解python字符串驻留技术
2021/05/21 Python
Springboot集成kafka高级应用实战分享
2022/08/14 Java/Android