文章目录
需求描述
在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
}
通过上面的方式已经可以输出我们想要的结构了:
但是,多运行几次就会发现,每次运行后的顺序并不一致,因为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: "新加坡",
}
)
按照上面的方法,运行后会发现,新增加的枚举值排在了最后面:
这是因为在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
}
输出结果验证一下,会发现按照我们定义二维切片的顺序输出了:
至此,问题解决。
如果你也需要在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