go语言简单快速的按顺序遍历kv结构(map)

发布于:2025-02-14 ⋅ 阅读:(14) ⋅ 点赞:(0)

需求描述

在go语言中,如果需要对map遍历,每次输出的顺序是不固定的,可以考虑存储为二维切片或结构体。
假设现在需要在页面的下拉菜单中展示一些基础的选项,需要服务端输出如下的结构:

{
    "code": 200000,
    "message": "请求成功",
    "data": [
        {
            "id": 1,
            "name": "中国"
        },
        {
            "id": 2,
            "name": "美国"
        },
        {
            "id": 3,
            "name": "韩国"
        },
        {
            "id": 4,
            "name": "新加坡"
        }
    ]
}

用map实现

首先,常规思维,考虑用map存储,实现方式如下:

先定义好常量值和映射关系,代码路径:gozero/internal/constants/commonConstants.go

package constants

const (
	// 国家常量值
	CountryChina     = 1
	CountryAmerica   = 2
	CountryKorea     = 3
	CountrySingapore = 4
)

var (
	//国家常量映射关系
	CountryMap = map[int]string{
		CountryChina:     "中国",
		CountryAmerica:   "美国",
		CountryKorea:     "韩国",
		CountrySingapore: "新加坡",
	}
)

然后在逻辑层使用上面定义的map:

代码路径:gozero/internal/logic/common/simpleselectlogic.go

func (l *SimpleSelectLogic) SimpleSelect(req *types.SimpleSelectRequest) (resp *utils.Response, err error) {
	paramType := req.Type
	var data interface{}
	switch paramType {
	case "country":
		data = mapToSlice(constants.CountryMap)
	case "others":
		data = nil
	}
	//成功返回
	return utils.SuccessResponse(data), nil
}

// 定义结构体
type Item struct {
	ID   int    `json:"id"`
	Name string `json:"name"`
}

func mapToSlice(m map[int]string) []Item {
	// 提取所有的key
	keys := make([]int, 0, len(m))
	for k := range m {
		keys = append(keys, k)
	}

	// 将key和value组合成结构体切片
	sortedSlice := make([]Item, 0, len(m))
	for _, k := range keys {
		sortedSlice = append(sortedSlice, Item{ID: k, Name: m[k]})
	}

	return sortedSlice
}

通过上面的方式已经可以输出我们想要的结构了:

image-20250212181659720

但是,多运行几次就会发现,每次运行后的顺序并不一致,因为go中的map的遍历是不保证顺序的。

那么,直接在遍历的时候对key进行排序再按照key的顺序输出是否可行?尝试如下:

按照map的key排序

mapToSlice方法中加上对key的排序:

func mapToSortedSlice(m map[int]string) []Item {
	// 提取所有的key
	keys := make([]int, 0, len(m))
	for k := range m {
		keys = append(keys, k)
	}

	// 对key进行排序
	sort.Ints(keys)

	// 根据排序后的key顺序,将key和value组合成结构体切片
	sortedSlice := make([]Item, 0, len(m))
	for _, k := range keys {
		sortedSlice = append(sortedSlice, Item{ID: k, Name: m[k]})
	}

	return sortedSlice
}

这样修改后,可以保证输出的都是按照key由小到大排序的结果。

但是,假如现在有一种情况,产品要求在已有的下拉选项中插入一个新的选项值,并且顺序在中间。比如加入一个“英国”的选项,在“美国”后面,我们修改常量枚举值如下:

package constants

const (
	// 国家常量值
	CountryChina     = 1
	CountryAmerica   = 2
	CountryEngland   = 5 //增加的"英国"的枚举值
	CountryKorea     = 3
	CountrySingapore = 4
)

var (
	//国家常量映射关系
	CountryMap = map[int]string{
		CountryChina:     "中国",
		CountryAmerica:   "美国",
		CountryEngland:   "英国", //增加的"英国"排在"美国"下面
		CountryKorea:     "韩国",
		CountrySingapore: "新加坡",
	}
)

按照上面的方法,运行后会发现,新增加的枚举值排在了最后面:

image-20250212182455332

这是因为在mapToSortedSlice方法中根据map的key排序后,后来新增的key是5,所以会排在最后面。由于已有的key(1-4)不能修改,那么只能考虑再定义一个排序的切片来自定义需要排序的数据:

CountrySort = []int{
		CountryChina,
		CountryAmerica,
		CountryEngland,
		CountryKorea,
		CountrySingapore,
	}

然后在 mapToSortedSlice 方法中对上面的CountrySort进行排序后遍历。这种方法可以实现需求,但是会比较麻烦。因此,我决定改为使用二维切片来存储数据。

用二维切片实现

package constants

const (
	// 国家常量值--改为存储字符串
	CountryStrChina     = "1"
	CountryStrAmerica   = "2"
	CountryStrEngland   = "5"
	CountryStrKorea     = "3"
	CountryStrSingapore = "4"
)

var (
	//国家常量映射关系--切片存储
	CountrySlice = [][]string{
		{CountryStrChina, "中国"},
		{CountryStrAmerica, "美国"},
		{CountryStrEngland, "英国"},
		{CountryStrKorea, "韩国"},
		{CountryStrSingapore, "新加坡"},
	}
)

然后,处理切片的函数如下:

func formatSlice(m [][]string) []Item {
	// 根据排序后的key顺序,将key和value组合成结构体切片
	sortedSlice := make([]Item, 0, len(m))
	for _, v := range m {
		id, _ := strconv.Atoi(v[0])
		sortedSlice = append(sortedSlice, Item{ID: id, Name: v[1]})
	}
	return sortedSlice
}

调用一下:

func (l *SimpleSelectLogic) SimpleSelect(req *types.SimpleSelectRequest) (resp *utils.Response, err error) {
	paramType := req.Type
	var data interface{}
	switch paramType {
	case "country":
		//data = mapToSortedSlice(constants.CountryMap)
		data = formatSlice(constants.CountrySlice)
	case "others":
		data = nil
	}
	//成功返回
	return utils.SuccessResponse(data), nil
}

输出结果验证一下,会发现按照我们定义二维切片的顺序输出了:

image-20250212183415855

至此,问题解决。

如果你也需要在go中按顺序遍历kv结构,并且想偷个懒,那么不妨试试这样的方式。如果还想再偷懒,那么可以连常量都省去了,直接这么写:

//国家常量映射关系--切片存储-省去常量
CountrySliceSimple = [][]string{
  {"1", "中国"},
  {"2", "美国"},
  {"5", "英国"},
  {"3", "韩国"},
  {"4", "新加坡"},
}

用结构体实现

当然,也可以直接在定义的时候存储为结构体:

package constants

const (
	// 国家常量值
	CountryChina     = 1
	CountryAmerica   = 2
	CountryEngland   = 5 //增加的"英国"的枚举值
	CountryKorea     = 3
	CountrySingapore = 4

	//国家常量映射关系--结构体存储
	CountryStruct = []struct {
		Id   int    `json:"id"`
		Name string `json:"name"`
	}{
		{CountryChina, "中国"},
		{CountryAmerica, "美国"},
		{CountryEngland, "英国"},
		{999, "法国"},
		{CountryKorea, "韩国"},
		{CountrySingapore, "新加坡"},
	}
)

使用的时候也不需要转换了:

func (l *SimpleSelectLogic) SimpleSelect(req *types.SimpleSelectRequest) (resp *utils.Response, err error) {
	paramType := req.Type
	var data interface{}
	switch paramType {
	case "country":
		//data = mapToSortedSlice(constants.CountryMap) //使用map
		//data = formatSlice(constants.CountrySlice) //使用切片
		data = constants.CountryStruct //使用结构体
	case "others":
		data = nil
	}
	//成功返回
	return utils.SuccessResponse(data), nil
}

源代码:https://gitee.com/rxbook/go-demo-2025/tree/master/gozero