golang 切片 接口_Go编程模式:切片,接口,时间和性能

在本篇文章中,我会对 Go 语言编程模式的一些基本技术和要点,这样可以让你更容易掌握 Go 语言编程。其中,主要包括,数组切片的一些小坑,还有接口编程,以及时间和程序运行性能相关的话题。

本文是全系列中第 1 / 9 篇:Go 编程模式[1]

Go 编程模式:切片,接口,时间和性能

Go 编程模式:错误处理

[2]

Go 编程模式:Functional Options

[3]

Go 编程模式:委托和反转控制

[4]

Go 编程模式:Map-Reduce

[5]

Go 编程模式:Go Generation

[6]

Go 编程模式:修饰器

[7]

Go 编程模式:Pipeline

[8]

Go 编程模式:k8s Visitor 模式

[9]

1. Slice

首先,我们先来讨论一下 Slice,中文翻译叫“切片”,这个东西在 Go 语言中不是数组,而是一个结构体,其定义如下:

type slice struct {

array unsafe.Pointer //指向存放数据的数组指针

len   int            //长度有多大

cap   int            //容量有多大

}

用图示来看,一个空的 slice 的表现如下:

953a63e0c8c8e92cb67a63384636c68f.png

熟悉 C/C++的同学一定会知道,在结构体里用数组指针的问题——数据会发生共享!下面我们来看一下 slice 的一些操作

foo = make([]int, 5)

foo[3] = 42

foo[4] = 100

bar  := foo[1:4]

bar[1] = 99

对于上面这段代码。

首先先创建一个 foo 的 slice,其中的长度和容量都是 5

然后开始对 foo 所指向的数组中的索引为 3 和 4 的元素进行赋值

然后,对 foo 做切片后赋值给 bar,再修改 bar[1]

b08704f77c95dc62bc4b022725841a5f.png

通过上图我们可以看到,因为 foo 和 bar 的内存是共享的,所以,foo 和 bar 的对数组内容的修改都会影响到对方。

接下来,我们再来看一个数据操作 append() 的示例

a := make([]int, 32)

b := a[1:16]

a = append(a, 1)

a[2] = 42

上面这段代码中,把 a[1:16] 的切片赋给到了 b ,此时,a 和 b 的内存空间是共享的,然后,对 a做了一个 append()的操作,这个操作会让 a 重新分享内存,导致 a 和 b 不再共享,如下图所示:

61d376c98daaa12f29c508a245f14e6a.png

从上图我们可以看以看到 append()操作让 a 的容量变成了 64,而长度是 33。这里,需要重点注意一下——append()这个函数在 cap 不够用的时候就会重新分配内存以扩大容量,而如果够用的时候不不会重新分享内存!

我们再看来看一个例子:

func main() {

path := []byte("AAAA/BBBBBBBBB")

sepIndex := bytes.IndexByte(path,'/’)

dir1 := path[:sepIndex]

dir2 := path[sepIndex+1:]

fmt.Println("dir1 =>",string(dir1)) //prints: dir1 => AAAA

fmt.Println("dir2 =>",string(dir2)) //prints: dir2 => BBBBBBBBB

dir1 = append(dir1,"suffix"...)

fmt.Println("dir1 =>",string(dir1)) //prints: dir1 => AAAAsuffix

fmt.Println("dir2 =>",string(dir2)) //prints: dir2 => uffixBBBB

}

上面这个例子中,dir1 和 dir2 共享内存,虽然 dir1 有一个 append() 操作,但是因为 cap 足够,于是数据扩展到了dir2 的空间。下面是相关的图示(注意上图中 dir1 和 dir2 结构体中的 cap 和 len 的变化)

b86b03637c6716e57951d3e77137d00c.png

如果要解决这个问题,我们只需要修改一行代码。

dir1 := path[:sepIndex]

修改为

dir1 := path[:sepIndex:sepIndex]

新的代码使用了 Full Slice Expression,其最后一个参数叫“Limited Capacity”,于是,后续的 append() 操作将会导致重新分配内存。

2. 深度比较

当我们复杂一个对象时,这个对象可以是内建数据类型,数组,结构体,map……我们在复制结构体的时候,当我们需要比较两个结构体中的数据是否相同时,我们需要使用深度比较,而不是只是简单地做浅度比较。这里需要使用到反射 reflect.DeepEqual() ,下面是几个示例

import (

"fmt"

"reflect"

)

func main() {

v1 := data{}

v2 := data{}

fmt.Println("v1 == v2:",reflect.DeepEqual(v1,v2))

//prints: v1 == v2: true

m1 := map[string]string{"one": "a","two": "b"}

m2 := map[string]string{"two": "b", "one": "a"}

fmt.Println("m1 == m2:",reflect.DeepEqual(m1, m2))

//prints: m1 == m2: true

s1 := []int{1, 2, 3}

s2 := []int{1, 2, 3}

fmt.Println("s1 == s2:",reflect.DeepEqual(s1, s2))

//prints: s1 == s2: true

}

3. 接口编程

下面,我们来看段代码,其中是两个方法,它们都是要输出一个结构体,其中一个使用一个函数,另一个使用一个“成员函数”。

func PrintPerson(p *Person) {

fmt.Printf("Name=%s, Sexual=%s, Age=%d\n",

p.Name, p.Sexual, p.Age)

}

func (p *Person) Print() {

fmt.Printf("Name=%s, Sexual=%s, Age=%d\n",

p.Name, p.Sexual, p.Age)

}

func main() {

var p = Person{

Name: "Hao Chen",

Sexual: "Male",

Age: 44,

}

PrintPerson(&p)

p.Print()

}

你更喜欢哪种方式呢?在 Go 语言中,使用“成员函数”的方式叫“Receiver”,这种方式是一种封装,因为 PrintPerson()本来就是和 Person强耦合的,所以,理应放在一起。更重要的是,这种方式可以进行接口编程,对于接口编程来说,也就是一种抽象,主要是用在“多态”,这个技术,在《Go 语言简介(上):接口与多态[10]》中已经讲过。在这里,我想讲另一个 Go 语言接口的编程模式。

首先,我们来看一下,有下面这段代码:

type Country struct {

Name string

}

type City struct {

Name string

}

type Printable interface {

PrintStr()

}

func (c Country) PrintStr() {

fmt.Println(c.Name)

}

func (c City) PrintStr() {

fmt.Println(c.Name)

}

c1 := Country {"China"}

c2 := City {"Beijing"}

c1.PrintStr()

c2.PrintStr()

其中,我们可以看到,其使用了一个 Printable 的接口,而 Country 和 City 都实现了接口方法 PrintStr() 而把自己输出。然而,这些代码都是一样的。能不能省掉呢?

我们可以使用“结构体嵌入”的方式来完成这个事,如下的代码所示:

type WithName struct {

Name string

}

type Country struct {

WithName

}

type City struct {

WithName

}

type Printable interface {

PrintStr()

}

func (w WithName) PrintStr() {

fmt.Println(w.Name)

}

c1 := Country {WithName{ "China"}}

c2 := City { WithName{"Beijing"}}

c1.PrintStr()

c2.PrintStr()

引入一个叫 WithName的结构体,然而,所带来的问题就是,在初始化的时候,变得有点乱。那么,我们有没有更好的方法?下面是另外一个解。

type Country struct {

Name string

}

type City struct {

Name string

}

type Stringable interface {

ToString() string

}

func (c Country) ToString() string {

return "Country = " + c.Name

}

func (c City) ToString() string{

return "City = " + c.Name

}

func PrintStr(p Stringable) {

fmt.Println(p.ToString())

}

d1 := Country {"USA"}

d2 := City{"Los Angeles"}

PrintStr(d1)

PrintStr(d2)

上面这段代码,我们可以看到——**我们使用了一个叫Stringable 的接口,我们用这个接口把“业务类型” Country 和 City 和“控制逻辑” Print() 给解耦了。**于是,只要实现了Stringable 接口,都可以传给 PrintStr() 来使用。

这种编程模式在 Go 的标准库有很多的示例,最著名的就是 io.Read 和 ioutil.ReadAll 的玩法,其中 io.Read 是一个接口,你需要实现他的一个 Read(p []byte) (n int, err error) 接口方法,只要满足这个规模,就可以被 ioutil.ReadAll这个方法所使用。这就是面向对象编程方法的黄金法则——“Program to an interface not an implementation”

4. 接口完整性检查

另外,我们可以看到,Go 语言的编程器并没有严格检查一个对象是否实现了某接口所有的接口方法,如下面这个示例:

type Shape interface {

Sides() int

Area() int

}

type Square struct {

len int

}

func (s* Square) Sides() int {

return 4

}

func main() {

s := Square{len: 5}

fmt.Printf("%d\n",s.Sides())

}

我们可以看到 Square 并没有实现 Shape 接口的所有方法,程序虽然可以跑通,但是这样编程的方式并不严谨,如果我们需要强制实现接口的所有方法,那么我们应该怎么办呢?

在 Go 语言编程圈里有一个比较标准的作法:

var _ Shape = (*Square)(nil)

声明一个 _ 变量(没人用),其会把一个 nil 的空指针,从 Square 转成 Shape,这样,如果没有实现完相关的接口方法,编译器就会报错:

cannot use (*Square)(nil) (type *Square) as type Shape in assignment: *Square does not implement Shape (missing Area method)

这样就做到了个强验证的方法。

5. 时间

对于时间来说,这应该是编程中比较复杂的问题了,相信我,时间是一种非常复杂的事(比如《你确信你了解时间吗?[11]》、《关于闰秒[12]》等文章)。而且,时间有时区、格式、精度等等问题,其复杂度不是一般人能处理的。所以,一定要重用已有的时间处理,而不是自己干。

在 Go 语言中,你一定要使用 time.Time 和 time.Duration 两个类型:

在命令行上,

flag 通过

time.ParseDuration 支持了

time.Duration

JSon 中的

encoding/json 中也可以把

time.Time 编码成

RFC 3339

[13] 的格式

数据库使用的

database/sql 也支持把

DATATIME 或

TIMESTAMP 类型转成

time.Time

YAML 你可以使用

gopkg.in/yaml.v2 也支持

time.Time 、

time.Duration 和

RFC 3339

[14] 格式

如果你要和第三方交互,实在没有办法,也请使用 RFC 3339[15] 的格式。

最后,如果你要做全球化跨时区的应用,你一定要把所有服务器和时间全部使用 UTC 时间。

6. 性能提示

Go 语言是一个高性能的语言,但并不是说这样我们就不用关心性能了,我们还是需要关心的。下面是一个在编程方面和性能相关的提示。

如果需要把数字转字符串,使用

strconv.Itoa() 会比

fmt.Sprintf() 要快一倍左右

尽可能地避免把

String转成

[]Byte 。这个转换会导致性能下降。

如果在 for-loop 里对某个 slice 使用

append()请先把 slice 的容量很扩充到位,这样可以避免内存重新分享以及系统自动按 2 的 N 次方幂进行扩展但又用不到,从而浪费内存。

使用

StringBuffer 或是

StringBuild 来拼接字符串,会比使用

+ 或

+= 性能高三到四个数量级。

尽可能的使用并发的 go routine,然后使用

sync.WaitGroup 来同步分片操作

避免在热代码中进行内存分配,这样会导致 gc 很忙。尽可能的使用

sync.Pool 来重用对象。

使用 lock-free 的操作,避免使用 mutex,尽可能使用

sync/Atomic包。(关于无锁编程的相关话题,可参看《

无锁队列实现

[16]》或《

无锁 Hashmap 实现

[17]》)

使用 I/O 缓冲,I/O 是个非常非常慢的操作,使用

bufio.NewWrite() 和

bufio.NewReader() 可以带来更高的性能。

对于在 for-loop 里的固定的正则表达式,一定要使用

regexp.Compile() 编译正则表达式。性能会得升两个数量级。

如果你需要更高性能的协议,你要考虑使用

protobuf

[18] 或

msgp

[19] 而不是 JSON,因为 JSON 的序列化和反序列化里使用了反射。

你在使用 map 的时候,使用整型的 key 会比字符串的要快,因为整型比较比字符串比较要快。

参考

还有很多不错的技巧,下面的这些参考文档可以让你写出更好的 Go 的代码,必读!

Effective Go

[20]

Uber Go Style

[21]

50 Shades of Go: Traps, Gotchas, and Common Mistakes for New Golang Devs

[22]

Go Advice

[23]

Practical Go Benchmarks

[24]

Benchmarks of Go serialization methods

[25]

Debugging performance issues in Go programs

[26]

Go code refactoring: the 23x performance hunt

[27]

参考资料

[1]

Go 编程模式: https://coolshell.cn/articles/series/go编程模式

[2]

Go 编程模式:错误处理: https://coolshell.cn/articles/21140.html

[3]

Go 编程模式:Functional Options: https://coolshell.cn/articles/21146.html

[4]

Go 编程模式:委托和反转控制: https://coolshell.cn/articles/21214.html

[5]

Go 编程模式:Map-Reduce: https://coolshell.cn/articles/21164.html

[6]

Go 编程模式:Go Generation: https://coolshell.cn/articles/21179.html

[7]

Go 编程模式:修饰器: https://coolshell.cn/articles/17929.html

[8]

Go 编程模式:Pipeline: https://coolshell.cn/articles/21228.html

[9]

Go 编程模式:k8s Visitor 模式: https://coolshell.cn/articles/21263.html

[10]

Go 语言简介(上):接口与多态: https://coolshell.cn/articles/8460.html#接口和多态

[11]

你确信你了解时间吗?: https://coolshell.cn/articles/5075.html

[12]

关于闰秒: https://coolshell.cn/articles/7804.html

[13]

RFC 3339: https://tools.ietf.org/html/rfc3339

[14]

RFC 3339: https://tools.ietf.org/html/rfc3339

[15]

RFC 3339: https://tools.ietf.org/html/rfc3339

[16]

无锁队列实现: https://coolshell.cn/articles/8239.html

[17]

无锁 Hashmap 实现: https://coolshell.cn/articles/9703.html

[18]

protobuf: https://github.com/golang/protobuf

[19]

msgp: https://github.com/tinylib/msgp

[20]

Effective Go: https://golang.org/doc/effective_go.html

[21]

Uber Go Style: https://github.com/uber-go/guide/blob/master/style.md

[22]

50 Shades of Go: Traps, Gotchas, and Common Mistakes for New Golang Devs: http://devs.cloudimmunity.com/gotchas-and-common-mistakes-in-go-golang/

[23]

Go Advice: https://github.com/cristaloleg/go-advice

[24]

Practical Go Benchmarks: https://www.instana.com/blog/practical-golang-benchmarks/

[25]

Benchmarks of Go serialization methods: https://github.com/alecthomas/go_serialization_benchmarks

[26]

Debugging performance issues in Go programs: https://github.com/golang/go/wiki/Performance

[27]

Go code refactoring: the 23x performance hunt: https://medium.com/@val_deleplace/go-code-refactoring-the-23x-performance-hunt-156746b522f7

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/531762.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

poj 3352Road Construction(无向双连通分量的分解)

1 /*2 题意:给定一个连通的无向图G,至少要添加几条边,才能使其变为强连通图(指的是边强联通)。 3 思路:利用tarjan算法找出所有的双联通分量!然后根据low[]值的不同将双联通分量4 进行…

jsp中去掉超链接下划线吗_网页中如何去掉超链接的下划线

展开全部a:link {text-decoration: none;}a:visited {text-decoration: none;color: #6B6C70;}其中的text-decoration: none;是消除下划线例如:只需加入一段代码32313133353236313431303231363533e59b9ee7ad9431333337393534:td,body { font-size: 9pt}a…

POJ 2312Battle City(BFS-priority_queue 或者是建图spfa)

1 /*2 bfs搜索&#xff01;要注意的是点与点的权值是不一样的哦&#xff01;3 空地到空地的步数是1&#xff0c; 空地到墙的步数是2&#xff08;轰一炮移过去&#xff09;4 所以用到优先队列进行对当前节点步数的更新&#xff01; 5 */6 #include<iostream>7 #…

linux训练python出现killed_Linux 查看进程被杀死的详情

运行写的不太完善的爬虫程序, 未限制任务队列大小, 再加上本子配置不高, 爬取网站到第3层大半时, 内存不足了...进程运行太猛, 导致系统 out of memory, 那么此进程被系统的oom killer杀死.此时终端显示 "Killed" 或 "已杀死".查看相关信息的命令:dmesg | …

mysql 123456_MySQL字符串中抽取数值的方法 select -(-'123456@163.com'); 很牛逼

MySQL的字符串函数非常多&#xff0c;以至于有时候我不知道该如何灵活的使用这些函数。字符串基本信息函数 collation convert&#xff0c;char_length等加密函数 password(x)&#xff0c;encode, aes_encrypt字符串连接函数 concat(x1,x2,….)修剪函数 trim,ltrim,…

ZZUOJ 1199 大小关系(拓扑排序,两种方法_判断入度和dfs回路判断)

1 /*2 这道题如果按照度为0的节点来判断的时候,将度为0的节点和其相连的节点&#xff08;度数并减去1&#xff09; 3 从图中去掉&#xff0c;如果度为0的节点的个数为0个但是图中的节点没有都去掉的 时候那么说明4 出现了回路!用这种方法必须将重边去除掉&#xff01; …

matlab画图plot设置字体_R语言科研画图字体格式设置

作者&#xff1a;黄天元&#xff0c;复旦大学博士在读&#xff0c;热爱数据科学与开源工具&#xff08;R&#xff09;&#xff0c;致力于利用数据科学迅速积累行业经验优势和科学知识发现&#xff0c;涉猎内容包括但不限于信息计量、机器学习、数据可视化、应用统计建模、知识图…

hdu3339 In Action(Dijkstra+01背包)

1 /*2 题意&#xff1a;有 n 个站点&#xff08;编号1...n&#xff09;&#xff0c;每一个站点都有一个能量值&#xff0c;为了不让这些能量值连接起来&#xff0c;要用 3 坦克占领这个站点&#xff01;已知站点的 之间的距离&#xff0c;每个坦克从0点出发到某一个站点&…

在手机上安装youget_you-get 安装和用法

Usage: you-get [OPTION]... [URL]...Startup options:-V | --version 版本信息-h | --help 帮助Dry-run options: (no actual downloading)-i | --info 列出所有可获取的视频信息-u | --url 打印URLs的提取出信息&#xff0c;真实链接地址--json 打印URLs的JSON格式Download o…

ZZUOJ1196: 单调数

1 /*2 注意的事项:是输出小于 10^n的正整数的个数哦&#xff01;开始的时候总比样例输出多一个数&#xff0c;3 纠结了好久&#xff0c;原来是 0加了进去了&#xff01;4 5 dpI[n][m]表示的是第n位添加数字m&#xff08;0....9&#xff09;的构成单调递增数个数 6 …

mac 爱普生打印机驱动_epson l360 mac版驱动下载-爱普生l360驱动Mac版最新版 - 极光下载站...

爱普生l360驱动苹果电脑版是专为mac用户所设计打造&#xff0c; 当你的电脑中安装了本驱动程序以后&#xff0c;就可以非常轻松的进行操作打印了&#xff0c;与该型号的打印机相匹配&#xff0c;将会带给你最流畅的打印体会&#xff01;爱普生l360打印机介绍--打印质量分辨率可…

mysql 生成 javabean_从MySQL快速生成JavaBean

SELECTCONCAT(/**\n*,COLUMN_COMMENT,\n*/\n), -- 注解CONCAT(Column(name ",column_name,")\n), -- JPA字段注解( -- 根据表定义的字段生成相应的 Java类型CASEdata_typeWHEN varcharTHEN private StringWHEN bigintTHEN private IntegerWHEN intTHEN private Inte…

poj2253 Frogger(最短路变型或者最小生成树)

1 /*2 题意&#xff1a;就是源点到终点有多条的路径&#xff0c;每一条路径中都有一段最大的距离&#xff01;3 求这些路径中最大距离的最小值&#xff01;4 5 Dijkstra, Floyd, spfa都是可以的&#xff01;只不过是将松弛的条件变一下就行了&#xff01;6 7 …

python包mdure_Python hashlib模块实例使用详解

这篇文章主要介绍了Python hashlib模块实例使用详解,文中通过示例代码介绍的非常详细&#xff0c;对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下hashlib模块主要的作用&#xff1a;加密保护消息安全&#xff0c;常用的加密算法如MD5&#xff0c;SHA1等。1、…

UVAoj 348 - Optimal Array Multiplication Sequence

1 /*2 题意&#xff1a;矩阵相乘的最少的步数3 dp[i][j]min(dp[i][j], dp[i][k]dp[k1][j]num[i-1]*num[k]*num[j]);4 表示的是第i个矩阵到第j个矩阵相乘的最少步数5 sign[i][j]表示的是第i个矩阵到第j个矩阵相乘的最少步数是由第i个矩阵到第sign[i][j]个矩阵相…

raft协议 MySQL 切换_Raft 协议实战系列(二)—— 选主

注&#xff1a;本文原创&#xff0c;转载请标明出处。欢迎转发、关注微信公众号&#xff1a;Q的博客。 不定期发送干货&#xff0c;实践经验、系统总结、源码解读、技术原理。本文目的笔者期望通过系列文章帮助读者深入理解Raft协议并能付诸于工程实践中&#xff0c;同时解读不…

codeforce Pashmak and Buses(dfs枚举)

1 /*2 题意&#xff1a;n个同学&#xff0c;k个车&#xff0c; 取旅游d天&#xff01;3 要求所有的学生没有两个或者两个以上的在同一辆车上共同带d天&#xff01; 输出可行的方案&#xff01;4 5 对于d行n列的矩阵&#xff0c;第i行第j列表示的是第i天第j个同学所…

怎样用mysql查询测试_如何测试数据库查询优化器

我一直认为&#xff0c;查询优化器(Query Optimizer&#xff0c;后面简称优化器)一直是数据库领域 Top 级别的 hardcore 技术&#xff0c;自己也一直尝试去深入理解&#xff0c;但每每看到 TiDB 代码里面那一大坨 plan 的代码&#xff0c;我就望而生畏了&#xff0c;就像是『可…

poj2060Taxi Cab Scheme(二分图匹配)

1 /*2 题意&#xff1a; 出租车 有一个出发的时间&#xff0c;从点&#xff08;a, b&#xff09;到点&#xff08;c, d&#xff09;&#xff0c;时间为3 abs(a-c)abs(b-d)! 一辆车可以在运完一个乘客后运另一个乘客, 4 条件是此车要在预约开始前一分钟之前到达出发地,…

二级java考什么_计算机二级Java考试资料!

Where领&#xff1f;基本要求1 . 掌握 Java 语言的特点&#xff64;实现机制和体系结构&#xff61;2 . 掌握 Java 语言中面向对象的特性&#xff61;3 . 掌握 Java 语言提供的数据类型和结构&#xff61;4 . 掌握 Java 语言编程的基本技术&#xff61;5 . 会编写 Java 用户界面…