首页 > 解决方案 > 如何有效地展平地图?

问题描述

我的应用程序需要获取大量嵌套实例map[string]interface{}并将它们转换为扁平地图。

例如:

{
                "foo": {
                    "jim":"bean"
                },
                "fee": "bar",
                "n1": {
                    "alist": [
                        "a",
                        "b",
                        "c",
                        {
                            "d": "other",
                            "e": "another"
                        }
                    ]
                },
                "number": 1.4567,
                "bool":   true
}

后:

json.Unmarshal([]byte(input), &out)
result = Flatten(m.(map[string]interface{}))

变成:

{
                "foo.jim":      "bean",
                "fee":          "bar",
                "n1.alist.0":   "a",
                "n1.alist.1":   "b",
                "n1.alist.2":   "c",
                "n1.alist.3.d": "other",
                "n1.alist.3.e": "another",
                "number":       1.4567,
                "bool":         true,
}

目前我正在使用以下代码:

func Flatten(m map[string]interface{}) map[string]interface{} {
    o := map[string]interface{}{}
    for k, v := range m {
        switch child := v.(type) {
        case map[string]interface{}:
            nm := Flatten(child)
            for nk, nv := range nm {
                o[k+"."+nk] = nv
            }
        case []interface{}:
            for i := 0; i < len(child); i++ {
                o[k+"."+strconv.Itoa(i)] = child[i]
            }
        default:
            o[k] = v
        }
    }
    return o
}

此代码的内存效率非常低,当展平约 800 个地图时,使用约 8 GiB 内存。我尝试过传递指针而不是副本,但这不会改变内存使用情况。我是否必须进行某种类型的手动内存管理?Go 应该垃圾收集任何未使用的内存,但可能是我没有以某种方式释放它。

标签: gomemory-leaksgarbage-collectionpass-by-referenceflatten

解决方案


正如 Andy Schweig 在评论中所建议的那样,在您遍历数据时会更新的地图会显着减少内存使用量(注意:尚未测试)。就像是:

func Flatten2(prefix string, src map[string]interface{}, dest map[string]interface{}) {
    if len(prefix) > 0 {
        prefix += "."
    }
    for k, v := range src {
        switch child := v.(type) {
        case map[string]interface{}:
            Flatten2(prefix+k, child, dest)
        case []interface{}:
            for i := 0; i < len(child); i++ {
                dest[prefix+k+"."+strconv.Itoa(i)] = child[i]
            }
        default:
            dest[prefix+k] = v
        }
    }
}

在操场上试试这个。

我是否必须进行某种类型的手动内存管理?

Go 运行时旨在为您处理内存管理(请参阅关于垃圾收集的常见问题解答文章)。虽然垃圾收集器非常好,但它只能释放未使用的内存,并且您的原始例程在执行过程中创建了很多映射,直到该级别的映射被处理后才能释放。

您可以通过环境变量或使用调试包设置一些标志,但很少需要这些标志(它们可能会产生意想不到的后果)。

关于在哪里进一步优化此功能有什么建议吗?

如果不访问您的数据集以及了解您的需求和有关如何测量内存使用情况的信息,很难发表评论(这可能比看起来更复杂)。分析您的应用程序可能会为您提供一些改进方面的建议。一种可能的改进是使用预先分配的切片而不是映射dest(但我不知道您对结果做了什么,所以这可能不合适)。


推荐阅读