map中的key

Go语言中对map中的key的数据类型有如下要求:

  • map使用哈希表, key必须可以比较相等
  • 除了slice, map, function的内建类型都可以作为key
  • struct类型如果不包含第二条中排除的数据类型, 也可以做为key

map的声明与赋值

1
2
3
4
5
6
7
8
9
10
11
12
package main

import "fmt"

func main() {
m := map[string]string {
"name": "larry",
"company": "PolarSnow Inc.",
"website": "https://lvrui.io",
}
fmt.Println(m)
}

执行结果:

1
map[name:larry company:PolarSnow Inc. website:https://lvrui.io]

嵌套Map

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package main

import "fmt"

func main() {
m := map[string]map[string]string {
"member": {
"name": "larry",
"company": "PolarSnow Inc.",
"website": "https://lvrui.io",
},
}
fmt.Println(m["member"]["name"])
}

执行结果:

1
larry

其他声明map的方式

1
2
3
4
5
6
7
8
9
package main

import "fmt"

func main() {
m := make(map[string]string) // m == empty map
var mm map[string]string // mm == nil
fmt.Println(m, mm)
}

执行结果:

1
map[] map[]

map的遍历

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main

import "fmt"

func main() {
m := map[string]string {
"name": "larry",
"company": "PolarSnow Inc.",
"website": "https://lvrui.io",
}

for k, v := range m {
fmt.Println(k, v)
}
}

执行结果:

1
2
3
company PolarSnow Inc.
website https://lvrui.io
name larry

注意: map是无序的

读取map中的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main

import "fmt"

func main() {
m := map[string]string {
"name": "larry",
"company": "PolarSnow Inc.",
"website": "https://lvrui.io",
}

fmt.Println(m["website"])

fmt.Println(m["site"]) // map中无此key, 但是不会报错, 拿到的是string的zero value

// 通常判断map取值有没有成功的方式
if address, ok := m["website"]; ok {
fmt.Println(address)
} else {
fmt.Println("Key does not exist")
}

}

执行结果:

1
2
3
https://lvrui.io

https://lvrui.io

增加/修改元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main

import "fmt"

func main() {
m := map[string]string {
"name": "larry",
"company": "PolarSnow Inc.",
"website": "https://lvrui.io",
}

fmt.Println(m)

m["country"] = "China"

fmt.Println(m)

m["name"] = "Polar Snow"

fmt.Println(m)

}

执行结果:

1
2
3
map[company:PolarSnow Inc. website:https://lvrui.io name:larry]
map[name:larry company:PolarSnow Inc. website:https://lvrui.io country:China]
map[name:Polar Snow company:PolarSnow Inc. website:https://lvrui.io country:China]

Go对map添加/修改元素的操作形式, 与Python相同

删除元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main

import "fmt"

func main() {
m := map[string]string {
"name": "larry",
"company": "PolarSnow Inc.",
"website": "https://lvrui.io",
}

fmt.Println(m)

delete(m, "company")

fmt.Println(m)

}

执行结果:

1
2
map[name:larry company:PolarSnow Inc. website:https://lvrui.io]
map[name:larry website:https://lvrui.io]

获取map元素的个数

1
2
3
4
5
6
7
8
9
10
11
12
13
package main

import "fmt"

func main() {
m := map[string]string {
"name": "larry",
"company": "PolarSnow Inc.",
"website": "https://lvrui.io",
}

fmt.Println(len(m))
}

执行结果:

1
3

map的应用

取出所有key和所有value

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main

import "fmt"

func main() {
m := map[string]string {
"name": "larry",
"company": "PolarSnow Inc.",
"website": "https://lvrui.io",
}

var key []string
var value []string

for k, v := range m {
key = append(key, k)
value = append(value, v)
}

fmt.Println(key)
fmt.Println(value)

}

执行结果:

1
2
[name company website]
[larry PolarSnow Inc. https://lvrui.io]

Go语言的range关键字用于在for语句中遍历数组Array, Slice, Map和Channel中的元素.

遍历Array

1
2
3
4
5
6
7
8
9
10
11
12
13
package main

import "fmt"

func main() {
// 定义一个array
arr := [4]string {"a", "b", "c", "d"}
// 遍历array
for i, v := range arr {
fmt.Println(i, v)
}

}

执行结果:

1
2
3
4
0 a
1 b
2 c
3 d

遍历Slice

1
2
3
4
5
6
7
8
9
10
11
12
13
package main

import "fmt"

func main() {
// 定义一个slice
arr := []string {"a", "b", "c", "d"}
// 遍历array
for i, v := range arr {
fmt.Println(i, v)
}

}

执行结果:

1
2
3
4
0 a
1 b
2 c
3 d

遍历Map

1
2
3
4
5
6
7
8
9
10
11
12
13
package main

import "fmt"

func main() {
// 定义一个map
m := map[string]string{"k1": "polarsnow", "k2": "larry", "k3": "lyu"}
// 遍历array
for k, v := range m {
fmt.Println(k, v)
}

}

执行结果:

1
2
3
k1 polarsnow
k2 larry
k3 lyu

如果使用MySQL 8.0+版本提供的命令行工具mysqldump来导出低于8.0版本的MySQL数据库到SQL文件,会出现Unknown table 'column_statistics' in information_schema的错误,因为早期版本的MySQL数据库的information_schema数据库中没有名为COLUMN_STATISTICS的数据表。

解决方法: 使用8.0以前版本MySQL附带的mysqldump工具,最好使用待备份的MySQL服务器版本对应版本号的mysqldump工具.

根据软件设计原则中开放封闭原则的指导思想, 一个类写好后,尽量不要修改里面的内容, 而是通过添加新的继承应对变化, 简单工厂不符合这个设计原则, 所以本篇文章将使用伪代码介绍工厂方法设计模式的使用

背景: 现公司监控系统报警需要对接企业微信公众号, 由于未认证企业微信推送消息的限制, 默认每天推送条数上限为6000条, 考虑到报警系统多, 规则没有收敛, 接收的人员多, 每天6000条可能不够用, 所以需要创建多个未认证的企业微信账号用于发送报警信息. 我们将以此需求为背景, 演示工厂方法的设计模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66

class WeChat:

def send_message(self, content):
pass

def send_image(self, imageid):
pass


class AccountA(WeChat):

def send_message(self, content):
print("使用企业微信账号A推送信息: ", content)

def send_image(self, imageid):
print("使用企业微信账号A推送图片: ", imageid)


class AccountB(WeChat):

def send_message(self, content):
print("使用企业微信账号B推送信息: ", content)

def send_image(self, imageid):
print("使用企业微信账号B推送图片: ", imageid)


class WeChatFactory:

def create_wechat(self):
pass


class AccountAFactory(WeChatFactory):

def create_wechat(self):
return AccountA()


class AccountBFactory(WeChatFactory):

def create_wechat(self):
return AccountB()


if __name__ == "__main__":
# 实例化账号A
wechat_factory_a = AccountAFactory()
# 创建账号A的微信对象
wechat1 = wechat_factory_a.create_wechat()
wechat2 = wechat_factory_a.create_wechat()
wechat3 = wechat_factory_a.create_wechat()
# 使用账号A对象发送信息
wechat1.send_message(content="haha")
wechat2.send_message(content="hehe")
wechat3.send_message(content="xixi")

# 实例化账号B
wechat_factory_b = AccountBFactory()
# 创建账号B的微信对象
wechat4 = wechat_factory_b.create_wechat()
wechat5 = wechat_factory_b.create_wechat()
# 使用账号B对象发送信息
wechat4.send_message(content="heihei")
wechat5.send_message(content="pupu")

执行结果:

1
2
3
4
5
使用企业微信账号A推送信息:  haha
使用企业微信账号A推送信息: hehe
使用企业微信账号A推送信息: xixi
使用企业微信账号B推送信息: heihei
使用企业微信账号B推送信息: pupu

如果此时, 两个微信账号都不够用了, 需要增加第三个账号时, 所有的类都不需要修改, 只需创建新的类即可, 符合开放封闭原则

1
2
3
4
5
6
7
8
9
10
11
12
13
class AccountC(WeChat):

def send_message(self, content):
print("使用企业微信账号C推送信息: ", content)

def send_image(self, imageid):
print("使用企业微信账号C推送图片: ", imageid)


class AccountCFactory(WeChatFactory):

def create_wechat(self):
return AccountC()

sorted函数

sorted(iterable,key,reverse)

  • iterable 待排序的可迭代对象
  • key 对应的是个函数, 该函数用来决定选取用哪些值来进行排序
  • reverse 反转排序

对key排序

1
2
3
4
d: dict = {"p": 59, "o": 9, "s": 5, "a": 20, "z": 18}

li: list = sorted(d.keys())
print(li)

执行结果:

1
['a', 'o', 'p', 's', 'z']

对value排序

在对value之前, 先来回炉一下Python的lambda表达式

1
2
3
4
5
6
7
f = lambda x: x+1
print(f(5))


def ff(x):
return x+1
print(ff(5))

执行结果:

1
2
6
6

以上两种方式是等价的, lambda表达式写法更简洁, 一般当做匿名表达式/匿名函数使用

lambda x:y

  • x 为入参
  • y 为计算表达式, 运算后返回

接下来进入到对字典的value排序正题, 这里就需要使用到lambda表达式

直接对值排序

1
2
3
4
d: dict = {"p": 59, "o": 9, "s": 5, "a": 20, "z": 18}

li: list = sorted(d.values())
print(li)

执行结果:

1
[5, 9, 18, 20, 59]

在包含Key的情况下对值排序

1
2
3
4
d: dict = {"p": 59, "o": 9, "s": 5, "a": 20, "z": 18}

li: list = sorted(d.items(), key=lambda x: x[1])
print(li)

执行结果:

1
[('s', 5), ('o', 9), ('z', 18), ('a', 20), ('p', 59)]

d.items() 返回以元组形式构成的列表 dict_items([('p', 59), ('o', 9), ('s', 5), ('a', 20), ('z', 18)]) (列表为可迭代对象)

lambda x: x[1] lambda表达式中, x为入参, 每次x的值为一个d.items()中的元素(一个元组), 第一个入参为('p', 59) 冒号后面的x[1]是运算表达式, 意思是取元组中的第二个元素返回(索引为1), 返回的元素即为排序的依据. 所以如果需要对key进行排序, 也可以在表达式里写 x[0]

aiohttp+uvloop

main.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import asyncio
import uvloop
from aiohttp import web
from utils.logger_helper import LogFactory

logger = LogFactory.get_logger()

def main():
logger.info("Server Starting at {0};{1}".format(cfg.host, cfg.port))
app = web.Application()
setup_routes(app)
app.on_shutdown.append(shutdown)
web.run_app(app, host=cfg.host, port=cfg.port)

if __name__ == "__main__":
# 使用 uvloop 替换掉 asyncio 默认的事件循环
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())

# 启动 Server
try:
main()
except Exception as e:
logger.error(e)

aiohttp+uvloop+gunicorn

main.py

1
2
3
4
5
6
7
# For Gunicorn
async def web_app():
logger.info("Server Starting at {0};{1}".format(cfg.host, cfg.port))
app = web.Application()
setup_routes(app)
app.on_shutdown.append(shutdown)
return app

gunicorn ... --worker-class aiohttp.worker.GunicornUVLoopWebWorker

在启动gunicorn的时候, 指定uvloop的worker-class即可

How to use uvloop in an asyncio application

1
2
3
import asyncio
import uvloop
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())

How to use uvloop with Tornado

1
2
3
4
5
6
7
from tornado.platform.asyncio import AsyncIOMainLoop
import asyncio
import uvloop

asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
AsyncIOMainLoop().install()
asyncio.get_event_loop().run_forever()

参考文档:

最近在发送邮件日报时, 使用smtplib库抛了一个异常raise SMTPException("No suitable authentication method found.")

原因是配置了https的邮件服务器, 在初始化SMTP和login之间调用starttls()方法即可解决问题

1
2
3
4
5
6
7
import smtplib
server = smtplib.SMTP(self.server, self.port)
server.set_debuglevel(1)
server.starttls()
server.login(self.send_user, self.password)
server.sendmail(self.send_user, [to], msg.as_string())
server.quit()

抽象方法是父类的一个方法, 父类没有实现这个方法, 父类是不可以实例化的. 子类继承父类, 子类必须实现父类定义的抽象方法, 子类才可以被实例化. Python中的abc提供了@abstractmethod装饰器实现抽象方法的定义

抽象类的实现

Python 3.4+

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from abc import ABC, abstractmethod

class Foo(ABC):
@abstractmethod
def fun(self):
"""
你需要在子类中实现该方法, 子类才允许被实例化
"""


class SubFoo(Foo):

def fun(self):
print("子类实现父类的抽象方法")


if __name__ == "__main__":

sf = SubFoo()
sf.fun()

Python 3.0 - 3.3 版本

1
2
3
4
5
6
from abc import abstractmethod, ABCMeta

class Foo(metaclass=ABCMeta):
@abstractmethod
def fun(self):
pass

Python 2 版本

1
2
3
4
5
6
7
from abc import ABCMeta, abstractmethod

class FooBar():
__metaclass__ = ABCMeta
@abstractmethod
def fun(self):
pass

浅谈抽象类

类, 是从一堆对象中抽象出来的, 比如猫类,狗类,人类

抽象类, 是从一堆类中抽象出来的, 比如上面的三个类可以抽取出动物类

抽象类的特点是不能给实例化, 只能被子类继承, 由子类实现了父类的抽象方法后, 子类才能被实例化

Python中的接口, 抽象类, 抽象函数

Python中的接口是个弱概念, 从Java中的概念延伸而来, Python中通过抽象类和抽象方法来实现一个接口, 例如Python3中, class 类继承的 abc.ABC 即为抽象类, @abstractmethod 装饰器使其装饰的函数成为抽象函数

一般情况下, Python多在单继承的情况下使用抽象类

向Slice中添加元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package main

import "fmt"

func main() {
arr := [...]string{"a", "b", "c", "d", "e", "f", "g", "h"}
s := arr[2:6]
ss := append(s, "xx")
sss := append(ss, "yy")
ssss := append(sss, "zz")
fmt.Println("s", s)
fmt.Println("ss", ss)
fmt.Println("sss", sss)
fmt.Println("ssss", ssss)
fmt.Println("arr", arr)
}

执行结果:

1
2
3
4
5
s [c d e f]
ss [c d e f xx]
sss [c d e f xx yy]
ssss [c d e f xx yy zz]
arr [a b c d e f xx yy]

从执行结果中可以看出, 向Slice中append元素后, 会覆盖掉底层Array的值, 会覆盖掉索引为ptr+len(Slice)的值. 当Slice append后, 超出了cap的长度, 底层Array中已经没有多余的值可以给他覆盖后, 这时, 新的Slice其实已经不再是老的Array的view了, Go语言会在内部创建一个新的Array, 把老数据拷贝过去, 且新的Array的长度会设置的更长一些. 上面的Demo中, ssss已经不再是原始Array的view

  • 当添加元素时, 如果超越了cap, 系统会重新分配更大的底层数组, 原来的Array如果没有被任何对象使用, 将被垃圾回收机制回收

  • 由于Go语言是值传递, 在append后, 必须接收其返回值. 原因是在append操作后, Slice的len肯定会变化, 如果超过了Array长度, cap的值也会变化, 所以需要我们使用新的Slice对象去接收新的返回值

创建Slice

上一篇文章的介绍中, 全篇都使用了Array去创建Slice, 我们可以直接创建Slice

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main

import "fmt"

func printSliceInfo(s []int) {
fmt.Printf("len=%d cap=%d\n", len(s), cap(s))
}

func main() {
var s []int // 声明创建一个int类型的Slice
// 如果变量没有被赋值, Go语言会为每个变量定义Zero Value
// Slice的Zero Value 为 nil

for i:=0; i<=59; i++ {
printSliceInfo(s)
s = append(s, i)
}
fmt.Println(s)
}

执行结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
len=0 cap=0
len=1 cap=1
len=2 cap=2
len=3 cap=4
len=4 cap=4
len=5 cap=8
len=6 cap=8
len=7 cap=8
len=8 cap=8
len=9 cap=16
len=10 cap=16
len=11 cap=16
len=12 cap=16
len=13 cap=16
len=14 cap=16
len=15 cap=16
len=16 cap=16
len=17 cap=32
len=18 cap=32
len=19 cap=32
len=20 cap=32
len=21 cap=32
len=22 cap=32
len=23 cap=32
len=24 cap=32
len=25 cap=32
len=26 cap=32
len=27 cap=32
len=28 cap=32
len=29 cap=32
len=30 cap=32
len=31 cap=32
len=32 cap=32
len=33 cap=64
len=34 cap=64
len=35 cap=64
len=36 cap=64
len=37 cap=64
len=38 cap=64
len=39 cap=64
len=40 cap=64
len=41 cap=64
len=42 cap=64
len=43 cap=64
len=44 cap=64
len=45 cap=64
len=46 cap=64
len=47 cap=64
len=48 cap=64
len=49 cap=64
len=50 cap=64
len=51 cap=64
len=52 cap=64
len=53 cap=64
len=54 cap=64
len=55 cap=64
len=56 cap=64
len=57 cap=64
len=58 cap=64
len=59 cap=64
[0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59]

从上面的执行结果可以看出, Go语言会根据使用情况自动去扩展Slice的len以及cap

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main

import "fmt"

func printSliceInfo(s []string) {
fmt.Printf("len=%d cap=%d\n", len(s), cap(s))
}

func main() {
s := []string{"a", "b", "c"} // 首先创建了个Array, 然后对其view
printSliceInfo(s)

ss := make([]string, 16, 32) // 创建一个len为16, cap为32的string类型Slice
printSliceInfo(ss)
}

执行结果:

1
2
len=3 cap=3
len=16 cap=32

拷贝Slice

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package main

import "fmt"

func printSliceInfo(s []int) {
fmt.Printf("len=%d cap=%d\n", len(s), cap(s))
}

func main() {
s := []int{2015, 5, 9} // 首先创建了个Array, 然后对其view
printSliceInfo(s)
fmt.Println("s", s)

ss := make([]int, 4, 8) // 创建一个len为16, cap为32的string类型Slice
printSliceInfo(ss)
fmt.Println("ss", ss)

copy(ss, s) // 将s切片的数据拷贝到ss切片中
fmt.Println("ss", ss)
}

执行结果:

1
2
3
4
5
len=3 cap=3
s [2015 5 9]
len=4 cap=8
ss [0 0 0 0]
ss [2015 5 9 0]

删除Slice中的元素

对于Slice的删除功能, Go语言没有提供直接的操作语句, 但是可以通过Reslice来实现

需求: 删除Slice中的第3个元素 [12, 34, 56, 78, 90, 89, 72]

思路: 使用Reslice, s[:3] + s[4:] 取得的就是删除原切片第三个元素的数据, 但Go语言也没有提供Slice相加的功能, 只能通过append函数来实现, append(s[:3], s[4:]), 问题又来了, append的源码中要求第二参数必须是可变参数, 也就是一个值一个值的写在后面 func append(slice []Type, elems ...Type) []Type

Go语言中提供了相关的语法, 来解决s[4:]变成可变参数的问题

1
2
3
4
5
6
7
8
9
10
11
package main

import "fmt"

func main() {
s := []int{12, 34, 56, 78, 90, 89, 72}
fmt.Println(s)

ss := append(s[:3], s[4:]...)
fmt.Println(ss)
}

执行结果:

1
2
[12 34 56 78 90 89 72]
[12 34 56 90 89 72]

删除头尾

删头

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package main

import "fmt"

func main() {
s := []int{12, 34, 56, 78, 90, 89, 72}
fmt.Println(s)

fmt.Println("Poping from front")
front := s[0]
ss := s[1:]
fmt.Println("取出的头部元素为", front)
fmt.Println("取出头部元素后的Slice为", ss)
}

执行结果:

1
2
3
4
[12 34 56 78 90 89 72]
Poping from front
取出的头部元素为 12
取出头部元素后的Slice为 [34 56 78 90 89 72]

删尾

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package main

import "fmt"

func main() {
s := []int{12, 34, 56, 78, 90, 89, 72}
fmt.Println(s)

fmt.Println("Poping from front")
tail := s[len(s)-1]
ss := s[:len(s)-1]
fmt.Println("取出的尾部元素为", tail)
fmt.Println("取出尾部元素后的Slice为", ss)
}

执行结果:

1
2
3
4
[12 34 56 78 90 89 72]
Poping from front
取出的尾部元素为 72
取出尾部元素后的Slice为 [12 34 56 78 90 89]

Slice的基本使用

1
2
3
4
5
6
7
8
9
10
11
12
package main

import "fmt"

func main() {
arr := [...]string{"a", "b", "c", "d", "e", "f", "g", "h"}

fmt.Println("arr[2:6]", arr[2:6])
fmt.Println("arr[:6]", arr[:6])
fmt.Println("arr[2:]", arr[2:])
fmt.Println("arr[:]", arr[:])
}

执行结果:

1
2
3
4
arr[2:6] [c d e f]
arr[:6] [a b c d e f]
arr[2:] [c d e f g h]
arr[:] [a b c d e f g h]

注意: 左开右闭区间; Slice是对数组的View(视图)

Slice是引用类型

由于Slice是对底层数组的视图, 当Slice中的元素被修改后, 同样的, 底层数组中的元素也将被修改

看以下demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main

import "fmt"

func main() {
arr := [...]string{"a", "b", "c", "d", "e", "f", "g", "h"}

s := arr[2:6]
fmt.Println("原始s变量的值", s)
fmt.Println("原始数组的值", arr)
s[0] = "xx" // 改变视图中的第一个元素
fmt.Println("修改后变量的值", s)
fmt.Println("修改后数组的值", arr)

}

执行结果:

1
2
3
4
原始s变量的值 [c d e f]
原始数组的值 [a b c d e f g h]
修改后变量的值 [xx d e f]
修改后数组的值 [a b xx d e f g h]

Slice & Array

切片和数组的写法很相似, 这里把他们放到一起区分以下

1
2
3
4
5
6
7
func funcName(arr [5]int) // 接收一个长度为5的int类型的数组(值传递)

func funcName(arr *[5]int) // 接收一个长度5的int类型的数组的指针(引用传递)
// &arr 用&传递参数

func funcName(sli []int) // 接收一个Slice切片(引用传递)
// arr[:] 用[:]切片语法传递参数

Reslice

Slice可以在Slice的基础上再次Slice, 创建Slice的视图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package main

import "fmt"

func main() {
arr := [...]string{"a", "b", "c", "d", "e", "f", "g", "h"}

s := arr[2:6]
fmt.Println("数组arr的值", arr)
fmt.Println("s切片的值", s)

s2 := s[:3]
s3 := s[2:]
fmt.Println("s2, 基于s切片的view", s2)
fmt.Println("s3, 基于s切片的view", s3)
}

执行结果:

1
2
3
4
数组arr的值 [a b c d e f g h]
s切片的值 [c d e f]
s2, 基于s切片的view [c d e]
s3, 基于s切片的view [e f]

上面的Demo中, 即使有多个Slice, 但其本质, 还是view了同一个Array

Slice的扩展

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package main

import "fmt"

func main() {
arr := [...]string{"a", "b", "c", "d", "e", "f", "g", "h"}

s := arr[2:6]
fmt.Println("数组arr的值", arr)
fmt.Println("s切片是对arr的view, 值为", s)

ss := s[3:5]
fmt.Println("ss切片是对s切片的view, 值为", ss)
}

执行结果:

1
2
3
数组arr的值 [a b c d e f g h]
s切片是对arr的view, 值为 [c d e f]
ss切片是对s切片的view, 值为 [f g]

上面的Demo中, 出现了一个很有意思的问题, 就是ss切片, 根据上面介绍的理论, ss切片是对s切片的view, 也就是Reslice, 但是s切片中只有4个值, 元素索引为0-3, 而ss对其进行reslice后, 却要看3-5的索引, 如果是普通Array的话, 肯定会报索引越界的异常, 但是上面Demo我们看出, 代码并没有抛出任何异常, 不仅还取到了值, 还取到了s切片中不存在的值

1
2
3
4
5
6
7
8
9
"a", "b", "c", "d", "e", "f", "g", "h"  <== Array
0 1 2 3 4 5 6 7 <== Array Index


"c", "d", "e", "f" <== Slice arr[2:6]
0 1 2 3 4 5 <== Sline Index

"f", "g" <== Reslice sli[3:5]
0 1 2 <== Reslice Index

Go语言Slice的扩展特性: 在Slice后, Array后面的元素依然可以被Slice感知, 但是Slice后面这些元素不能直接被取出, 直接取值会报索引越界的错误. 但是当Slice被Reslice时, Slice后面隐藏的值是可以在Reslice中重见天日的

Slice的实现

假设: - - - -为Slice对Array的视图

1
2
3
4
5
Array:  `+ + + + - - - - ~ ~ ~ ~`
↑ | |
ptr | |
|-len-| |
|-----cap-----|

Slice内部有三个元素:

  • ptr: 指向了Slice开头的元素. 依据上图, 也就是指向了第一个-
  • len: 保存了Slice的长度, 使用 sli[x] 这种方式取值, 只能取到sli[len-1]. 依据上图, len保存了-的长度, 4
  • cap: cap保存了从ptr开始到Array数组结束的长度. 依据上图, 也就是说cap保存了从第一个-开始, 到最后一个~为止的长度, 8

所以Go知道, Slice在扩展的时候, 只要不超过cap的长度, 就是可以取到的

注意: Slice可以向后扩展, 但是不能向前扩展, 图示中+部分的值在Slice中永远不会被取出来, 除非重新使用Slice对Array进行view操作`

sli[x]取值不可以超越len(sli); 向后扩展, 不可以超越底层Array cap(sli)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main

import "fmt"

func main() {
arr := [...]string{"a", "b", "c", "d", "e", "f", "g", "h"}
fmt.Println("Array数组的cap长度为: ", cap(arr)) // 结果为: 8

s := arr[2:6]
fmt.Println("数组arr的值", arr)
fmt.Println("s切片是对arr的view, 值为", s)
fmt.Println("s切片的len长度和cap长度分别为: ", len(s), cap(s))

ss := s[3:5]
fmt.Println("ss切片是对s切片的view, 值为", ss)
fmt.Println("ss切片的len长度和cap长度分别为: ", len(ss), cap(ss))

}

执行结果:

1
2
3
4
5
6
Array数组的cap长度为:  8
数组arr的值 [a b c d e f g h]
s切片是对arr的view, 值为 [c d e f]
s切片的len长度和cap长度分别为: 4 6
ss切片是对s切片的view, 值为 [f g]
ss切片的len长度和cap长度分别为: 2 3