go语言基础-map技巧


原文链接: go语言基础-map技巧

Golang map 的底层实现 - 简书

当一个map变量被创建后,你可以指定map的容量,但是不可以在map上使用cap()方法
代码示例:
package main
func main() {

m := make(map[string]int, 99)
cap(m) //error

}

command-line-arguments

./hello.go:5: invalid argument m (type map[string]int) for cap

先来看一下go的内置函数cap与map:
cap: 返回的是数组切片分配的空间大小, 根本不能用于map
make: 用于slice,map,和channel的初始化
要获取map的容量,可以用len函数。

关于map的cap问题

是不是只有连续内存空间的才有cap, map不是连续的底层空间, 所以map没有cap一说

  1. map 必须用make进行初始化后才能使用,否则报错.
    var temp = make(map[string]struct{})
    或 temp := map[string]struct{}{}

  2. map 空判断

    if _, ok := map[key]; ok {
    //存在
    }
    

    Golang workaround for cannot assign to struct field in map ?

    1. golang 不能直接修改map[]struct 的值,如果想修改 必须用指针形式


    Yesterday, I was working one of the Kompose issue, and I was working on map of string to struct, while iterating over a map I wanted to change elements of struct, so I tried similar to this,
    
    package main
    
    import "fmt"
    
    type Animal struct {
    	count int
    }
    
    func main() {
    	m := map[string]Animal{"cat": Animal{2}, "dog": Animal{3}, "mouse": Animal{5}}
        fmt.Println(m)
    	m["dog"].count = 4
    	
    	fmt.Println(m)
    
    }
    so I got this error,
    
    tmp/sandbox728133053/main.go:12: cannot assign to struct field m["dog"].count in map
    After googling for some time, I found this solution and I tried & it worked as below:
    
    package main
    
    import "fmt"
    
    type Animal struct {
    	count int
    }
    
    func main() {
    	m := map[string]Animal{"cat": Animal{2}, "dog": Animal{3}, "mouse": Animal{5}}
    
    	fmt.Println(m)
    
    	var x = m["dog"]
    	x.count = 4
    	m["dog"] = x
    
    	fmt.Println(m)
    
    
    }
    I found one more way to do this by storing pointers as shown below:
    
    package main
    
    import "fmt"
    
    type Animal struct {
    	count int
    }
    
    func main() {
    	m := map[string]*Animal{"cat": &Animal{2}, "dog": &Animal{3}, "mouse": &Animal{5}}
    	fmt.Printf("%#v\n",m["dog"])
    	
    	m["dog"].count = 4
    
    	fmt.Printf("%#v", m["dog"])
    
    }
    
  3. 利用空struct{}对slice去重

    func main() {
    	s := []string{"hello", "world", "hello", "golang", "hello", "ruby", "php", "java"}
    	fmt.Println(removeDuplicateElement(s))
    }
    func removeDuplicateElement(addrs []string) []string {
    	result := make([]string, 0, len(addrs))
    	temp := map[string]struct{}{}
    	for _, item := range addrs {
    		if _, ok := temp[item]; !ok {
    			temp[item] = struct{}{}
    			result = append(result, item)
    		}
    	}
    	return result
    }
    
    func (s *Schema) hasDuplicateValue(values []string) bool {
    	l := len(values)
    
    	tmp := map[string]bool{}
    	for _, v := range values {
    		tmp[v] = true
    	}
    
    	return l != len(tmp)
    }
    

sync.Map

// sync.Map的实现有几个优化点,这里先列出来,我们后面慢慢分析。
//     空间换时间。 通过冗余的两个数据结构(read、dirty),实现加锁对性能的影响。
//     使用只读数据(read),避免读写冲突。
//     动态调整,miss次数多了之后,将dirty数据提升为read。
//     double-checking。
//     延迟删除。 删除一个键值只是打标记,只有在提升dirty的时候才清理删除的数据。
//     优先从read读取、更新、删除,因为对read的读取不需要锁。
const N = 60000

func main() {
	var m sync.Map
	var g errgroup.Group
	for i := 0; i < N; i++ {
		g.Go(func() error {
			m.Store(rand.Int(), rand.Int())
			return nil
		})
	}
	g.Wait()

	m.Delete(rand.Int())
	m.Load(rand.Int())
	m.Store("abc", "1")
	res, err := m.Load("abc")
	fmt.Println(res, err)

	var iter = func(k, v interface{}) bool {
		fmt.Println(k, v)
		return true
	}
	m.Range(iter)
}

错误信息cannot assign to struct field m["foo"].x in map

注意: Golang中是无法修改map中的成员变量,修改成指针就行了

在开始代码设计的时候想要将原struct中的成员变量进行修改或者替换。

代码示例如下

package main

import "fmt"

var m = map[string]struct{ x, y int } {

"foo": {2, 3}

}

func main() {

m["foo"].x = 4
fmt.Printf("result is : %+v", m)

}
本以为这个会将 m[“foo”] 中的 x 替换成 4, 从而打印出来的效果是

result is : map[foo:{x:4 y:3}]

然而,并不是的,这段代码在保存后编译时提示

cannot assign to struct field m["foo"].x in map

这就尴尬了,无法在已经存在的key的节点中修改值,这是为什么?

m中已经存在”foo”这个节点了啊,

然后就去google搜了下,然后看到在github上有人提到这个问题, 问题地址 issue-3117

ianlancetaylor 回答给出了一个比较能理解的解释。

简单来说就是map不是一个并发安全的结构,所以,并不能修改他在结构体中的值。

这如果目前的形式不能修改的话,就面临两种选择,

1.修改原来的设计;

2.想办法让map中的成员变量可以修改,

因为懒得该这个结构体,就选择了方法2,

但是不支持这种方式传递值,应该如何进行修改现在已经存在在struct中的map的成员变量呢?

热心的网友们倒是提供了一种方式,示例如下:

package main

import "fmt"

var m = map[string]struct{ x, y int } {

"foo": {2, 3}

}

func main() {

tmp := m["foo"]
tmp.x = 4
m["foo"] = tmp
fmt.Printf("result is : %+v", m)

}
果然和预期结果一致,不过,总是觉得有点怪怪的,

既然是使用了类似临时空间的方式,那我们用地址引用传值不也是一样的么...

于是,我们就使用了另外一种方式来处理这个东西,

示例如下:

package main

import "fmt"

var m = map[string]*struct{ x, y int } {

"foo": &{2, 3}

}

func main() {
m["foo"].x = 4
fmt.Println("result is : %+v \n", m)
fmt.Println("m's node is : %+v \n", *m["foo"])
}
最后的展示结果为:

result is : map[foo:0xc42000cff0]
m's node is : {4, 3}
多亏了经过这么一番折腾,我知道了,下次要是想在struct中的map里面改变成员变量,就直接用地址吧。

`