golang slice理解

介绍

Go的切片类型提供了一种方便有效的处理类型数据序列的方法。切片类似于其他语言中的数组,但具有一些不寻常的属性。本文将介绍什么是切片并且如何使用他们

数组

切片类型是构建在Go的数组类型之上的抽象,因此为了理解切片,我们必须首先理解数组。

数组类型定义指定长度和元素类型。例如,类型[4]int表示四个整数的数组。数组的大小是固定的; 它的长度是它的类型的一部分([4]int并且[5]int是不同的,不兼容的类型)。数组可以通常的方式编入索引,因此表达式s[n]从零开始访问第n个元素。

package main

import "fmt"

func main() {
    var a [4]int
    a[0] = 1
    fmt.Println(a[0], a[2])

}
output:
1 0

数组不需要显式初始化; 数组的零值是一个现成的数组,其元素本身为零.
内存中的表示[4]int只是顺序排列的四个整数值:


Go的数组是值。数组变量表示整个数组; 它不是指向第一个数组元素的指针(如C中的情况)。这意味着当您分配或传递数组值时,您将复制其内容。(为了避免复制,你可以传递一个指向数组的指针,但那是一个指向数组的指针,而不是一个数组。)一种思考数组的方法是作为一种结构,但有索引而不是命名字段:一个固定的-size复合值。
数组可以这么定义:

b := [2]string{"Penn", "Teller"}

你也可以这么定义:

b := [...]string{"Penn", "Teller"}

两种定义方式变量都是[2]string类型

slices

数组有它们的位置,但它们有点不灵活,所以你不会在Go代码中经常看到它们。然而,切片无处不在。它们以阵列为基础,提供强大的功能和便利性。

切片的类型规范是[]T,切片T元素的类型。与数组类型不同,切片类型没有指定的长度。

切片文字声明就像数组文字一样,除了省略元素数:
可以使用调用的内置函数make创建切片,

func make([] T,len,cap)[] T

其中T代表要创建的切片的元素类型。该make函数采用类型,长度和可选容量。调用时,make分配一个数组并返回引用该数组的切片。

var s [] byte
s = make([] byte,5,5)
// s == [] byte {0,0,0,0,0}

省略capacity参数时,默认为指定的长度。这是相同代码的更简洁版本:

s:= make([] byte,5)

可以使用内置len和cap函数检查切片的长度和容量。

len(s)== 5
cap(s)== 5

切片长度和容量的关系

还可以通过“切片”现有切片或阵列来形成切片。通过指定半开放范围来完成切片,其中两个索引用冒号分隔。例如,表达式b[1:4]创建一个包含元素1到3的b切片(结果切片的索引将为0到2)。

b:= [] byte {'g','o','l','a','n','g'}
// b [1:4] == [] byte {'o','l','a'},与b共享相同的存储空间

切片表达式的开始和结束索引是可选的; 它们分别默认为零和切片长度:

// b [:2] == [] byte {'g','o'}
// b [2:] == [] byte {'l','a','n','g'}
// b [:] == b

这也是给定数组创建切片的语法:

x:= [3] string {“Лайка”,“Белка”,“Стрелка”}
s:= x [:] //引用x存储的切片

切片内部

切片是数组段的描述符。它由指向数组的指针,段的长度及其容量(段的最大长度)组成
[站外图片上传中...(image-2fae1-1563440373053)]
我们s之前创建的变量的make([]byte, 5)结构如下:
[站外图片上传中...(image-2218ae-1563440373054)]
长度是切片引用的元素数。容量是底层数组中元素的数量(从切片指针引用的元素开始)。在我们通过接下来的几个例子时,将明确区分长度和容量。

在切片时s,观察切片数据结构的变化及其与底层数组的关系:

s = s [2:4]

[站外图片上传中...(image-6f6534-1563440373054)]
切片不会复制切片的数据。它创建一个指向原始数组的新切片值。这使切片操作与操作数组索引一样高效。因此,修改重新切片的元素(而不是切片本身)会修改原始切片的元素:

    d := []byte{'r', 'o', 'a', 'd'}
    e := d[2:]
    fmt.Println(e)
    fmt.Println(d)
    e[1] = 'm'
    fmt.Println(e)
    fmt.Println(d)
output:
[97 100]
[114 111 97 100]
[97 109]
[114 111 97 109]

切片不能超出其容量。尝试这样做会导致运行时出现混乱,就像在切片或数组的边界之外进行索引一样。类似地,切片不能在零以下重新切片以访问数组中的早期元素

生长切片(复制和追加功能)

要增加切片的容量,必须创建一个新的更大的切片并将原始切片的内容复制到切片中

        s := make([]byte, 1)
    fmt.Println(s, len(s), cap(s))
    t := make([]byte, len(s), (cap(s)+1)*2)
    //for i := range s {
    //  t[i] = s[i]
    //}
    copy(t, s)
    fmt.Println(t, len(t), cap(t))
    output:
    [0] 1 1
    [0] 1 4

扩容后,使用range赋值还是copy函数两种方式是对等的

func copy(dst,src [] T)int

当然copy方法简化了赋值的步骤
一个常用的操作是动态增加容量,举个例子:

func AppendByte(slice []byte, data ...byte) []byte {
    m := len(slice)
    n := m + len(data)
    if n > cap(slice) { // if necessary, reallocate
        // allocate double what's needed, for future growth.
        newSlice := make([]byte, (n+1)*2)
        copy(newSlice, slice)
        slice = newSlice
    }
    slice = slice[0:n]
    copy(slice[m:n], data)
    return slice
}

func main() {
    a := []byte{1, 2}
    fmt.Println(a)
    a = AppendByte(a, 3, 4, 6)
    fmt.Println(a)
}
output:
[1 2]
[1 2 3 4 6]

上面的例子显示如果容量不够,以两倍容量增加,当然算法都是根据需求自定义

不过大多数情况不需要太复杂操作,可以通过append函数将新元素加入到slice尾部,如果slice容量不够,系统会动态添加容量

func append(s []T, x ...T) []T

举例如下:

a := make([]int, 1)
fmt.Println(a, len(a), cap(a))
a = append(a, 1, 2, 3)
fmt.Println(a, len(a), cap(a))
output:
[0] 1 1
[0 1 2 3] 4 4

如果想通过append添加另一个slice,可以如下操作:

 b := []string{"John", "Paul"}
fmt.Println(b, len(b), cap(b))
c := []string{"George", "Ringo", "Pete"}
b = append(b, c...)
fmt.Println(b, len(b), cap(b))
var d []string
b = append(b, d...)
fmt.Println(b, len(b), cap(b), d, d == nil)
output:
[John Paul] 2 2
[John Paul George Ringo Pete] 5 5
[John Paul George Ringo Pete] 5 5 [] true

nil slice也可以进行append

一个可能的陷阱

如前所述,重新切片切片不会复制底层数组。完整数组将保留在内存中,直到不再引用它为止。偶尔这会导致程序在只需要一小部分数据时将所有数据保存在内存中。

例如,此FindDigits函数将文件加载到内存中,并在其中搜索第一组连续数字数字,并将其作为新切片返回。

var digitRegexp = regexp.MustCompile(“[0-9] +”)

func FindDigits(filename string)[] byte { 
    b,_:= ioutil.ReadFile(filename)
    return digitRegexp.Find(b)
}

此代码的行为与广告一样,但返回的[]byte指向包含整个文件的数组。由于切片引用原始数组,只要切片保持在垃圾收集器周围就不能释放数组; 文件中几个有用的字节将整个内容保存在内存中。

要解决此问题,可以在返回之前将数据复制到新切片:

func CopyDigits(filename string)[] byte { 
    b,_:= ioutil.ReadFile(filename)
    b = digitRegexp.Find(b)
    c:= make([] byte,len(b))
    copy(c,b)
    return c 
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,843评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,538评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,187评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,264评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,289评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,231评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,116评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,945评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,367评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,581评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,754评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,458评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,068评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,692评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,842评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,797评论 2 369
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,654评论 2 354

推荐阅读更多精彩内容

  • 切片(slice)是 Golang 中一种比较特殊的数据结构,这种数据结构更便于使用和管理数据集合。切片是围绕动态...
    小孩真笨阅读 1,073评论 0 1
  • 切片(slice)是 Golang 中一种比较特殊的数据结构,这种数据结构更便于使用和管理数据集合。切片是围绕动态...
    51reboot阅读 28,649评论 2 10
  • 线性结构是计算机最常用的数据结构之一。无论是数组(arrary)还是链表(list),在编程中不可或缺。golan...
    _二少爷阅读 6,611评论 5 13
  • 切片是 Go 中的一种基本的数据结构,使用这种结构可以用来管理数据集合。切片的设计想法是由动态数组概念而来,为了开...
    一缕殇流化隐半边冰霜阅读 11,252评论 21 55
  • 云绕青山翻林中,雾腾空穴话朦胧,山涧溪水忙湍急,草木如洗碧翠玉,斜风细雨织惆怅,幽风清冷泣哀怨。人间七月阴雨天,忆...
    诗心不改阅读 287评论 1 7