当前位置:网站首页>[golang] how to clear slices gracefully

[golang] how to clear slices gracefully

2022-06-23 20:05:00 DDGarfield

This is an interesting question , Before that , Bloggers have never considered this issue , Until recently , After all, it's still with Empty the slice Met .

The scene is like this : Need to batch from influxdb Query data in , The query condition of this batch query is to traverse the field of a structure slice , constantly append, In order to avoid a large number of queries , Affecting query efficiency . The following processing is done on the code :

var queryIDs []int64
for _,v:= range vList{
    queryIDs=append(queryIDs,v.ID)
    if len(queryIDs)>50{
        //omit query code
        queryIDs=[]int64{}
    }
}

if len(queryIDs) > 0 {
     //omit query code
}

During traversal , When queryIDs Is longer than 50, Start the query now , After the query , Slice empty , After the traversal is over ,queryIDs There are still data , The rest of the query continues .

1. Method 1

Like the code above :

func main() {
    sliceIntA := make([]int, 0, 50)
    sliceIntA = append(sliceIntA, 1, 2, 3)
    sliceIntA = []int{}
}

It's a way , But we want to know what happens to the slice ?

func main() {
    sliceTestB := make([]int, 0, 50)
    fmt.Printf("len of sliceIntA:%d,cap of sliceIntA:%d\n", len(sliceIntA), cap(sliceIntA))
    sliceTestB = append(sliceTestB, 1, 2, 3)
    fmt.Printf("len of sliceIntA:%d,cap of sliceIntA:%d\n", len(sliceIntA), cap(sliceIntA))
    sliceTestB = []int{}
    fmt.Printf("len of sliceIntA:%d,cap of sliceIntA:%d\n", len(sliceIntA), cap(sliceIntA))
}
len of sliceIntA:0,cap of sliceIntA:50
len of sliceIntA:3,cap of sliceIntA:50
len of sliceIntA:0,cap of sliceIntA:0

It can be seen that the slices emptied by this method , length len by 0, Capacity cap Also for the 0

2. Method 2

Go In language , The initial value of a type is called a zero value , Common Boolean values of type zero false, The zero value of a numeric type is 0, The zero value of the string type is an empty string "", and pointer、slice、map、channel、func and interface The zero value of is nil. So can we use nil To empty the slices ?

func main() {
    array := [...]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
    sliceIntB := array[2:6]
    sliceIntB = nil
}
func main() {
    array := [...]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
    sliceIntB := array[2:6]
    sliceIntB = nil
    fmt.Printf("len of sliceIntB:%d,cap of sliceIntB:%d\n", len(sliceIntB), cap(sliceIntB))
}
len of sliceIntB:0,cap of sliceIntB:0

2.1 Is it the same ?

Look at the output , It seems that the effect of the above method is the same . Is this the case ?

We add the following code :

fmt.Printf("%p\n", sliceIntB) //  Output  0x0
fmt.Println(sliceIntB==nil) //true

Look at the above method

fmt.Printf("%p\n", sliceIntA) //  Output  0xb3fe00fmt.Println(sliceIntA==nil) //false

See? , In terms of the length and capacity of the slice , Both of these are empty slices , Both length and capacity belong to 0. Learn from bloggers go The beginning of language , I was told by my predecessors :

  • To check if the slice is empty , Please always use len(s) == 0 To judge , Instead of using s == nil To judge .
    • As above sliceIntA equally , Although it's an empty slice , But it's not zero .
    • One nil The slice of values has no underlying array , But one nil The length and capacity of the slice of value are 0. But we can't say that a length and capacity are 0 The slice of must be nil;
    • adopt nil After emptying the slice , The slice does not point to the underlying array , If there is no other reference to the underlying array , If you're right , I'm afraid we can only rely on GC Recycled .

2.2 Then compare the differences

In order to see the difference of memory address more intuitively , We get the slice based on the array through the slice expression , And from 0 Start cutting , So you can get the same address .

func main() { array := [...]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} sliceIntA := array[0:6] fmt.Printf("1.array %p\n", &array) fmt.Printf("2.sliceIntA %p\n", sliceIntA) fmt.Println(sliceIntA) sliceIntA = []int{} fmt.Printf("1.array  %p\n", &array) fmt.Printf("2.sliceIntA %p\n", sliceIntA) sliceIntA = nil fmt.Printf("2.sliceIntA %p\n", sliceIntA) fmt.Printf("1.array  %p\n", &array) sliceIntA = append(sliceIntA, 1) fmt.Printf("2.sliceIntA %p\n", sliceIntA)}
1.array 0xc00000c1e02.sliceIntA 0xc00000c1e0[1 2 3 4 5 6]1.array  0xc00000c1e0#  Empty method 1 2.sliceIntA 0x108de00#  Empty method 2 2.sliceIntA 0x01.array  0xc00000c1e0# append2.sliceIntA 0xc0000120d0

It can be seen that :

  • Use method 1. After emptying , The underlying array pointed to by the slice has changed , The original underlying array remains unchanged , Therefore, operate the slice again at this time , It will not affect the original underlying array ;
  • Use nil After emptying , The slice has no underlying array ,append after , It points to the new underlying array , The original underlying array remains unchanged ;

It can be concluded that : The above emptying method , Will cause the underlying array to be replaced .

3. A more elegant way

It seems that the above has met our need to empty the slice , But there will be the following problems :

  • Continue when you need to empty append In case of operation , Will cause the underlying array to be replaced , Open up new space , I'm afraid the original underlying array depends on GC Recycled ;
  • After the slice is empty , Except for the length 0, Capacity also belongs to 0 了 , This is actually not conducive to our follow-up append, Because when the length is about to exceed the capacity , The capacity will be expanded according to the expansion strategy .

There will be a performance problem with both of the above problems , In most cases , We can ignore , however , We still have to ask : Is there a more elegant way to empty the slice ?

func main() { array := [...]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} sliceIntA := array[0:6] fmt.Printf("array %p\n", &array) fmt.Printf("sliceIntA %p\n", sliceIntA) fmt.Println(array) fmt.Println(sliceIntA) fmt.Printf("len of sliceIntA:%d,cap of sliceIntA:%d\n", len(sliceIntA), cap(sliceIntA)) sliceIntA = sliceIntA[0:0] fmt.Printf("array %p\n", &array) fmt.Printf("sliceIntA %p\n", sliceIntA) fmt.Println(sliceIntA) fmt.Println(array) fmt.Printf("len of sliceIntA:%d,cap of sliceIntA:%d\n", len(sliceIntA), cap(sliceIntA))}
array 0xc00000c1e0sliceIntA 0xc00000c1e0[1 2 3 4 5 6 7 8 9 10][1 2 3 4 5 6]len of sliceIntA:6,cap of sliceIntA:10# After emptying array 0xc00000c1e0sliceIntA 0xc00000c1e0[][1 2 3 4 5 6 7 8 9 10]len of sliceIntA:0,cap of sliceIntA:10

By output , We can easily draw the following conclusion :

  • adopt Slice expression empty section , Length only 0, And the capacity remains the same
    • Solved the problem of possible capacity expansion
  • After emptying , The underlying array pointed to by the slice is also unchanged
    • Solved the problem of replacing the underlying array , Open up new space , And possible garbage collection problems

Be careful : The underlying array pointed to by the slice remains unchanged , This leads to both slicing and array operations , Will affect each other .

sliceIntA = append(sliceIntA, 999) // After the slice is empty append value sliceIntA = append(sliceIntA, 888) // After the slice is empty append value fmt.Printf("array %p\n", &array)fmt.Printf("sliceIntA %p\n", sliceIntA)fmt.Println(sliceIntA) //[999 888]fmt.Println(array) //[999 888 3 4 5 6 7 8 9 10]fmt.Printf("len of sliceIntA:%d,cap of sliceIntA:%d\n", len(sliceIntA), cap(sliceIntA))array[0]=777  // Modify the value through the array index fmt.Printf("array %p\n", &array)fmt.Printf("sliceIntA %p\n", sliceIntA)fmt.Println(sliceIntA)  // [777 888]fmt.Println(array) //[777 888 3 4 5 6 7 8 9 10]fmt.Printf("len of sliceIntA:%d,cap of sliceIntA:%d\n", len(sliceIntA), cap(sliceIntA))
array 0xc00000c1e0sliceIntA 0xc00000c1e0[999 888][999 888 3 4 5 6 7 8 9 10]len of sliceIntA:2,cap of sliceIntA:10array 0xc00000c1e0sliceIntA 0xc00000c1e0[777 888][777 888 3 4 5 6 7 8 9 10]len of sliceIntA:2,cap of sliceIntA:10

see , Modifying the slice affects the array , Modifying the array will affect the slice , until Slice length is about to exceed capacity , Replace the underlying array , The two of them will decouple , This conclusion , Please move to another blog post 【Golang】 A few questions to strengthen Slice

4. Conclusion

There are 3 A way to empty slices , But their essence is different , There are no extreme requirements for performance , Can be used , If there are other special needs , Be sure to keep in mind whether the bottom layer array is replaced , Please use with care . Of course , If you just empty the slice , Go ahead append operation , Bloggers recommend slicing expressions [0:0].

------------------- End -------------------

原网站

版权声明
本文为[DDGarfield]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/174/202206231941490527.html