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…

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

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

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

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

二分匹配最大匹配的理解(附图解)

定义一个PXP的有向图中,路径覆盖就是在图中找一些路径,使之覆盖了图中的所有顶点,且任何一个顶点有且只有一条路径与之关联;(如果把这些路径中的每条路径从它的起始点走到它的终点,那么恰好可以经过图中的每…

Floyd算法的理解

转载于:https://www.cnblogs.com/hujunzheng/p/3919226.html

hdu1269迷宫城堡(判断有向图是否是一个强连通图)

1 /* 题意: 给你一个图,求这个有向图示否是一个强连通图(每两个节点都是可以相互到达的)! 思路1:按正向边dfs一遍,将经过的节点计数,如果记录的节点的个数小于…

mgg mysql_mgg文件怎么转换mp3格式?

步骤/方法方法/步骤1:下载载视频转换器,我们说到在官网下载比较好吧。下载完成之后,我们就直接点击进行安装,一般 在安装的过程也是非常快速的,主要是按照安装向导上的步骤进行就可以了。方法/步骤2:安装好之后,我们就…

java dao 泛型的好处_java中泛型有什么作用

泛型的作用如下:1、类型安全泛型的主要目标是提高 Java 程序的类型安全。编译时的强类型检查;通过知道使用泛型定义的变量的类型限制,编译器可以在一个高得多的程度上验证类型假设。没有泛型,这些假设就只存在于程序员的头脑中(或…

java ==和=_Java ==和equals()的区别

前言本篇文章讲的是从JVM角度比较和equals的区别一:** Java数据类型分类**Paste_Image.png1:基本数据类型又称为原始数据类型,他们之间的比较应该使用(),比较的是他们的值。2:引用数据类型当引用数据类型用()进行比较&…

poj1330Nearest Common Ancestors 1470 Closest Common Ancestors(LCA算法)

LCA思想:http://www.cnblogs.com/hujunzheng/p/3945885.html 在求解最近公共祖先为问题上,用到的是Tarjan的思想,从根结点开始形成一棵深搜树,非常好的处理技巧就是在回溯到结点u的时候,u的子树已经遍历,这…

LCA算法的理解

LCA思想:在求解最近公共祖先为问题上,用到的是Tarjan的思想,从根结点开始形成一棵深搜树,非常好的处理技巧就是在回溯到结点u的时候,u的子树已经遍历,这时候才把u结点放入合并集合中, 这样u结点…

java词汇速查手册_java 词汇表速查手册

Abstract class 抽象类:抽象类是不允许实例化的类,因此一般它需要被进行扩展继承。Abstract method 抽象方法:抽象方法即不包含任何功能代码的方法。Access modifier 访问控制修饰符:访问控制修饰符用来修饰Java中类、以及类的方法和变量的访问控制属性。Anonymous …

python3.5 连接mysql_python3.5 連接mysql本地數據庫

前期准備工作:安裝python的模塊,網上大部分讓安裝mysqldb模塊,但是會報錯,原因是python3.5不被其支持:請看該鏈接 我們也可以這樣解決:直接執行:sudo pip3 install pymysql;在python3中輸入impo…

java异常顺序_网易新闻

public class SmallT {public static void main(String args[]) {SmallT t new SmallT();int b t.get();System.out.println(b);}public int get() {try {return 1;} finally {return 2;}}}返回的结果是2。我可以通过下面一个例子程序来帮助我解释这个答案,从下面…

下载国外网站资料需java_Java开发必知道的国外10大网站

1、https://www.google.com/不解释2、https://stackoverflow.com里面包含各种开发遇到的问题及答案,质量比较高。3、https://github.com/免费的开源代码托管网站,包括了许多开源的项目及示例项目等。4、https://dzone.com/提供技术新闻、编程教程、及各种…

java 空数组如何判断,java判断数组是否为空

java判断数组是否为空根据数组长度判断,如果为0,则为空,反之不是。 (推荐学习:java课程)public class Main {public static void main(String[] args) {int[] array1 new int[]{}; //被当成 {0}if (array1 null) {System.out.pr…

php访问网页post获取源码,第一次抓别人网站数据,用postman直接请求可以获取到返回数据,通过代码的方式就一直报错,php...

最近需要抓取下KFC的一些数据通过postman把请求地址和参数都拿过来后可以返回数据我就天真的以为可以通过代码直接发送一个post请求即可但是通过php的curl模拟请求后,返回的一直是服务器异常刚开始时好像成功过,但现在一直都是报这个,我用的就…

php注册机制,php自动注册登录验证机制实现代码_PHP教程

背景:在phpwind站点后台添加一个名为“广告管家”(广告管家为CNZZ的一款广告投放的应用)的应用,整个“广告管家”的应用是通过iframe载入,载入的具体内容根据不同站点显示针对该站点的具体内容,为了提高易用性,有以下的…

php实现直播答题系统,直播答题解决方案

概述即构提供直播答题一站式解决方案,包括 Windows 主播端、移动 APP 端示例源代码(iOS、Android)。1 下载/体验地址由于直播答题场景需要主播端(推流、发题)和观众端(拉流、答题)配合使用,因此开发者需要同时下载这两端的软件。下载后,具体的…