go orderedmap 有序map

7,808 阅读2分钟

前言

在go中,map中的元素迭代的时候是无序的,使用encoding/json进行序列化的时候默认是按照字典序排序的。

在一些业务场景中,我们需要使用key有序的map。关于有序map,很自然的就能够想到需要额外使用一个链表或者数组来记录key写入的顺序。go的标准库中没有提供,这里推荐一个用起来感觉不错的第三方的有序map:

github.com/iancoleman/orderedmap

go get

go get github.com/iancoleman/orderedmap

代码样例

下面是代码样例,使用orderedmap.New()创建一个有序数组对象,通过Set方法往有序map中添加key和value,可以通过Get方法获取元素的值。key的顺序与元素添加顺序相关,也可以通过SortKeys方法对key进行排序,或者通过Sort方法通过value来对value进行排序。

对有序map进行json序列化的时候,如果未对key或者value进行排序的话,默认会按照元素添加的顺序进行序列化。

package main

import (
   "encoding/json"
   "fmt"
   "sort"

   "github.com/iancoleman/orderedmap"
)

func start() {
   o := orderedmap.New()
   o.Set("name", "tom")
   o.Set("age", "10")
   o.Set("height", "170")
   o.Set("hobby", "ball")
   x, _ := json.Marshal(o)

   var m map[string]interface{}
   _ = json.Unmarshal(x, &m)
   y, _ := json.Marshal(m)

   fmt.Println("ordered map: ", string(x))
   fmt.Println("map: ", string(y))

   //ordered map:  {"name":"tom","age":"10","height":"170","hobby":"ball"}
   //map:  {"age":"10","height":"170","hobby":"ball","name":"tom"} map序列化后默认按照key的字典序排序

   //按照key的字典序升序排序
   o.SortKeys(sort.Strings)
   x, _ = json.Marshal(o)
   fmt.Println("asc sort key: ", string(x))
   //asc sort key:  {"age":"10","height":"170","hobby":"ball","name":"tom"} 这是就是标准库中的map序列化后结果一致

   //按照key的字典序降序排序
   o.SortKeys(func(keys []string) {
      sort.Slice(keys, func(i, j int) bool {
         return keys[i] > keys[j]
      })
   })
   x, _ = json.Marshal(o)
   fmt.Println("desc sort key: ", string(x))
   //desc sort key:  {"name":"tom","hobby":"ball","height":"170","age":"10"}

   //按照value的字典序升序排序
   o.Sort(func(a *orderedmap.Pair, b *orderedmap.Pair) bool {
      return a.Value().(string) < b.Value().(string)
   })
   x, _ = json.Marshal(o)
   fmt.Println("sort value: ", string(x))
   //sort value:  {"age":"10","height":"170","hobby":"ball","name":"tom"}
}

func main() {
   start()
}

源码说明

1. orderedmap数据结构

有序map包含三个元素。其中escapeHTML表示是否转义HTML字符,默认为true表示转义。可以调用SetEscapeHTML(false)方法来设置不转义HTML字符。

type OrderedMap struct {
   keys       []string // 存放所有的key,用来记录元素顺序
   values     map[string]interface{} //存放key和value
   escapeHTML bool //是否转义HTML字符。默认为true表示转义。
}

2. 排序函数

//对key进行排序
// SortKeys Sort the map keys using your sort func
func (o *OrderedMap) SortKeys(sortFunc func(keys []string)) {
   sortFunc(o.keys)
}

//对value进行排序。其中结构体Pair实现了排序接口的Len()、Less()、Swap()这三个方法。可以直接使用标准库中的排序方法
// Sort Sort the map using your sort func
func (o *OrderedMap) Sort(lessFunc func(a *Pair, b *Pair) bool) {
   pairs := make([]*Pair, len(o.keys))
   for i, key := range o.keys {
      pairs[i] = &Pair{key, o.values[key]}
   }

   sort.Sort(ByPair{pairs, lessFunc})

   for i, pair := range pairs {
      o.keys[i] = pair.key
   }
}

3. 序列化函数

// 主要是分别序列化key和value,然后组装带一起
func (o OrderedMap) MarshalJSON() ([]byte, error) {
   var buf bytes.Buffer
   buf.WriteByte('{')
   encoder := json.NewEncoder(&buf)
   encoder.SetEscapeHTML(o.escapeHTML)
   for i, k := range o.keys {
      if i > 0 {
         buf.WriteByte(',')
      }
      // add key
      if err := encoder.Encode(k); err != nil {
         return nil, err
      }
      buf.WriteByte(':')
      // add value
      if err := encoder.Encode(o.values[k]); err != nil {
         return nil, err
      }
   }
   buf.WriteByte('}')
   return buf.Bytes(), nil
}

4. 反序列化函数

func (o *OrderedMap) UnmarshalJSON(b []byte) error {
   if o.values == nil {
      o.values = map[string]interface{}{}
   }
   // 1. 先直接对整体进行反序列化
   err := json.Unmarshal(b, &o.values)
   if err != nil {
      return err
   }
   dec := json.NewDecoder(bytes.NewReader(b))
   if _, err = dec.Token(); err != nil { // skip '{'
      return err
   }
   o.keys = make([]string, 0, len(o.values))
   // 2. 解析出源数据中key,主要分为了map和slice两种情况分别解析
   return decodeOrderedMap(dec, o)
}

5. 参考自:

最后

大家好,如果觉得有用,可以悄悄给我点个赞[😄],也可以关注微信公众号「那只猴子」。