Java技术债务Java技术债务

  •  首页
  •  分类
  •  归档
  •  标签
  • 博客日志
  • 资源分享
  •  友链
  •  关于本站
注册
登录

Go语言的容器

Go

文章目录

  • Arrays(数组)
  • Slices(切片)
  • Map
  • list(列表)

Arrays(数组)

类型 [n]T 表示拥有 n 个 T 类型的值的数组.

表达式:var a [10]int

会将变量 a 声明为拥有 10 个整数的数组.

数组的长度是其类型的一部分,因此无法调整数组的大小。这似乎是限制性的

package main

import "fmt"

func main() {
    var a [2]string
    a[0] = "Hello"
    a[1] = "World"
    fmt.Println(a[0], a[1])
    fmt.Println(a)

    primes := [6]int{2, 3, 5, 7, 11, 13}
    fmt.Println(primes)
}

Slices(切片)

数组具有固定大小; 而切片则为数组元素提供动态大小的、灵活的视角。

切片的零值是 nil 一个 nil 切片的长度和容量为 0,并且没有底层数组。切片可以包含任何类型,包括其他切片。

切片不存储任何数据,它只是描述底层数组的一部分,更改切片的元素会修改其底层数组的相应元素,共享相同底层数组的其他切片将看到这些更改。

在实践中,切片比数组更常见。

类型 []T 表示一个元素类型为 T 的切片。通过指定两个索引(下限和上限)来形成切片,并用冒号分隔。

a[low : high]

这将选择一个包含第一个元素但不包括最后一个元素的半开范围.

以下表达式创建一个包含 a 的 1 到 3 元素的切片.

a[1:4]
package main

import "fmt"

func main() {
    primes := [6]int{2, 3, 5, 7, 11, 13}

    var s []int = primes[1:4]
    fmt.Println(s)
}

我认为从表现上显示:和Java中的subString()类似,只是Java中subString()方法是针对字符串进行截取,而Go中的切片是针对数组进行截取。

Slice literals(切片字面量)

切片字面量就像没有长度的数组字面量。这是一个数组字面量:

[3]bool{true, true, false}

这将创建与上面相同的数组,然后构建一个引用它的切片

[]bool{true, true, false}
package main

import "fmt"

func main() {
    q := []int{2, 3, 5, 7, 11, 13}
    fmt.Println(q)

    r := []bool{true, false, true, true, false, true}
    fmt.Println(r)

    s := []struct {
        i int
        b bool
    }{
        {2, true},
        {3, false},
        {5, true},
        {7, true},
        {11, false},
        {13, true},
    }
    fmt.Println(s)
}

切片默认值

切片时,可以省略上限或下限以使用它们的默认值。

下限默认为零,上限默认为切片长度。

对于数组

var a [10]int

这些切片表达式是等价的:

a[0:10]
a[:10]
a[0:]
a[:]
package main

import "fmt"

func main() {
    s := []int{2, 3, 5, 7, 11, 13}

    s = s[1:4]
    fmt.Println(s)

    s = s[:2]
    fmt.Println(s)

    s = s[1:]
    fmt.Println(s)
}

切片长度和容量

切片同时具有 长度 和 容量 .

切片的长度是它包含的元素数.

切片的容量是底层数组中元素的数量,从切片中的第一个元素开始计数.

切片 s 的长度和容量可通过表达式 len(s) 和 cap(s) 来获取.

如果切片具有足够的容量,则可以通过重新切片来延长切片的长度。

用 make 创建切片

可以使用内置 make 函数创建切片;这是创建动态大小数组的方式.

make 函数分配一个归零数组并返回一个引用该数组的切片:

a := make([]int, 5)  // len(a)=5

要指定容量,请将第三个参数传递给 make:

b := make([]int, 0, 5) // len(b)=0, cap(b)=5

b = b[:cap(b)] // len(b)=5, cap(b)=5
b = b[1:]      // len(b)=4, cap(b)=4

切片追加元素

将新元素附加到切片是很常见的,因此 Go 提供了一个内置 append 函数。

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

append 的第一个参数 s 是一个元素类型为T 的切片,其余类型为 T 的值将会追加到该切片的末尾.

append 的结果是一个包含原切片所有元素加上新添加元素的切片.

如果 s 的底层数组太小而无法容纳所有给定值,则将分配一个更大的数组。返回的切片将指向新分配的数组。

package main

import "fmt"

func main() {
    var s []int
    printSlice(s)

    // append works on nil slices.
    s = append(s, 0)
    printSlice(s)

    // The slice grows as needed.
    s = append(s, 1)
    printSlice(s)

    // We can add more than one element at a time.
    s = append(s, 2, 3, 4)
    printSlice(s)
}

func printSlice(s []int) {
    fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}

Range

for 循环的 range 形式可遍历切片或映射.

当在切片上进行ranging时,每次迭代都会返回两个值。第一个是索引,第二个是该索引中元素的副本。可以通过赋值给 _ 来跳过索引或值.

for i, _ := range pow
for _, value := range pow

如果只需要索引,则可以省略第二个变量。

for i := range pow
package main

import "fmt"

func main() {
    pow := make([]int, 10)
    for i := range pow {
        pow[i] = 1 << uint(i) // == 2**i
    }
    for _, value := range pow {
        fmt.Printf("%d\n", value)
    }
}

Map

Map 是一种无序的键值对的集合。通过 key 来快速检索数据,key 类似于索引,指向数据的值。

map将键映射到值。map的零值是 nil

nil map 没有键,也不能添加键。

make 函数返回给定类型的map,该map已初始化并可供使用

Map 是一种集合,所以我们可以像迭代数组和切片那样迭代它。不过,Map 是无序的,遍历 Map 时返回的键值对的顺序是不确定的。

定义 Map

可以使用内建函数 make 或使用 map 关键字来定义 Map:

/* 使用 make 函数 */
map_variable := make(map[KeyType]ValueType, initialCapacity)

Map字面量类似于结构字面量,但需要键。如果顶级类型只是一个类型名称,则可以从字面量的元素中省略它.

package main

import "fmt"

type Vertex struct {
    Lat, Long float64
}

var m map[string]Vertex

var m2 = map[string]Vertex{
    "Bell Labs": Vertex{
        40.68433, -74.39967,
    },
    "Google": {
        37.42202, -122.08408,
    },
}

func main() {
    m = make(map[string]Vertex)
    m["Bell Labs"] = Vertex{
        40.68433, -74.39967,
    }
    fmt.Println(m["Bell Labs"])

    var m = map[string]int64{}
    fmt.Println(m)
    m["aa"] = 1
    m["bb"] = 2
    m["bb"] = 3
    fmt.Println(m)
    fmt.Println(m2)
}

map 容量

和数组不同,map 可以根据新增的 key-value 动态的伸缩,因此它不存在固定长度或者最大限制,但是也可以选择标明 map 的初始容量 capacity

格式:make(map[keytype]valuetype, cap)

当 map 增长到容量上限的时候,如果再增加新的 key-value,map 的大小会自动加 1,所以出于性能的考虑,对于大的 map 或者会快速扩张的 map,即使只是大概知道容量,也最好先标明。

修改 Maps

  • 在 map m 中插入或更新元素: m[key] = elem
  • 检索一个元素:elem = m[key] 如果 key 不在map中,则 elem 是map元素类型的零值。
  • 删除一个元素:delete(m, key)
  • 通过双赋值检测某个键是否存在:elem, ok = m[key]如果 key 在 m 中,ok 为 true ;否则,ok 为 false

注意: 如果 elem 或 ok 尚未声明,您可以使用简短的声明形式:elem, ok := m[key]

用切片作为 map 的值

既然一个 key 只能对应一个 value,而 value 又是一个原始类型,那么如果一个 key 要对应多个值怎么办?例如,当我们要处理 unix 机器上的所有进程,以父进程(pid 为整形)作为 key,所有的子进程(以所有子进程的 pid 组成的切片)作为 value。通过将 value 定义为 []int 类型或者其他类型的切片,就可以优雅的解决这个问题,示例代码如下所示:

mp1 := make(map[int][]int)mp2 := make(map[int]*[]int)

Map 的长度

// 获取 Map 的长度
len := len(m)

遍历map

map 的遍历过程使用 for range 循环完成,代码如下:

scene := make(map[string]int)
scene["route"] = 66
scene["brazil"] = 4
scene["china"] = 960
for k, v := range scene {
    fmt.Println(k, v)
}

遍历对于Go语言的很多对象来说都是差不多的,直接使用 for range 语法即可,遍历时,可以同时获得键和值,如只遍历值,可以使用的形式:for _, v := range scene {

将不需要的键使用,改为匿名变量形式。

for k := range scene {

排序map

sort.Strings 的作用是对传入的字符串切片进行字符串字符的升序排列

map删除key和value

使用 delete() 函数从 map 中删除键值对:delete(map, 键)

清空 map 中的所有元素

Go语言中并没有为 map 提供任何清空所有元素的函数、方法,清空 map 的唯一办法就是重新 make 一个新的 map,不用担心垃圾回收的效率,Go语言中的并行垃圾回收效率比写一个清空函数要高效的多。

sync.Map(在并发环境中使用的map)

Go语言中的 map 在并发情况下,只读是线程安全的,同时读写是线程不安全的。

需要并发读写时,一般的做法是加锁,但这样性能并不高,Go语言在 1.9 版本中提供了一种效率较高的并发安全的 sync.Map,sync.Map 和 map 不同,不是以语言原生形态提供,而是在 sync 包下的特殊结构。

sync.Map 有以下特性:

  • 无须初始化,直接声明即可。
  • sync.Map 不能使用 map 的方式进行取值和设置等操作,而是使用 sync.Map 的方法进行调用,Store 表示存储,Load 表示获取,Delete 表示删除。
  • 使用 Range 配合一个回调函数进行遍历操作,通过回调函数返回内部遍历出来的值,Range 参数中回调函数的返回值在需要继续迭代遍历时,返回 true,终止迭代遍历时,返回 false。
package main
import (
      "fmt"
      "sync"
)
func main() {
    var scene sync.Map
    // 将键值对保存到sync.Map
    scene.Store("greece", 97)
    scene.Store("london", 100)
    scene.Store("egypt", 200)
    // 从sync.Map中根据键取值
    fmt.Println(scene.Load("london"))
    // 根据键删除对应的键值对
    scene.Delete("london")
    // 遍历所有sync.Map中的键值对
    scene.Range(func(k, v interface{}) bool {
        fmt.Println("iterate:", k, v)
        return true
    })
}

sync.Map 没有提供获取 map 数量的方法,替代方法是在获取 sync.Map 时遍历自行计算数量,sync.Map 为了保证并发安全有一些性能损失,因此在非并发情况下,使用 map 相比使用 sync.Map 会有更好的性能。

list(列表)

列表是一种非连续的存储容器,由多个节点组成,节点通过一些变量记录彼此之间的关系,列表有多种实现方法,如单链表、双链表等。

列表的原理可以这样理解:假设 A、B、C 三个人都有电话号码,如果 A 把号码告诉给 B,B 把号码告诉给 C,这个过程就建立了一个单链表结构,如下图所示。

如果在这个基础上,再从 C 开始将自己的号码告诉给自己所知道号码的主人,这样就形成了双链表结构,如下图所示。

那么如果需要获得所有人的号码,只需要从 A 或者 C 开始,要求他们将自己的号码发出来,然后再通知下一个人如此循环,这样就构成了一个列表遍历的过程。

如果 B 换号码了,他需要通知 A 和 C,将自己的号码移除,这个过程就是列表元素的删除操作,如下图所示。

在Go语言中,列表使用 container/list 包来实现,内部的实现原理是双链表,列表能够高效地进行任意位置的元素插入和删除操作。

初始化列表

list 的初始化有两种方法:分别是使用 New() 函数和 var 关键字声明,两种方法的初始化效果都是一致的。

  • 通过 container/list 包的 New() 函数初始化 list

    变量名 := list.New()

  • 通过 var 关键字声明初始化 list

    var 变量名 list.List

列表与切片和 map 不同的是,列表并没有具体元素类型的限制,因此,列表的元素可以是任意类型,这既带来了便利,也引来一些问题,例如给列表中放入了一个 interface{} 类型的值,取出值后,如果要将 interface{} 转换为其他类型将会发生宕机。

列表中插入元素

双链表支持从队列前方或后方插入元素,分别对应的方法是 PushFront 和 PushBack。

这两个方法都会返回一个 list.Element 结构,如果在以后的使用中需要删除插入的元素,则只能通过 list.Element 配合 Remove() 方法进行删除,这种方法可以让删除更加效率化,同时也是双链表特性之一。下面代码展示如何给 list 添加元素:

l := list.New()l.PushBack("fist")l.PushFront(67)

列表中删除元素

列表插入函数的返回值会提供一个 *list.Element 结构,这个结构记录着列表元素的值以及与其他节点之间的关系等信息,从列表中删除元素时,需要用到这个结构进行快速删除。列表操作元素:

package main
import "container/list"
func main() {
    l := list.New()
    // 尾部添加
    l.PushBack("canon")
    // 头部添加
    l.PushFront(67)
    // 尾部添加后保存元素句柄
    element := l.PushBack("fist")
    // 在fist之后添加high
    l.InsertAfter("high", element)
    // 在fist之前添加noon
    l.InsertBefore("noon", element)
    // 使用
    l.Remove(element)
}

遍历列表

遍历双链表需要配合 Front() 函数获取头元素,遍历时只要元素不为空就可以继续进行,每一次遍历都会调用元素的 Next() 函数,代码如下所示。

l := list.New()
// 尾部添加
l.PushBack("canon")
// 头部添加
l.PushFront(67)
for i := l.Front(); i != nil; i = i.Next() {
    fmt.Println(i.Value)
}

Arrays(数组)

类型 [n]T 表示拥有 n 个 T 类型的值的数组.

表达式:var a [10]int

会将变量 a 声明为拥有 10 个整数的数组.

数组的长度是其类型的一部分,因此无法调整数组的大小。这似乎是限制性的

package main

import "fmt"

func main() {
    var a [2]string
    a[0] = "Hello"
    a[1] = "World"
    fmt.Println(a[0], a[1])
    fmt.Println(a)

    primes := [6]int{2, 3, 5, 7, 11, 13}
    fmt.Println(primes)
}

Slices(切片)

数组具有固定大小; 而切片则为数组元素提供动态大小的、灵活的视角。

切片的零值是 nil 一个 nil 切片的长度和容量为 0,并且没有底层数组。切片可以包含任何类型,包括其他切片。

切片不存储任何数据,它只是描述底层数组的一部分,更改切片的元素会修改其底层数组的相应元素,共享相同底层数组的其他切片将看到这些更改。

在实践中,切片比数组更常见。

类型 []T 表示一个元素类型为 T 的切片。通过指定两个索引(下限和上限)来形成切片,并用冒号分隔。

a[low : high]

这将选择一个包含第一个元素但不包括最后一个元素的半开范围.

以下表达式创建一个包含 a 的 1 到 3 元素的切片.

a[1:4]
package main

import "fmt"

func main() {
    primes := [6]int{2, 3, 5, 7, 11, 13}

    var s []int = primes[1:4]
    fmt.Println(s)
}

我认为从表现上显示:和Java中的subString()类似,只是Java中subString()方法是针对字符串进行截取,而Go中的切片是针对数组进行截取。

Slice literals(切片字面量)

切片字面量就像没有长度的数组字面量。这是一个数组字面量:

[3]bool{true, true, false}

这将创建与上面相同的数组,然后构建一个引用它的切片

[]bool{true, true, false}
package main

import "fmt"

func main() {
    q := []int{2, 3, 5, 7, 11, 13}
    fmt.Println(q)

    r := []bool{true, false, true, true, false, true}
    fmt.Println(r)

    s := []struct {
        i int
        b bool
    }{
        {2, true},
        {3, false},
        {5, true},
        {7, true},
        {11, false},
        {13, true},
    }
    fmt.Println(s)
}

切片默认值

切片时,可以省略上限或下限以使用它们的默认值。

下限默认为零,上限默认为切片长度。

对于数组

var a [10]int

这些切片表达式是等价的:

a[0:10]
a[:10]
a[0:]
a[:]
package main

import "fmt"

func main() {
    s := []int{2, 3, 5, 7, 11, 13}

    s = s[1:4]
    fmt.Println(s)

    s = s[:2]
    fmt.Println(s)

    s = s[1:]
    fmt.Println(s)
}

切片长度和容量

切片同时具有 长度 和 容量 .

切片的长度是它包含的元素数.

切片的容量是底层数组中元素的数量,从切片中的第一个元素开始计数.

切片 s 的长度和容量可通过表达式 len(s) 和 cap(s) 来获取.

如果切片具有足够的容量,则可以通过重新切片来延长切片的长度。

用 make 创建切片

可以使用内置 make 函数创建切片;这是创建动态大小数组的方式.

make 函数分配一个归零数组并返回一个引用该数组的切片:

a := make([]int, 5)  // len(a)=5

要指定容量,请将第三个参数传递给 make:

b := make([]int, 0, 5) // len(b)=0, cap(b)=5

b = b[:cap(b)] // len(b)=5, cap(b)=5
b = b[1:]      // len(b)=4, cap(b)=4

切片追加元素

将新元素附加到切片是很常见的,因此 Go 提供了一个内置 append 函数。

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

append 的第一个参数 s 是一个元素类型为T 的切片,其余类型为 T 的值将会追加到该切片的末尾.

append 的结果是一个包含原切片所有元素加上新添加元素的切片.

如果 s 的底层数组太小而无法容纳所有给定值,则将分配一个更大的数组。返回的切片将指向新分配的数组。

package main

import "fmt"

func main() {
    var s []int
    printSlice(s)

    // append works on nil slices.
    s = append(s, 0)
    printSlice(s)

    // The slice grows as needed.
    s = append(s, 1)
    printSlice(s)

    // We can add more than one element at a time.
    s = append(s, 2, 3, 4)
    printSlice(s)
}

func printSlice(s []int) {
    fmt.Printf("len=%d cap=%d %vn", len(s), cap(s), s)
}

Range

for 循环的 range 形式可遍历切片或映射.

当在切片上进行ranging时,每次迭代都会返回两个值。第一个是索引,第二个是该索引中元素的副本。可以通过赋值给 _ 来跳过索引或值.

for i, _ := range pow
for _, value := range pow

如果只需要索引,则可以省略第二个变量。

for i := range pow
package main

import "fmt"

func main() {
    pow := make([]int, 10)
    for i := range pow {
        pow[i] = 1 << uint(i) // == 2**i
    }
    for _, value := range pow {
        fmt.Printf("%dn", value)
    }
}

Map

Map 是一种无序的键值对的集合。通过 key 来快速检索数据,key 类似于索引,指向数据的值。

map将键映射到值。map的零值是 nil

nil map 没有键,也不能添加键。

make 函数返回给定类型的map,该map已初始化并可供使用

Map 是一种集合,所以我们可以像迭代数组和切片那样迭代它。不过,Map 是无序的,遍历 Map 时返回的键值对的顺序是不确定的。

定义 Map

可以使用内建函数 make 或使用 map 关键字来定义 Map:

/* 使用 make 函数 */
map_variable := make(map[KeyType]ValueType, initialCapacity)

Map字面量类似于结构字面量,但需要键。如果顶级类型只是一个类型名称,则可以从字面量的元素中省略它.

package main

import "fmt"

type Vertex struct {
    Lat, Long float64
}

var m map[string]Vertex

var m2 = map[string]Vertex{
    "Bell Labs": Vertex{
        40.68433, -74.39967,
    },
    "Google": {
        37.42202, -122.08408,
    },
}

func main() {
    m = make(map[string]Vertex)
    m["Bell Labs"] = Vertex{
        40.68433, -74.39967,
    }
    fmt.Println(m["Bell Labs"])

    var m = map[string]int64{}
    fmt.Println(m)
    m["aa"] = 1
    m["bb"] = 2
    m["bb"] = 3
    fmt.Println(m)
    fmt.Println(m2)
}

map 容量

和数组不同,map 可以根据新增的 key-value 动态的伸缩,因此它不存在固定长度或者最大限制,但是也可以选择标明 map 的初始容量 capacity

格式:make(map[keytype]valuetype, cap)

当 map 增长到容量上限的时候,如果再增加新的 key-value,map 的大小会自动加 1,所以出于性能的考虑,对于大的 map 或者会快速扩张的 map,即使只是大概知道容量,也最好先标明。

修改 Maps

  • 在 map m 中插入或更新元素: m[key] = elem
  • 检索一个元素:elem = m[key] 如果 key 不在map中,则 elem 是map元素类型的零值。
  • 删除一个元素:delete(m, key)
  • 通过双赋值检测某个键是否存在:elem, ok = m[key]如果 key 在 m 中,ok 为 true ;否则,ok 为 false

注意: 如果 elem 或 ok 尚未声明,您可以使用简短的声明形式:elem, ok := m[key]

用切片作为 map 的值

既然一个 key 只能对应一个 value,而 value 又是一个原始类型,那么如果一个 key 要对应多个值怎么办?例如,当我们要处理 unix 机器上的所有进程,以父进程(pid 为整形)作为 key,所有的子进程(以所有子进程的 pid 组成的切片)作为 value。通过将 value 定义为 []int 类型或者其他类型的切片,就可以优雅的解决这个问题,示例代码如下所示:

mp1 := make(map[int][]int)mp2 := make(map[int]*[]int)

Map 的长度

// 获取 Map 的长度
len := len(m)

遍历map

map 的遍历过程使用 for range 循环完成,代码如下:

scene := make(map[string]int)
scene["route"] = 66
scene["brazil"] = 4
scene["china"] = 960
for k, v := range scene {
    fmt.Println(k, v)
}

遍历对于Go语言的很多对象来说都是差不多的,直接使用 for range 语法即可,遍历时,可以同时获得键和值,如只遍历值,可以使用的形式:for _, v := range scene {

将不需要的键使用,改为匿名变量形式。

for k := range scene {

排序map

sort.Strings 的作用是对传入的字符串切片进行字符串字符的升序排列

map删除key和value

使用 delete() 函数从 map 中删除键值对:delete(map, 键)

清空 map 中的所有元素

Go语言中并没有为 map 提供任何清空所有元素的函数、方法,清空 map 的唯一办法就是重新 make 一个新的 map,不用担心垃圾回收的效率,Go语言中的并行垃圾回收效率比写一个清空函数要高效的多。

sync.Map(在并发环境中使用的map)

Go语言中的 map 在并发情况下,只读是线程安全的,同时读写是线程不安全的。

需要并发读写时,一般的做法是加锁,但这样性能并不高,Go语言在 1.9 版本中提供了一种效率较高的并发安全的 sync.Map,sync.Map 和 map 不同,不是以语言原生形态提供,而是在 sync 包下的特殊结构。

sync.Map 有以下特性:

  • 无须初始化,直接声明即可。
  • sync.Map 不能使用 map 的方式进行取值和设置等操作,而是使用 sync.Map 的方法进行调用,Store 表示存储,Load 表示获取,Delete 表示删除。
  • 使用 Range 配合一个回调函数进行遍历操作,通过回调函数返回内部遍历出来的值,Range 参数中回调函数的返回值在需要继续迭代遍历时,返回 true,终止迭代遍历时,返回 false。
package main
import (
      "fmt"
      "sync"
)
func main() {
    var scene sync.Map
    // 将键值对保存到sync.Map
    scene.Store("greece", 97)
    scene.Store("london", 100)
    scene.Store("egypt", 200)
    // 从sync.Map中根据键取值
    fmt.Println(scene.Load("london"))
    // 根据键删除对应的键值对
    scene.Delete("london")
    // 遍历所有sync.Map中的键值对
    scene.Range(func(k, v interface{}) bool {
        fmt.Println("iterate:", k, v)
        return true
    })
}

sync.Map 没有提供获取 map 数量的方法,替代方法是在获取 sync.Map 时遍历自行计算数量,sync.Map 为了保证并发安全有一些性能损失,因此在非并发情况下,使用 map 相比使用 sync.Map 会有更好的性能。

list(列表)

列表是一种非连续的存储容器,由多个节点组成,节点通过一些变量记录彼此之间的关系,列表有多种实现方法,如单链表、双链表等。

列表的原理可以这样理解:假设 A、B、C 三个人都有电话号码,如果 A 把号码告诉给 B,B 把号码告诉给 C,这个过程就建立了一个单链表结构,如下图所示。

如果在这个基础上,再从 C 开始将自己的号码告诉给自己所知道号码的主人,这样就形成了双链表结构,如下图所示。

那么如果需要获得所有人的号码,只需要从 A 或者 C 开始,要求他们将自己的号码发出来,然后再通知下一个人如此循环,这样就构成了一个列表遍历的过程。

如果 B 换号码了,他需要通知 A 和 C,将自己的号码移除,这个过程就是列表元素的删除操作,如下图所示。

在Go语言中,列表使用 container/list 包来实现,内部的实现原理是双链表,列表能够高效地进行任意位置的元素插入和删除操作。

初始化列表

list 的初始化有两种方法:分别是使用 New() 函数和 var 关键字声明,两种方法的初始化效果都是一致的。

  • 通过 container/list 包的 New() 函数初始化 list

    变量名 := list.New()

  • 通过 var 关键字声明初始化 list

    var 变量名 list.List

列表与切片和 map 不同的是,列表并没有具体元素类型的限制,因此,列表的元素可以是任意类型,这既带来了便利,也引来一些问题,例如给列表中放入了一个 interface{} 类型的值,取出值后,如果要将 interface{} 转换为其他类型将会发生宕机。

列表中插入元素

双链表支持从队列前方或后方插入元素,分别对应的方法是 PushFront 和 PushBack。

这两个方法都会返回一个 list.Element 结构,如果在以后的使用中需要删除插入的元素,则只能通过 list.Element 配合 Remove() 方法进行删除,这种方法可以让删除更加效率化,同时也是双链表特性之一。下面代码展示如何给 list 添加元素:

l := list.New()l.PushBack("fist")l.PushFront(67)

列表中删除元素

列表插入函数的返回值会提供一个 *list.Element 结构,这个结构记录着列表元素的值以及与其他节点之间的关系等信息,从列表中删除元素时,需要用到这个结构进行快速删除。列表操作元素:

package main
import "container/list"
func main() {
    l := list.New()
    // 尾部添加
    l.PushBack("canon")
    // 头部添加
    l.PushFront(67)
    // 尾部添加后保存元素句柄
    element := l.PushBack("fist")
    // 在fist之后添加high
    l.InsertAfter("high", element)
    // 在fist之前添加noon
    l.InsertBefore("noon", element)
    // 使用
    l.Remove(element)
}

遍历列表

遍历双链表需要配合 Front() 函数获取头元素,遍历时只要元素不为空就可以继续进行,每一次遍历都会调用元素的 Next() 函数,代码如下所示。

l := list.New()
// 尾部添加
l.PushBack("canon")
// 头部添加
l.PushFront(67)
for i := l.Front(); i != nil; i = i.Next() {
    fmt.Println(i.Value)
}
完
  • 本文作者:Java技术债务
  • 原文链接: https://cuizb.top/myblog/article/1687918864
  • 版权声明: 本博客所有文章除特别声明外,均采用 CC BY 3.0 CN协议进行许可。转载请署名作者且注明文章出处。
阅读全文
Java技术债务

Java技术债务

Java技术债务
Java技术债务
热门文章
  1. ClickHouse使用过程中的一些查询优化(六)2003
  2. MySQL数据库被攻击,被删库勒索,逼迫我使出洪荒之力进行恢复数据764
  3. MySQL主从同步原理458
  4. 线程池的理解以及使用414
  5. Spring Cloud Gateway整合nacos实战(三)409
分类
  • Java
    30篇
  • 设计模式
    27篇
  • 数据库
    20篇
  • Spring
    18篇
  • MySQL
    13篇
  • ClickHouse
    11篇
  • Kubernetes
    10篇
  • Redis
    9篇
  • Docker
    8篇
  • SpringBoot
    7篇
  • JVM
    6篇
  • Linux
    5篇
  • Spring Cloud
    5篇
  • 多线程
    5篇
  • Netty
    4篇
  • Kafka
    4篇
  • 面经
    4篇
  • Nginx
    3篇
  • JUC
    3篇
  • 随笔
    2篇
  • 分布式
    1篇
  • MyBatis
    1篇
  • 报错合集
    1篇
  • 生活记录
    1篇
  • 源码
    1篇
  • 性能优化
    1篇

最新评论

  • MySQL数据库被攻击,被删库勒索,逼迫我使出洪荒之力进行恢复数据2022-05-06
    Java技术债务:@capture 一起探讨学习,服务器被黑很正常,及时做好备份以及做好防护
  • MySQL数据库被攻击,被删库勒索,逼迫我使出洪荒之力进行恢复数据2022-04-13
    capture:我的刚上线两天,网站里就两篇文章也被攻击了,纳闷
  • Java常用集合List、Map、Set介绍以及一些面试问题2022-01-18
    Java技术债务:HashSet和TreeSet 相同点:数据不能重复 不同点: 1、底层存储结构不同; HashSet底层使用HashMap哈希表存储 TreeSet底层使用TreeMap树结构存储 2、唯一性方式不同 HashSet底层使用hashcode()和equal()方法判断 TreeSet底层使用Comparable接口的compareTo判断的 3、HashSet无序,TreeSet有序
  • undefined2021-12-14
    Java技术债务:如果不指定线程池,CompletableFuture会默认使用ForkJoin线程池,如果同一时间出现大量请求的话,会出现线程等待问题,建议使用自定义线程池。。。
  • undefined2021-12-02
    you:很好,对于小白相当不错了,谢谢
  • CSDN
  • 博客园
  • 程序猿DD
  • 纯洁的微笑
  • spring4all
  • 廖雪峰的官方网站
  • 猿天地
  • 泥瓦匠BYSocket
  • crossoverJie
  • 张先森个人博客
  • 越加网

© 2021-2023 Java技术债务 - Java技术债务 版权所有
总访问量 0 次 您是本文第 0 位童鞋
豫ICP备2021034516号
Java技术债务 豫公网安备 51011402000164号

微信公众号

Java技术债务
Java技术债务

专注于Spring,SpringBoot等后端技术探索

以及MySql数据库开发和Netty等后端流行框架学习

日志
分类
标签
RSS

有不足之处也希望各位前辈指出