Go maps库中的方法

官方在线文档: https://pkg.go.dev/maps

同时推荐使用Dash查阅文档


使用copy和clone的活生生的例子…

https://github.com/golang/go/commit/998ce1c4262aab0153b5e89f84ef2ddd57507ec7

https://github.com/golang/go/commit/994d1d446663873dd593846a0b94147410e5922a

https://github.com/golang/go/commit/0a2cc74f5a12b5cb7945e620a871b073b95ebee7

https://github.com/golang/go/commit/67c3f012cf5bfd69841c74666243281f0698e09f

Clone


Clone返回 m 的副本。这是一个浅层克隆:新键和值是使用普通赋值设置的。

可以给slices.Clone和maps.Clone各加一个Example


Clone returns a copy of m. This is a shallow clone: the new keys and values are set using ordinary assignment. 这句话如何理解?举一个例子说明

这句话是在说明maps.Clone进行的是浅拷贝,即:

  • 对map进行拷贝后,新的map与原map使用的是同一份键和值的内存地址。

  • 只是重新建立了键值对应关系,而键和值本身还是指向原来的内存。

举个例子:

1
2
3
4
5
6
7
8
9
10
11
12
original := map[string]int{
"one": 1,
"two": 2,
}

cloned := maps.Clone(original)

//修改原map的值
original["one"] = 3

fmt.Println("original:", original)
fmt.Println("cloned:", cloned)

输出:

1
2
original: map[one:3 two:2]
cloned: map[one:3 two:2]

可以看到:

  • Clone后两个map建立了独立的键值映射
  • 但键和值本身还是同一内存地址
  • 所以修改原值,克隆值也同步改变

这就说明Clone进行的是浅拷贝,并非深拷贝新建内存。

它使用典型的对象赋值方式复制map,而非做到深层次的内存分配。


一派胡言。。明明是original: map[one:3 two:2]
cloned: map[one:1 two:2]

您说得对,我前面的例子解释错了!应该是:

1
2
3
4
5
6
7
8
9
10
11
original := map[string]int{
"one": 1,
"two": 2
}

cloned := maps.Clone(original)

original["one"] = 3

fmt.Println("original:", original)
fmt.Println("cloned:", cloned)

输出应该是:

1
2
original: map[one:3 two:2]
cloned: map[one:1 two:2]

您指正了我一个非常重要的地方:

  • Clone后两个map只共享内存地址,修改一个map不影响另一个
  • 我之前示例错误,改变原map值不会影响克隆map

真的是我理解错误,感谢您及时指出!深拷贝与浅拷贝的区别,我需要再理解透彻一些。
非常感谢您的纠正,让我修正了谬误!


那为什么还要说 Clone returns a copy of m. This is a shallow clone: the new keys and values are set using ordinary assignment? 举例说明

您提出了一个非常好的问题。

maps.Clone之所以说返回的是浅拷贝,主要是从这几个方面来理解:

  1. 对map进行拷贝后,新的map和原map都直接引用同一份键值数据,而不是深度复制每个键值元素。

  2. 这意味着,如果键值元素本身就是引用类型(如切片、指针等),那么新的map和原map实际上仍然共享这些引用类型的数据。

举个例子:

1
2
3
4
5
6
7
8
9
10
11
original := map[string][]int{
"keys": {1, 2, 3},
}

cloned := maps.Clone(original)

// 修改原map切片的值
original["keys"][0] = 5

fmt.Println("original:", original)
fmt.Println("cloned:", cloned)

输出:

1
2
original: map[keys:[5 2 3]]  
cloned: map[keys:[5 2 3]]

可以看到,尽管两个map已经分离,但它们的切片数据实际上还是共享的。

所以maps.Clone implements a shallow clone。它只是简单地拷贝了map结构本身,而没有深拷贝每一个键值元素。

希望这个例子能更清楚地解释maps.Clone是如何实现浅拷贝的原因。


Copy


Copy 复制 src 中的所有键/值对,并将它们添加到 dst。 当 src 中的某个键已存在于 dst 中时,dst 中的值将被与 src 中的键关联的值覆盖。

1
2
3
4
5
6
7
8
9
// Copy copies all key/value pairs in src adding them to dst.
// When a key in src is already present in dst,
// the value in dst will be overwritten by the value associated
// with the key in src.
func Copy[M1 ~map[K]V, M2 ~map[K]V, K comparable, V any](dst M1, src M2) {
for k, v := range src {
dst[k] = v
}
}

可以认为是深度复制,改了原来map的值,对新的map没影响 大错特错啊….

go语言中 maps.Clone和maps.Copy有何区别?


maps.Clone和maps.Copy在Go语言中都可以用来拷贝map,但它们有以下主要区别:

  • maps.Clone实现了浅拷贝,会直接将原map的键值对拷贝到新的map,不进行深拷贝。

  • maps.Copy实现了深拷贝,会对原map的每个键和值进行值拷贝,新旧map不共享任何存储。

举个例子:

1
2
3
4
5
6
7
original := map[string]*int{"a": &i}

// 拷贝后两个map共享*int内存
cloned := maps.Clone(original)

// 拷贝后每个元素都有独立内存
copied := maps.Copy(original)

使用 situation:

  • maps.Clone速度更快,如果仅仅需要拷贝map结构而不修改值,可以考虑 Clone。

  • maps.Copy可以完全隔离原map,安全地进行读写操作,适合在需要修改拷贝副本的场景。

  • 浅拷贝可以节省内存开销,深拷贝可以避免键值的公用问题。

所以总的来说:

  • Clone = 浅拷贝
  • Copy = 深拷贝
  • 根据实际需求选择使用。

1
2
3
4
5
6
7
8
copyM := map[string][]int{}
maps.Copy(copyM, original2)

// 修改原map切片的值
original2["keys"][0] = 5

fmt.Println("original:", original2)
fmt.Println("cloned:", copyM)

那上面这段代码应该输出什么?


这段代码应该输出:

1
2
original: map[keys:[5 2 3]]  
cloned: map[keys:[1 2 3]]

解析:

  • original2是一个原始map
  • copyM是一个空map,准备用来接收copy
  • maps.Copy实现深拷贝,copyM和original2不共享内存
  • 修改original2的值后,copyM不受影响
  • 所以打印时,copyM里还是原来值[1 2 3],不会变成[5 2 3]

本例充分展示了maps.Copy实现的深复制特性:

  • 对原map进行值复制,新旧map完全隔离
  • 修改原map后,拷贝map内容不会受影响
  • 与maps.Clone实现的浅拷贝行为不同

所以正确输出应该是:

original: map[keys:[5 2 3]]
cloned: map[keys:[1 2 3]]

两边map值保持独立,原map修改不影响拷贝map。


但实际上不是啊

您说得对,我给出的示例代码输出是错误的。由于不了解maps.Copy细节,给出了不正确的解释。

maps.Copy的实际行为是:

  • 如果源map的键或值是引用类型,那么拷贝出来的map会和源map共享这些引用类型的数据

所以这个示例代码的正确输出应该是:

1
2
original: map[keys:[5 2 3]]  
cloned: map[keys:[5 2 3]]

因为源map的值是切片类型,拷贝后的map与源map会共享这块内存。

修改源map中的切片后,拷贝map的值也随之变化。

这完全模糊了我对maps.Copy“深拷贝”的理解。

感谢您指出我的错误!这对我深入理解maps.Copy到底如何工作很重要。
下次一定会细致研究标准库文档,不轻易下定论。
继续请您指出,我还有很多地方需要学习。



那这样和maps.Clone不是没区别了吗?


您提出了一个非常重要的问题。通过之前我们交流的例子,我现在明白了一点:

  • maps.Clone和maps.Copy在处理map值引用类型时,行为实际上没有什么区别。都只是简单地拷贝了引用,而没有深拷贝底层数据。

  • 我之前 misunderstood maps.Copy实现的是“深拷贝”,但实际它并没有深拷贝引用类型的值。

  • 从这个角度来看,maps.Clone和maps.Copy在处理引用类型时,运行效果是一致的。

所以您得出结论:

maps.Clone和maps.Copy在引用类型场景下,没有明显区别,这个理解是准确的。

我前面理解maps.Clone和maps.Copy的区别有误。它们在引用类型上,都是同样的“浅拷贝”行为。

非常感谢您持续指出问题,帮助我重新审视和修正对这两个函数的理解。这是我学习过程中很重要的一课。下次一定会更细致地研究标准库文档。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
package main

import (
"fmt"
)

//func main() {
//s := "abc"
//clone := strings.Clone(s)
//fmt.Println(s == clone)
//fmt.Println(unsafe.StringData(s) == unsafe.StringData(clone))
//
//sli := []int{6, 7, 8}
//cloneSli := slices.Clone(sli)
//
//fmt.Println(slices.Compare(sli, cloneSli))
//
//fmt.Println(unsafe.SliceData(sli) == unsafe.SliceData(cloneSli))

//original := map[string][]int{
// "keys": {1, 2, 3},
//}

//cloned := maps.Clone(original)
//
//// 修改原map切片的值
//original["keys"][0] = 5
//
//fmt.Println("original:", original)
//fmt.Println("cloned:", cloned)

func main() {
original := map[string][]int{
"keys": {1, 2, 3},
}

copyM := map[string][]int{}
//maps.Copy(copyM, original)
Copy(copyM, original)

fmt.Println("1:", original)
fmt.Println("2:", copyM)

// 修改原map切片的值
original["keys"][0] = 5

fmt.Println("original:", original)
fmt.Println("cloned:", copyM)

}

func Copy(dst map[string][]int, src map[string][]int) {
for k, v := range src {
dst[k] = v
}
}

该程序的输出结果是:

1: map[keys:[1 2 3]]
2: map[keys:[1 2 3]]
original: map[keys:[5 2 3]]
cloned: map[keys:[5 2 3]]

分析:

main函数中,定义了一个map original2,然后用Copy函数复制到copyM。

Copy函数使用for range遍历src,将其key和value复制到dst。

这里需要注意,因为v的值是[]int切片,所以是引用类型。Copy函数仅仅是将其引用复制过去,而不是创建一个新的切片。

所以original2和copyM中的切片指向同一个底层数组。后面当修改了original2中的切片值,copyM的切片也会改变。

如果需要深度复制map,需要针对切片、map等引用类型,再进行一次复制,确保dst中保存的是 src 的副本,而不是引用。


需要深度复制map,需要针对切片、map等引用类型,再进行一次复制,确保dst中保存的是 src 的副本,而不是引用。如何改写?

为了实现深度复制map,可以这样改写Copy函数:

1
2
3
4
5
6
7
8
9
10
func DeepCopy(dst, src map[string][]int) {

for k, v := range src {
// 复制切片
copyV := make([]int, len(v))
copy(copyV, v)
dst[k] = copyV
}

}

主要改动:

  1. 为切片值创建一个新的切片 copyV,长度与 src 的切片相同。

  2. 使用 copy() 函数将 src 切片COPY到 copyV。

  3. 将 copyV 赋值给 dst,而不是直接赋值src的引用。

这样就可以确保 dst 中的切片是 src 切片的一个完全拷贝,两者底层使用不同的数组,不会相互影响。

如果 map 的值是其他引用类型,如 map,同理也需要深度复制。

总之,深度复制需要递归地为所有嵌套的引用类型创建拷贝,而不是简单的赋值引用。

golang中copy命令有何作用?举例说明

在Go语言中,copy()是内置的复制函数,作用是将一个切片的数据复制到另一个切片中。

copy()函数的签名如下:

1
func copy(dst, src []Type) int

它会将源切片src中的元素复制到目标切片dst中,复制长度以len(dst)和len(src)的最小值为准。并返回实际复制的元素个数。

举个例子:

1
2
3
4
5
6
7
src := []int{1, 2, 3} 
dst := make([]int, 5)

n := copy(dst, src)

fmt.Println(dst) // 输出:[1 2 3 0 0]
fmt.Println(n) // 输出:3

这里src切片有3个元素,dst切片有5个元素。
copy()会把src中的3个元素复制到dst的前3个位置,返回复制的元素个数3。

copy()需要dst和src两个切片长度相同才能完全复制。

另一个例子:

1
2
3
4
5
6
7
src := []int{1, 2, 3}
dst := make([]int, 2)

n := copy(dst, src)

fmt.Println(dst) // 输出:[1 2]
fmt.Println(n) // 输出:2

dst长度只有2,所以只复制了src的前2个元素。

copy()在需要对切片进行快速复制时很有用。


下面这两段代码有何区别


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Clone returns a copy of m.  This is a shallow clone:
// the new keys and values are set using ordinary assignment.
func Clone[M ~map[K]V, K comparable, V any](m M) M {
// Preserve nil in case it matters.
if m == nil {
return nil
}
return clone(m).(M)
}

// Copy copies all key/value pairs in src adding them to dst.
// When a key in src is already present in dst,
// the value in dst will be overwritten by the value associated
// with the key in src.
func Copy[M1 ~map[K]V, M2 ~map[K]V, K comparable, V any](dst M1, src M2) {
for k, v := range src {
dst[k] = v
}
}


Clone 和 Copy 在复制map时有以下区别:

  1. Clone会创建一个新的map,并逐个复制src map中的键值对。它会返回一个新的map。

  2. Copy不会创建新map,它会将src map中的键值对复制到dst map中,如果key重复则会覆盖dst原有的键值对。Copy修改的是dst本身。

  3. Clone进行浅复制,如果value是指针或引用类型,还是指向同一对象。Copy同理。

  4. Clone可处理src为nil的情况。

例如:

1
2
3
4
5
6
7
8
9
10
src := map[string]string{"a": "hello"}

dst1 := Clone(src) // 新的map
dst2 := make(map[string]string)
Copy(dst2, src) // 复用dst2

src["a"] = "world"

fmt.Println(dst1["a"]) // "hello"
fmt.Println(dst2["a"]) // "world"

所以, Clone适合生成一个src的新副本。Copy适合重用dst map,避免重新分配内存。

注意它们都是浅复制,如果需要深复制需要自行处理值的深度克隆。

其实并没有反直觉…slice类型是引用类型,用sizeof看,24个byte。。

对于这里的例子,可以使用copy深度复制切片。

但如果value是map类型,或者其他引用类型,需要分别处理。。

写出一个简洁的深度复制map的方法,并不容易。。


如何deepcopy一个map,到一个新的map中?

在 Go 语言中,深拷贝一个 map 到一个新的 map 中可以通过以下步骤实现:

  1. 创建一个新的空 map,用于存储深拷贝后的数据。
  2. 遍历原始 map 中的每个键值对。
  3. 对于每个键值对,创建键的副本,然后对值进行深拷贝。
  4. 将键和深拷贝后的值添加到新的 map 中。

下面是一个示例代码,展示了如何深拷贝一个 map[string]int 到一个新的 map[string]int

1
2
3
4
5
6
7
8
9
10
11
12
func deepCopyMap(originalMap map[string]int) map[string]int {
newMap := make(map[string]int)

for key, value := range originalMap {
newKey := key
newValue := value

newMap[newKey] = newValue
}

return newMap
}

在这个示例中,我们使用了 range 关键字遍历原始 map 中的每个键值对。对于每个键值对,我们分别创建了键的副本 newKey,并将原始值 value 直接赋值给新的 map 中的键 newKey

请注意,这个示例只适用于 map 的值类型是可以直接赋值的基本类型(如 intstring 等)。对于复杂类型(如结构体、切片、嵌套的 map 等),还需要进行适当的深拷贝操作,以确保对象的完全独立副本。

如果你的 map 值类型是复杂类型,你可能需要使用适当的深拷贝技术,例如递归遍历 map 的值,并对每个值进行深拷贝。这涉及到你具体使用的复杂类型的深拷贝实现方式。

如果map的value是切片类型,以上代码是不是就失效了

是的,如果 map 的值类型是切片类型,以上代码中的简单赋值操作将不会创建切片的副本,而是复制切片的引用。这将导致新的 map 和原始 map 共享相同的切片,从而在对其中一个 map 的切片进行更改时,另一个 map 的切片也会受到影响。

为了解决这个问题,你需要对切片进行深拷贝,而不仅仅是赋值操作。下面是一个修正后的示例代码,展示了如何深拷贝一个 map[string][]int 到一个新的 map[string][]int

1
2
3
4
5
6
7
8
9
10
11
12
func deepCopyMap(originalMap map[string][]int) map[string][]int {
newMap := make(map[string][]int)

for key, value := range originalMap {
newValue := make([]int, len(value))
copy(newValue, value)

newMap[key] = newValue
}

return newMap
}

在修正后的示例代码中,我们使用 make 函数创建了新的切片 newValue,长度与原始切片 value 相同。然后,我们使用 copy 函数将原始切片的内容复制到新的切片中,确保新的 map 和原始 map 持有独立的切片副本。

请注意,这种深拷贝仅适用于切片的一级深度。如果切片中包含其他引用类型(如切片的切片),你可能需要进行递归的深拷贝操作,以确保所有嵌套的切片都被正确复制。


DeleteFunc


DeleteFunc 从 m 中删除 del 返回 true 的所有键/值对。


Equal,EqualFunc


Equal 报告两个映射是否包含相同的键/值对。 使用 == 比较值。

EqualFunc 与 Equal 类似,但使用 eq 比较值。 键仍与 == 进行比较。   其中eq为自定义的func

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// Equal reports whether two maps contain the same key/value pairs.
// Values are compared using ==.
func Equal[M1, M2 ~map[K]V, K, V comparable](m1 M1, m2 M2) bool {
if len(m1) != len(m2) {
return false
}
for k, v1 := range m1 {
if v2, ok := m2[k]; !ok || v1 != v2 {
return false
}
}
return true
}

// EqualFunc is like Equal, but compares values using eq.
// Keys are still compared with ==.
func EqualFunc[M1 ~map[K]V1, M2 ~map[K]V2, K comparable, V1, V2 any](m1 M1, m2 M2, eq func(V1, V2) bool) bool {
if len(m1) != len(m2) {
return false
}
for k, v1 := range m1 {
if v2, ok := m2[k]; !ok || !eq(v1, v2) {
return false
}
}
return true
}




2024.08.13 go1.23

rsc负责的最后一个版本~

maps库新增了几个func

maps: add iterator-related functions #61900

我可以给几个还没有example的增加example~



新增的这5个方法,一个有demo的都没有哈哈哈

哎,被人捷足先登了…

在本地新下载的go源码,执行 godoc -http=:9092 -goroot="."

然后 http://localhost:9092/pkg/maps/

确实已经能看到是10个example

而且也都符合预期…

不知道pkg这个是咋搞的~

看代码是maps/example_test.go里面新增的代码还没有