早上(11.30)收到邮件,Vultr东京机房网络故障。当时搭建SS时,考虑到了机房故障。所以特意分出了日本和香港两条线路。但千算万算,忘记数据库还在东京机房中。 现在网络故障,SS服务器无法读取数据库中的账号信息。于是乎,主备两条线同时宕了。哭笑两声,没钱去做异地双活,访问量又不大,就这么凑合吧。 我就不信Vultr网络故障是大概率事件。如果很频繁的出故障,用户会用脚去投票的。

铁路警察各管一段,Vultr的故障让Vultr的运维去背锅吧。我们言归正传,继续聊Golang。

在<撸袖子>那节,我们提到了数组。 其中用了一个很少的篇幅说了一下数组的近亲-切片。当时说到数组使用起来不方便,Golang提供了一种更方便的数组使用方式,就是切片。这节中,我们就先来说切片。

先来复习数组的概念,就是一组相同数据类型的集合。在说数组的时候,没有什么动态扩展的方法。只能实现规定好这个数组有多少个元素,然后按照下标进行增删改查。在真实环境中,有很大的局限性。 切片作为数组的近亲,就弥补了这种缺陷。Golang所提供的切片,内置了很多方法来达到数组的动态扩容/缩容。

切片既然是数组的近亲,那声明方式基本上长得很像:

var name []type

name自然是切片名称,type就是数据类型。仅此而已,就完成了一个切片的声明。和数组的声明最大的不同,就在于没有长度限制。这是最常用的声明方式,还有一种文绉绉的声明方式,如下:

name := make([]type, length, capacity)

一瞅就有种学院派的作风。 多了两个参数,length和capacity。这两个概念理解不好,这就是一个大坑。Golang为了让切片有很高的读效率和又不容易出现指针越界,就创造了length和capacity两个属性。

capacity指的是此切片当前指向内存的数据大小。而length指的是当前切片的容量大小,从逻辑上来看,满足这个条件: 0<=length<=capacity。

为什么说这是一个坑? 如果打算用切片操作目标内存的时候,必须小心别append过头,否则就操作到新开启的内存块去了,也要小心别意外覆盖了原slice的值。比如下面:

s := []int{10} //创建一个legnth = capacity = 1 的切片,并且初始化为10
s = append(s,11) //容量不够,翻倍扩容。legnth = capacity = 2,现在是10,11
s = append(s,12) //容量又不够了,再次扩容。legnth =3, capacity = 4,现在是10,11,12
x := append(s, 13) //容量够了,不扩容。legnth = capacity = 4,现在是10,11,12,13
坑来了
y := append(s, 14) //容量够了,不扩容。legnth = capacity = 4,现在是10,11,12,14

但如果你将上面代码输出一下,会看到x和y的值是相同的,都是10.11.12.14。这里面包含了切片的本质。 在Golang官方文档中提及,对切片单独进行append操作,并不会修改切片的内容(也就是单独执行append(s,12)),往往需要将append后的数据重新赋值给源切片,也就是s = append(s,12),这是Golang官方所推荐的用法。 上面的例子中,在x和y那两行,因为s没有发生变化,length=3.所以后面append的值会直接添加到末尾。而返回的又都是同一块内存地址,所以x和y其实指的是同一块内存,因此其内部值也是相同的。 可以来一段代码,把x,y和s的内存地址都输出出来,结果就一目了然了。

如果嫌麻烦,那就用最简单的方式:

var s []int
s = append(s,xxx)

而如果想输出当前的length和capacity,就直接使用len()和cap()两个内置函数。

数组允许存在空数据,切片也当然允许存在空切片。当直接声明一个切片的时候,此时此刻,length = capacity = 0.

var numbers []int
此时此刻 len = 0 cap = 0 slice = []

又该如何判断切片是否为空呢?可以使用length和capacity属性,但不如使用nil来的简单:

numbers == nil

true就表示是空切片,false表示是非空切片。

切片同数组相比,最灵活的方面在于切分子切片。例如可以在代码中,根据业务需要,随时将一个大切片取出任意元素组成一个子切片。看下面:

numbers := []int{0,1,2,3,4,5,6,7,8}
number2 := numbers[:2] // 从0到2,但不包括2.所以是0,1
number3 := numbers[2:5] // 从2到5,但不包括5.所以是2,3,4
number4 := numbers[5:] // 从5到末尾,包括末尾。

上面number2, number3和number4都是子切片,在使用时,需要记住这些子切片都是指向了源切片某一块内存,什么意思?也就是说源切片元素发生了变化,那么子切片也会发生变化。不信? 在上面代码中声明子切片后,任意修改numbers的元素,在看看结果。

如果不想受源切片影响怎么办?使用copy()函数。顾名思义,也就是把重新创建一个切片,自立山头呗。

number5 := make([]int, 2)
copy(number5, numbers[:2])

输出地址之后,就可以看到两者已经完全脱离父子关系,想干嘛就干嘛去吧。

说到最后,需要看一下切片的数据结构了。 我想看到数据结构,上面那些所谓的坑应该就能看明白了。

type slice struct {
array unsafe.Pointer
len int
cap int
}
src/runtime/slice.go

可以看到slice,包含一个指针,一个len变量和一个cap变量。当需要获取length和capacity时,是直接读取的len和cap变量值,不需要再遍历一遍,所以获取长度和容量效率非常高。 而array指向了一块内存,进行append操作时,如果len == cap,则扩容。如果len < cap,那么就是array[len+1]操作。因为golang默认都是值传递,虽然len已经变成len+1了,但原始的slice的len仍然没有变。因此golang才建议,用源切片来接受返回值,这样源切片的len和cap就会同步发生变化。

说实话,这部分脑子里面清楚,但用文字表述的效果欠佳。所以遇到切片时刻记住,用源切片来接受返回值。如果需要子切片,首要需要考虑,是不是需要用copy来复制生成。

转载请保留联系方式 ztao8607@gmail.com

崩溃 golang入坑系列的更多相关文章

  1. 入坑第二式 golang入坑系列

    史前必读: 这是入坑系列的第二式,如果错过了第一式,可以去gitbook( https://andy-zhangtao.gitbooks.io/golang/content/ )点个回放,看个重播.因 ...

  2. 初生牛犊不怕虎 golang入坑系列

    读前必读,下面所有内容都是来自这里. 放到这里的目的,就是为了比对一下,哪里的读者多.平心而论,同样的Markdown,博客园排版真心X看,怎么瞅怎么X看.(X := '难' || X :='耐' | ...

  3. 维多利亚的秘密 golang入坑系列

    原文在gitbook,字字原创,版权没有,转载随意. 在写本文的前一天,2017维密在上海开始了. 为了纪念屌丝界的盛世,特为本节起名维多利亚的秘密.现在的社会,要想出名只有抓眼球.所以写份技术文章, ...

  4. 分水岭 golang入坑系列

    第三式开篇语有些负面, 所以这里就不贴了.有兴趣的自己可以去看看 https://andy-zhangtao.gitbooks.io/golang/content/ .怒发冲冠,意气之作.看完就完了, ...

  5. 准备冲锋 golang入坑系列

    史前摘要: 本来想写读前必读,但连续几篇博文都写读前必读,感觉就没有了新意. 所以换成史前摘要,反正是一个意思. 此摘要的目的仍然是提醒点击而来的同学,本系列最新文章在这里.放到博客园的目的是为了方便 ...

  6. 坐忘峰 golang入坑系列

    读前必读: 本文写于20日,首发于gitbook. 迟到的是日期,没变的是内容. 点击进入 https://andy-zhangtao.gitbooks.io/golang/content/ 可以看到 ...

  7. 乐呵乐呵得了 golang入坑系列

    开场就有料,今天返回去看了看以前的文章,轻松指数有点下降趋势.一琢磨,这不是我的风格呀.一反思,合着是这段时间,脑子里杂七杂八的杂事有点多,事情一多,就忘了快乐.古话说得好:愁也一天,乐也一天,只要还 ...

  8. 来自朝鲜的问候 golang入坑系列

    鸿渐于陆 本想着写满十八式,但按照目前的进度来看,是很难凑够十八式了.所以还是那句话,量力而行,适可而止.能写多少就写多少,我没法保证看完这本golang脱口秀,一定能成为golang大拿.但入了门, ...

  9. SEO是件贼有意思的事情 golang入坑系列

    这两天迷上了SEO.真心看不起百度的竞价排名,但作为一个商业网站,赚钱是一件无可厚非的事情.只做活雷锋,没有大金主是做不长的.做完功课后,发现百度和google的SEO策略又不相同,几乎是无法通用.百 ...

随机推荐

  1. substance在java swing中使用注意事项

    package org.dgw.uidemo; import java.awt.EventQueue; import javax.swing.JFrame; import javax.swing.UI ...

  2. for循环的基础使用

    for循环:    for 变量名 in 列表:do           循环体    done    执行机制:           依次将列表中的元素赋值给“变量名”:每次赋值后即执行一次循环体: ...

  3. MyBatis 笔记总结

    1.MyBatis中的一些要点: 1.1 SqlMapConfig.xml:mybatis的全局配置文件,配置mybatis的运行环境等信息,包括mapper.xml文件 1.2 mapper.xml ...

  4. 探索equals()和hashCode()方法

    探索equals()和hashCode()方法 在根类Object中,实现了equals()和hashCode()这两个方法,默认: equals()是对两个对象的地址值进行的比较(即比较引用是否相同 ...

  5. How the Intelligent Digital Mesh Will Transform Every Business Layer

    The "intelligent digital mesh" is the definitive package for tomorrow's titans of business ...

  6. 一个fork短码的扩展版本

    原本代码: 链接 int skip = !!fork() + 2*(!!fork()); for (uint32_t i=skip;i!=INT_MAX;i+=4) { } 这个是多进程加速循环的代码 ...

  7. SAP开发快捷键

    F1 帮助     F2 回车确认(在某些地方可用,比如ABAP)     F3 返回     F4 选择输入项     F5 新增     F6 复制为...     F7 全选     F8 选择 ...

  8. 基于Lua脚本解决实时数据处理流程中的关键问题

    摘要 在处理实时数据的过程中需要缓存的参与,由于在更新实时数据时并发处理的特点,因此在更新实时数据时经常产生新老数据相互覆盖的情况,针对这个情况调查了Redis事务和Lua脚本后,发现Redis事务并 ...

  9. 暑假练习赛 007 C - OCR

    C - OCR Description standard input/outputStatements Optical Character Recognition (OCR) is one of th ...

  10. 【每天半小时学框架】——React.js的模板语法与组件概念

           [重点提前说:组件化与虚拟DOM是React.js的核心理念!]        先抛出一个论题:在React.js中,JSX语法提倡将 HTML 和 CSS 全都写入到JavaScrip ...