做产品的淘宝客网站,招聘网站如何做运营,洛阳建设信息网站,给网站做seo的价格1.什么是切片
在 Go 语言中的切片#xff08;slice#xff09;是一种灵活的动态数组#xff0c;它可以自动扩展和收缩#xff0c;是 Go 语言中非常重要的数据结构之一。切片是基于数组实现的#xff0c;它的底层是数组#xff0c;可以理解为对底层数组的抽象。它会生成一…1.什么是切片
在 Go 语言中的切片slice是一种灵活的动态数组它可以自动扩展和收缩是 Go 语言中非常重要的数据结构之一。切片是基于数组实现的它的底层是数组可以理解为对底层数组的抽象。它会生成一个指向数组的指针,并通过切片长度关联到底层数组部分或者全部元素。
2.切片底层结构
在 Go 语言的源码包中src/runtime/slice.go 定义了切片的数据结构
type slice struct {array unsafe.Pointerlen intcap int
}
切片占用 24 个字节 array指向底层数组的指针占用 8 个字节。当你创建一个切片时Go 会分配一个底层数组并将切片的指针指向该数组。 len切片中元素的数量占用 8 个字节。 cap切片的容量cap 总是大于等于 len占用 8 个字节。
3.初始化方式
通常用以下三种初始化切片 通过下标的方式获得数组的一部分
slice : arr[1:3] 使用字面量初始化新的切片
slice : []int{1, 2, 3} 使用关键字 make 创建切片
slice : make([]int, 10)
//或
slice : make([]int, 3, 5)
make函数有三个参数
第一个参数为切片类型可以是[]int[]string[]float32等。第二个参数为切片初始长度。第三个为切片容量该参数为可选参数。
注意点
var slice1 []int 这是声明此时切片为nil
例如
package mainimport fmtfunc main() {var slice1 []intif slice1 nil {fmt.Println(slice is nil!)}// slice1[0] 9 报错panic: runtime error: index out of range [0] with length 0slice1 append(slice1, 2) //可以fmt.Println(slice1)
}
结果
slice is nil!
[2]4.追加元素
append函数是Go语言内置的一个函数用于向切片中追加元素。
s : []int{1, 2, 3}
s append(s, 4)
fmt.Println(s) // 输出[1 2 3 4]
或
//如果你要追加的元素是另一个切片那么可以使用...运算符这样可以把那个切片的所有元素都追加进来。
s : []int{1, 2, 3}
t : []int{4, 5, 6}
s append(s, t...)
fmt.Println(s) // 输出[1 2 3 4 5 6]
5.切片表达式
Golang 中通常的 slice 语法是 a[low:high]您可能很熟悉。还有另一种切片语法形式为 a[low:high:max]它采用三个索引而不是两个索引。第三索引 max 是做什么的
提示: 不是 Python 切片语法 a[low:high:step] 中的 step 步长索引。
答: 第三个索引用于设置切片的容量在 Golang 规范中称为 “全切片表达式”。
简单切片表达式的格式[low:high]。
如下面例子n为一个切片当用这个表达式[1:4]表示的是左闭右开[low, high)区间截取一个新的切片例子中结果是[2 3 4]切片被截取之后截取的长度是high-low。
n : []int{1, 2, 3, 4, 5, 6}
fmt.Println(n[1:4]) // [2 3 4]
切片表达式的开始low和结束索引high是可选的它们分别默认为零和切片的长度
n : []int{1, 2, 3, 4, 5, 6}
fmt.Println(n[:4]) // [1 2 3 4]:前面没有值默认表示0
fmt.Println(n[1:]) // [2 3 4 5 6]:后面没有值默认表示切片的长度
边界问题 1、当n为数组或字符串表达式n[low:high]中low和high的取值关系 0 low high len(n) 2、当n为切片的时候表达式n[low:high]中high最大值变成了cap(n)low和high的取值关系 0 low high cap(n) 不满足以上条件会发送越界panic。
全切片表达式 n[low:high:max] max表示新生成切片的容量新切片容量等于max-low表达式中low、high、max关系 0 low high max cap(n) 继续刚才的例子当计算n[1:4]的容量用cap得到值等于5用扩展表达式n[1:4:5]用cap重新计算得到新的容量值5-1等于4 fmt.Println(cap(n[1:4])) // 5fmt.Println(cap(n[1:4:5])) // 4
关于容量
n[1:4]的长度是3好理解4-1容量为什么是5
因为切片n[1:4]和切片n是共享底层空间所以它的容量并不等于他的长度3根据1等于索引1的位置等于值2从值2这个元素开始到末尾元素6共5个所以n[1:4]容量是5。
如果append超过切片的长度会重新生产一个全新的切片不会覆盖原来的
func main() {n : []int{1, 2, 3, 4, 5, 6}n2 : n[1:4:5] // 长度等于3容量等于4fmt.Printf(%p\n, n2) // 0xc0000220c8n2 append(n2, 5)fmt.Printf(%p\n, n2) // 0xc0000220c8n2 append(n2, 6)fmt.Printf(%p\n, n2) // 地址发生改变0xc00001e080
}
6.常见使用陷阱
切片作为参数传递陷阱 在函数里修改切片元素的值原切片的值也会被改变 在函数里通过 append 方法对切片执行追加元素的操作可能会引起切片扩容导致内存分配的问题可能会对程序的性能 造成影响 在函数里通过 append 函数对切片执行追加元素的操作原切片里不存在新元素。
看一下代码示例
func main() {s : []int{0, 2, 3}fmt.Printf(main中1 切片的长度%d, 切片的容量%d, 切片的元素%v\n, len(s), cap(s), s) // 3 3 [0, 2, 3]sliceOperation(s)fmt.Printf(main中2 切片的长度%d, 切片的容量%d, 切片的元素%v\n, len(s), cap(s), s) // 3 3 [1, 2, 3]
}func sliceOperation(s []int) {s[0] 1fmt.Printf(sliceOperation中 切片的长度%d, 切片的容量%d, 切片的元素%v\n, len(s), cap(s), s) // 3 3 [1, 2, 3]
}
结果
main中1 切片的长度3, 切片的容量3, 切片的元素[0 2 3]
sliceOperation中 切片的长度3, 切片的容量3, 切片的元素[1 2 3]
main中2 切片的长度3, 切片的容量3, 切片的元素[1 2 3]看到切片中的元素会被改变就认为改变切片中的元素数据就直接将切片作为参数传过去就可以了但是如果用了append追加元素就会发现追加的元素并不会加到原切片中
func main() {s : []int{0, 2, 3}fmt.Printf(main中1 切片的长度%d, 切片的容量%d, 切片的元素%v\n, len(s), cap(s), s) // 3 3 [0, 2, 3]sliceOperation(s)fmt.Printf(main中2 切片的长度%d, 切片的容量%d, 切片的元素%v\n, len(s), cap(s), s) // 3 3 [1, 2, 3]
}func sliceOperation(s []int) {s append(s, 4)fmt.Printf(sliceOperation中 切片的长度%d, 切片的容量%d, 切片的元素%v\n, len(s), cap(s), s) // 4 6 [0, 2, 3]
}结果
main中1 切片的长度3, 切片的容量3, 切片的元素[0 2 3]
sliceOperation中 切片的长度4, 切片的容量6, 切片的元素[0 2 3 4]
main中2 切片的长度3, 切片的容量3, 切片的元素[0 2 3]若想实现执行 append 函数之后原切片也能得到新元素需将函数的参数类型由 切片类型 改成 切片指针类型。
func main() {s : []int{0, 2, 3}fmt.Printf(main中1 切片的长度%d, 切片的容量%d, 切片的元素%v\n, len(s), cap(s), s) sliceOperation(s)fmt.Printf(main中2 切片的长度%d, 切片的容量%d, 切片的元素%v\n, len(s), cap(s), s)
}func sliceOperation(s *[]int) {*s append(*s, 4)fmt.Printf(sliceOperation中 切片的长度%d, 切片的容量%d, 切片的元素%v\n, len(*s), cap(*s), s)
}
结果
main中1 切片的长度3, 切片的容量3, 切片的元素[0 2 3]
sliceOperation中 切片的长度4, 切片的容量6, 切片的元素[0 2 3 4]
main中2 切片的长度4, 切片的容量6, 切片的元素[0 2 3 4]slice 通过 make 函数初始化后续操作不当所造成的陷阱
1.使用 make 函数初始化切片后如果在后续操作中没有正确处理切片长度容易造成以下陷阱
func main() {s : make([]int, 0, 4)s[0] 1 // panic: runtime error: index out of range [0] with length 0
}
通过 make([]int, 0, 4) 初始化切片虽说容量为 4但是长度为 0如果通过索引去赋值会发生panic为避免 panic可以通过 s : make([]int, 4) 或 s : make([]int, 4, 4) 对切片进行初始化。
2.切片初始化不当通过 append 函数追加新元素的位置可能于预料之外
func main() {s : make([]int, 4)s append(s, 1)fmt.Println(s[0]) // 0s2 : make([]int, 0, 4)s2 append(s2, 1)fmt.Println(s2[0]) // 1
} 通过打印结果可知对于切片 s元素 1 没有被放置在第一个位置而对于切片 s2元素 1 被放置在切片的第一个位置。这是因为通过 make([]int, 4) 和 make([]int, 0, 4) 初始化切片底层所指向的数组的值是不一样的 第一种初始化的方式切片的长度和容量都为 4底层所指向的数组长度也是 4数组的值为 [0, 0, 0, 0]每个位置的元素被赋值为零值s append(s, 1) 执行后s 切片的值为 [0, 0, 0, 0, 1] 第二种初始化的方式切片的长度为 0容量为 4底层所指向的数组长度为 0数组的值为 []s2 append(s2, 1) 执行后s2 切片的值为 [1] 通过 append 向切片追加元素会执行尾插操作。如果我们需要初始化一个空切片然后从第一个位置开始插入元素需要避免 make([]int, 4) 这种初始化的方式否则添加的结果会在预料之外。 性能陷阱 内存泄露 内存泄露是指程序分配内存后不再使用该内存但未将其释放导致内存资源被浪费。 切片引用切片场景如果一个切片有大量的元素而它只有少部分元素被引用其他元素存在于内存中但是没有被使用则会造成内存泄露。代码示例如下 var s []intfunc main() {sliceOperation()fmt.Println(s)}func sliceOperation() {a : make([]int, 0, 10)a append(a, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)s a[0:4]} 上述代码中切片 a 的元素有 10 个而切片 s 是基于 a 创建的它底层所指向的数组与 a 所指向的数组是同一个只不过范围为前四个元素而后六个元素依然存在于内存中却没有被使用这样会造成内存泄露。为了避免内存泄露我们可以对代码进行改造s a[0:4] → s append(s, a[0:4]...)通过 append 进行元素追加这样切片 a 底层的数组没有被引用后面会被 gc。
7.切片技巧
Go在Github的官方Wiki上介绍了切片的技巧SliceTricks[4]另外这个项目Go Slice Tricks Cheat Sheet[5]基于SliceTricks做了一系列的图比较直观。 AppendVector
这个是添加一个切片的操作上面我们在切片操作中已经介绍过。
a append(a, b...)Copy
这边给我们展示了三种copy的写法
b : make([]T, len(a))
copy(b, a)// 效率一般比上面的写法慢但是如果有更多他们效率更好
b append([]T(nil), a...)
b append(a[:0:0], a...)// 这个实现等价于makecopy。
// 但在Go工具链v1.16上实际上会比较慢。
b append(make([]T, 0, len(a)), a...)Cut
截掉切片[i,j之间的元素
a append(a[:i], a[j:]...)CutGC
上面的Cut如果元素是指针的话会存在内存泄露所以我们要对删除的元素设置nil等待GC。
copy(a[i:], a[j:])
for k, n : len(a)-ji, len(a); k n; k {a[k] nil // or the zero value of T
}
a a[:len(a)-ji]Delete
删除索引位置i的元素
a append(a[:i], a[i1:]...)
// or
a a[:icopy(a[i:], a[i1:])]DeleteGC
删除索引位置i的元素
copy(a[i:], a[i1:])
a[len(a)-1] nil // or the zero value of T
a a[:len(a)-1]Delete without preserving order
删除索引位置i的元素把最后一位放到索引位置i上然后把最后一位元素删除。这种方式底层并没有发生复制操作。
a[i] a[len(a)-1]
a a[:len(a)-1]Delete without preserving orderGC
上面的删除操作元素是一个指针的类型或结构体指针字段会存在最后一个元素不能被GC掉造成泄露把末尾的元素设置nil等待GC。
a[i] a[len(a)-1]
a[len(a)-1] nil
a a[:len(a)-1]Expand
这个本质上是多个append的组合操作。
a append(a[:i], append(make([]T, j), a[i:]...)...) Extend
用新列表扩展原来的列表
a append(a, make([]T, j)...)Filter (in place)
下面代码演示原地删除Go切片元素
n : 0
for _, x : range a {if keep(x) {a[n] xn}
}
a a[:n]Insert
a append(a[:i], append([]T{x}, a[i:]...)...)第二个append会产生新的切片产生一次copy可以用以下代码方式可免去第二次的copy
s append(s, 0 /* 先添加一个0值*/)
copy(s[i1:], s[i:])
s[i] xInsertVector
下面代码演示插入向量封装了动态大小数组的顺序容器的实现
a append(a[:i], append(b, a[i:]...)...)func Insert(s []int, k int, vs ...int) []int {if n : len(s) len(vs); n cap(s) {s2 : s[:n]copy(s2[klen(vs):], s[k:])copy(s2[k:], vs)return s2}s2 : make([]int, len(s) len(vs))copy(s2, s[:k])copy(s2[k:], vs)copy(s2[klen(vs):], s[k:])return s2
}a Insert(a, i, b...)Push
a append(a, x)Pop x, a a[len(a)-1], a[:len(a)-1]Push Front/Unshift
a append([]T{x}, a...)Pop Front/Shift
x, a a[0], a[1:] 7 切片额外技巧
Filtering without allocating
下面例子演示数据过滤的时候b基于原来的a存储空间来操作并没有重新生成新的存储空间。
b : a[:0]
for _, x : range a {if f(x) {b append(b, x)}
}为了让截取之后没有使用的存储被GC掉需要设置成nil
for i : len(b); i len(a); i {a[i] nil // or the zero value of T
}Reversing
反转操作演示
for i : len(a)/2-1; i 0; i-- {opp : len(a)-1-ia[i], a[opp] a[opp], a[i]
}还有一种方法
for left, right : 0, len(a)-1; left right; left, right left1, right-1 {a[left], a[right] a[right], a[left]
}Shuffling
洗牌算法。算法思想就是从原始数组中随机抽取一个新的数字到新数组中。
for i : len(a) - 1; i 0; i-- {j : rand.Intn(i 1)a[i], a[j] a[j], a[i]
}go1.10之后有内置函数Shuffle[6] Batching with minimal allocation
做批处理大的切片的时候这个技巧可以了解下
actions : []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
batchSize : 3
batches : make([][]int, 0, (len(actions) batchSize - 1) / batchSize)for batchSize len(actions) {actions, batches actions[batchSize:], append(batches, actions[0:batchSize:batchSize])
}
batches append(batches, actions)// 结果
// [[0 1 2] [3 4 5] [6 7 8] [9]] In-place deduplicate (comparable)
删除有序数组中的重复项
import sortin : []int{3,2,1,4,3,2,1,4,1} // any item can be sorted
sort.Ints(in)
j : 0
for i : 1; i len(in); i {if in[j] in[i] {continue}j// preserve the original data// in[i], in[j] in[j], in[i]// only set what is requiredin[j] in[i]
}
result : in[:j1]
fmt.Println(result) // [1 2 3 4]Move to front, or prepend if not present, in place if possible.
下面代码演示移动指定元素到头部
// moveToFront moves needle to the front of haystack, in place if possible.
func moveToFront(needle string, haystack []string) []string {if len(haystack) ! 0 haystack[0] needle {return haystack}prev : needlefor i, elem : range haystack {switch {case i 0:haystack[0] needleprev elemcase elem needle:haystack[i] prevreturn haystackdefault:haystack[i] prevprev elem}}return append(haystack, prev)
}haystack : []string{a, b, c, d, e} // [a b c d e]
haystack moveToFront(c, haystack) // [c a b d e]
haystack moveToFront(f, haystack) // [f c a b d e]Sliding Window
下面实现根据size的滑动窗口输出
func slidingWindow(size int, input []int) [][]int {// returns the input slice as the first elementif len(input) size {return [][]int{input}}// allocate slice at the precise size we needr : make([][]int, 0, len(input)-size1)for i, j : 0, size; j len(input); i, j i1, j1 {r append(r, input[i:j])}return r
}func TestSlidingWindow(t *testing.T) {result : slidingWindow(2, []int{1, 2, 3, 4, 5})fmt.Println(result) // [[1 2] [2 3] [3 4] [4 5]]
}最后的技巧章节参考微信公众号太白技术 Go切片与技巧附图解