penglongli / blog

18 stars 1 forks source link

golang 的 slice 与数组 #59

Open penglongli opened 6 years ago

penglongli commented 6 years ago

Go 的数组、切片和映射(Map)

Go 的集合数据结构只有三种:数组、切片和映射,比起 Java 繁杂的扩展简单很多。当然,我们也可以通过基本的数据结构来自己造轮子,如:ConcurrentHashMap(https://github.com/orcaman/concurrent-map)等

数组

Go 中的数组是一个长度固定的数据类型,用于存储具有相同类型的元素。由于数据的内存是连续的,CPU 能够把正在试用的数据缓存更久的时间。并且内存连续很容易计算索引,可以快速迭代数组中的所有元素。

初始化数组

数组的使用

指针数组访问

var (
    array = [5]*int{0: new(int), 1: new(int)}
)

func main() {
    *array[0] = 1
    fmt.Println(array[0])
    fmt.Println(*array[0])

    fmt.Println(array[4])
    array[4] = new(int)
    *array[4] = 4
}

使用 new(T) 返回的是 T 的指针,如上 new(int) 返回的是一个 *int 类型

我们使用 *array[0] = 1 来给数组的元素赋值,并且array[0] 的输出是一个地址。

可以注意到 fmt.Println(array[4]) 输出的值是:<nil> ,这是由于其没有初始化的缘故

数组复制

var (
    array = [5]int{1, 2, 3, 4, 5}
)

func main() {
    var arrayCopy [5]int
    arrayCopy = array

    arrayCopy[0] = 100
    array[0] = 111
    fmt.Println(arrayCopy[0])
    fmt.Println(array[0])
}

非指针数组的复制,在复制之后底层内存空间存储的就是两个完全不一样的数组了

指针数组复制

var (
    array = [5]*int{0: new(int), 1: new(int)}
    arrayCopy [5]*int
)

func main() {
    *array[0] = 1

    arrayCopy = array
    *arrayCopy[0] = 111

    fmt.Println(*array[0])
}

我们在将指针数组赋值给另一个数组后,在另一个数组修改指针指向的值。发现原数组的值也发生了变化,这是由于他们的指针地址是相同的。

数组传参

var (
    array = [5]int{1, 2, 3, 4, 5}
)

func changeArr(arr [5]int) {
    arr[0] = 111
}

func main() {
    changeArr(array)

    fmt.Println(array[0])
}

Go 的参数传递与 Java 一样也是传值的,这里传递的是数组的复制体,所以在函数中改变数组的值并不会在原数组生效。

切片

切片的使用和数组很相似,但是底层的数据结构却是不同的:

如上图,我们声明了一个长度为 3,容量为 5 的整型切片。slice 的数据结构是:指向底层数组的指针、长度、容量。

如果切片仅声明,但是并没有初始化,则其为 nill

var slice []string
fmt.Println(slice == nil) // True

切片初始化

make 初始化切片

需要注意的是:上述均初始化了切片,分配了内存。但是访问元素却是什么都没有,其元素不是 Nil

字面量初始化

var (
    slice = []string{"first", "second"}
)

索引初始化

var (
    slice = []string{20: "first", 1000: "second"}
)

上述声明了一个长度和容量为 1001 的切片

切片使用

切片创建切片

slice := []int{1, 2, 3, 4, 5}

newSlice := slice[1:3]
fmt.Println(newSlice[1])

使用切片创建另外一个切片,共享底部数组。结构如下:

新建的切片地址指向数组第二个元素,长度为 3 - 1,容量为 5 - 1

修改新切片

由于我们的新切片和原切片引用的是同一个底层数组,所以修改新切片元素的值会影响到原切片:

slice := []int{1, 2, 3, 4, 5}

newSlice := slice[1:3]
newSlice[0] = 1000

fmt.Println(slice[1]) // 1000

切片增长

当我们切片仍然有空余容量可用的时候,append 方法能够将剩余容量中可用元素,合并到切片的长度中;如果无空余容量,将会创建一个新底层数组,然后把原数组的值复制过来,然后执行扩展。

切片追加合并

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

s1 = append(s1, s2...)
fmt.Printf("%v\n", s1) // [1 2 3 4]

使用 可以析构切片的全部元素

切片迭代

映射

映射(Map) 就是一个键值对,和 Java 中的 Map 功能相似。需要注意的一点是:

映射作为参数传递给函数,并不会创造出映射的副本。即:函数中对映射的修改,会直接影响到其它调用者

创建和初始化

make 创建

dict := make(map[string]string)

dict["first"] = "red"
dict["second"] = "orange"
dict["third"] = "yellow"        

字面量初始化

m := map[string]string{
    "first":    "red",
    "second":   "orange",
    "third":    "yellow",
}