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 相关文章推荐
golang判断key是否在map中的代码
Apr 24 Golang
Golang 正则匹配效率详解
Apr 25 Golang
彻底理解golang中什么是nil
Apr 29 Golang
golang 在windows中设置环境变量的操作
Apr 29 Golang
浅谈golang 中time.After释放的问题
May 05 Golang
Golang Gob编码(gob包的使用详解)
May 07 Golang
关于golang高并发的实现与注意事项说明
May 08 Golang
Go语言基础切片的创建及初始化示例详解
Nov 17 Golang
如何利用golang运用mysql数据库
Mar 13 Golang
实现GO语言对数组切片去重
Apr 20 Golang
Golang map映射的用法
Apr 22 Golang
基于Python实现西西成语接龙小助手
Aug 05 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生成PDF方法详解
2015/01/23 PHP
php 读取文件夹下所有图片、文件的实例
2018/10/17 PHP
TNC vs BOOM BO3 第一场2.13
2021/03/10 DOTA
js tab 选项卡
2009/04/26 Javascript
jQuery仿gmail实现fixed布局的方法
2015/05/27 Javascript
JavaScript中的条件判断语句使用详解
2015/06/03 Javascript
使用Javascript写的2048小游戏
2015/11/25 Javascript
javascript html实现网页版日历代码
2016/03/08 Javascript
通过构造函数实例化对象的方法
2017/06/28 Javascript
基于IView中on-change属性的使用详解
2018/03/15 Javascript
父组件中vuex方法更新state子组件不能及时更新并渲染的完美解决方法
2018/04/25 Javascript
vue axios 简单封装以及思考
2018/10/09 Javascript
微信小程序实现动态获取元素宽高的方法分析
2018/12/10 Javascript
Javascript实现关闭广告效果
2021/01/29 Javascript
[02:51]DOTA2 Supermajor小组分组对阵抽签仪式
2018/06/01 DOTA
[01:32:22]DOTA2-DPC中国联赛 正赛 Ehome vs VG BO3 第一场 2月5日
2021/03/11 DOTA
使用Python求解最大公约数的实现方法
2015/08/20 Python
python 判断是否为正小数和正整数的实例
2017/07/23 Python
Python IDLE清空窗口的实例
2018/06/25 Python
python实现的爬取电影下载链接功能示例
2019/08/26 Python
使用Python给头像戴上圣诞帽的图像操作过程解析
2019/09/20 Python
简单了解Django ORM常用字段类型及参数配置
2020/01/07 Python
关于Tensorflow分布式并行策略
2020/02/03 Python
执行Python程序时模块报错问题
2020/03/26 Python
python画图时设置分辨率和画布大小的实现(plt.figure())
2021/01/08 Python
佳能加拿大网上商店:Canon eStore Canada
2018/04/04 全球购物
介绍一下如何优化MySql
2016/12/20 面试题
人力资源经理的岗位职责范本
2014/02/28 职场文书
中式婚礼主持词
2014/03/13 职场文书
采购求职信
2014/03/17 职场文书
《第一朵杏花》教学反思
2014/04/16 职场文书
党内外群众意见范文
2015/06/02 职场文书
班主任寄语2016
2015/12/04 职场文书
python xlwt模块的使用解析
2021/04/13 Python
MySQL里面的子查询的基本使用
2021/08/02 MySQL
详解Mysql数据库平滑扩容解决高并发和大数据量问题
2022/05/25 MySQL