go语言:字典、字符串

1. 字典:

go语言中的字典与C++的map类似,是key-value类型的数据结构。

1.1 数组\切片 与 字典的比较:

数组切片让我们具备了可以操作一块连续内存的能力,它是对同质元素的统一管理。而字典则是对关联性数据的进行操作的数据结构。

1.2 字典的创建:

创建字典可以使用 make 函数,但这种方法创建的字典是空的,长度为零,内部没有任何元素。
如果需要给字典提供初始化的元素,则需要使用另一种创建字典的方式。

字典类型使用 map[key]value 表示。

package main
import "fmt"

func main() {
	//method 1: make
    var m1 map[int]string = make(map[int]string)
    fmt.Println(m1, len(m1))

	//method 2:
	var m2 map[int]string = map[int]string{
		90:"excellent",
		80:"good",
		60:"pass",
	}
	fmt.Println(m2, len(m2))
}

------
map[] 0
map[60:pass 80:good 90:excellent] 3

make函数可以传递一个整型值,在创建字典时提前分配好内存,避免字典在长大的过程中要经历的多次扩容操作:

func main() {
    var m map[int]string = make(map[int]string, 16)
}

1.3 字典的读写:

同C++中的map一样,Go语言中的字典也可以使用中括号来读写内部元素,使用delete函数来删除元素。

package main
import "fmt"

func main() {
	var fruits map[string]int = map[string]int {
		"apple":2,
		"banana":5,
		"orange":8,
	}
	var price = fruits["apple"]
	fmt.Println(price)

	fruits["pear"] = 3		//增加元素
	fmt.Println(fruits["pear"])

	delete(fruits, "pear")	//delete删除元素
	fmt.Println(fruits)
}

------
2
3
map[apple:2 banana:5 orange:8]

当delete删除操作对应的key不存在时,delete函数会静默处理,delete没有返回值。因此无法直接判断delete操作是否真的删除了某个元素,只能通过判断字典的长度信息 或者提前尝试读取key对应的value。

当读操作时,如果key不存在,也不会抛出异常,它会返回value类型对应的零值。
key和value可能恰好就是零值,所以不能通过读操作的返回值直接判断key是否存在,而是要使用到字典的特殊语法:函数的“多态返回值”(一个函数的返回值可以有多个)。

package main
import "fmt"

func main() {
    var fruits map[string]int = map[string]int {
		"apple":2,
		"banana":5,
		"ornage":8,
	}
	var price, result = fruits["pear"];
	if result {
		fmt.Println(price)
	} else {
        fmt.Println("key is not exists")
	}

	var score, ok = fruits["apple"]
	if ok {
		fmt.Println(score)
	} else {
		fmt.Println("key is not exists")
	}
}

------
key is not exists
2

1.4 字典的遍历:

字典的遍历提供两种方式,一种是可以同时获取key和value的值,一种是只获取key的值。
字典的遍历需要使用Go语言的 range 关键字。

package main
import "fmt"

func main() {
    var fruits map[string]int = map[string]int {
		"apple":2,
		"banana":5,
		"ornage":8,
	}

	//method 1:
	//格式: for key value := range map {}
	for name, score := range fruits {
        fmt.Println(name, score)
    }

	//method 2:
	//格式: for key := range map {}
	for name := range fruits {
        fmt.Println(name)
	}
}

------
apple 2
banana 5
ornage 8
apple
banana
ornage

Go语言的字典没有提供 keys() 和 values() 这样的获取key或者value列表的方法,需要自行循环遍历获取:

package main
import "fmt"

func main() {
    var fruits map[string]int = map[string]int {
        "apple":2,
        "banana":5,
        "ornage":8,
    }

    var names []string = make([]string, 0, len(fruits))
    var scores []int = make([]int, 0, len(fruits))

    for name, score := range fruits {
        names = append(names, name)
        scores = append(scores, score)
    }

    fmt.Println(names)
    fmt.Println(scores)
}
------
[apple banana ornage]
[2 5 8]

1.5 线程(协程)安全:

Go语言内置的字典不是线程安全的,如果需要线程安全,必须使用锁来控制。

1.6 字典变量中存放的值:

字典里存放的只是一个地址指针,这个指针指向字典的头部对象。
所以字典占用的空间是一个字,即一个指针的大小,在32位机器上是4个字节,在64位机器上是8个字节。

请添加图片描述

2. 字符串:

Go语言中的字符串的每个节点是 不定长的,其中 英文字符 占用 1个字节,非英文字符占用多个字节。
这意味着无法通过位置来快速定位出一个完整的字符来,而必须通过遍历的方式来逐个获取单个字符。

Go语言中的字符类型是 rune,rune是一个衍生类型,在内存中使用 int32 类型的4个字节存储:

type rune int32

Go语言中的字符串并不是用 rune字符 来实现的,而是一种“字节串”,即按照节点元素的实际大小来分配内存:例如一个英文字符分配 1字节,一个中文字符分配 3字节。
如果使用 字符串 来表示字符串会造成空间浪费,因为所有的英文字符本来只需要一个字节来表示,用 rune 字符来表示的话那么剩余的3个字节都是零。但使用 rune来完全实现字符串有一个好处,那就是可以快速定位节点位置,因为所有节点占用的内存都是等长的。

图示 字节byte 与 字符rune 的区别:

请添加图片描述

其中,codepoint是每个字的真实偏移量,Go语言的字符采用 utf8 编码,其中中文汉字占用 3个字节,英文字符占用 1个字节。
len() 函数得到的是 字节的数量,通过下标来访问字符串得到的是 字节。

2.1 字符串的内存结构:

字符串的内存结构类似于切片的结构,编译器为其分配了头部字段来存储长度信息和 指向底层字节数组的指针,如下图所示:

请添加图片描述

当我们将一个字符串赋值给另一个字符串变量时,发生的是浅拷贝,底层的字节数组是共享的,只是浅拷贝了头部字段。

2.2 字符串是只读的:

编译器禁止使用字符串下标来直接赋值,只能通过下标读取字符串上指定位置的字节。

package main
import "fmt"

func main() {
    var s = "hello"
    fmt.Printf("%c\n", s[0])	//正确!输出: h

    s[0] = 'w'		//错误!编译器:cannot assign to s[0] (strings are immutable)
}

2.3 字符串的切割:

字符串的实现上与切片类似,字符串也同样支持切割操作,切割后子串与母串共享底层的字节数组。

package main
import "fmt"

func main() {
    var s1 string = "hello world";
    var s2 string = s1[2:5]
    fmt.Println(s1)
    fmt.Println(s2)
}

------
hello world 
llo

2.4 字节切片和字符串的相互转换:

在使用Go语言进行网络编程时,经常需要将来自网络的字节流转换成内存字符串,同时也需要将内存字符串转换成网络字节流。Go语言内置了字节切片和字符串的相互转换语法。

package main
import "fmt"

func main() {
    var s1 string = "hello world"
    var b []byte = []byte(s1)		//字符串转成字节切片
    var s2 string = string(b)		//字节切片转成字符串

	fmt.Println(b)
	fmt.Println(s2)	
}

------
[104 101 108 108 111 32 119 111 114 108 100]
hello world

当字节切片与字符串进行相互转换时,底层字节数组是不共享的,而是会进行拷贝,如果内容很大,那么转换操作需要一定的成本。

转换时需要进行拷贝的原因是:字节切片的底层数组内容是可以进行修改的,而字符串的底层字节数组是只读的,如果共享底层数组,就会导致字符串的只读属性不再成立。

参考内容:
https://zhuanlan.zhihu.com/p/50047198
https://zhuanlan.zhihu.com/p/50399072

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值