有没有更好的方法来解析这个地图?
Is there a better way to parse this Map?
Go 相当新,基本上在我正在编写的实际代码中,我打算从一个包含环境变量的文件中读取,即 API_KEY=XYZ
。意味着我可以让它们不受版本控制。下面的解决方案 'works' 但我觉得可能有更好的方法。
最终目标是能够像这样访问文件中的元素
m["API_KEY"]
将打印 XYZ
。这甚至可能已经存在,我正在重新发明轮子,我看到 Go 有环境变量,但它似乎并不是我特别想要的。
非常感谢您的帮助。
代码:
package main
import (
"fmt"
"strings"
)
var m = make(map[string]string)
func main() {
text := `Var1=Value1
Var2=Value2
Var3=Value3`
arr := strings.Split(text, "\n")
for _, value := range arr {
tmp := strings.Split(value, "=")
m[strings.TrimSpace(tmp[0])] = strings.TrimSpace(tmp[1])
}
fmt.Println(m)
}
1- 您可以使用 encoding/csv
中的 csv.NewReader
使用 r.ReadAll()
一个函数调用 r.ReadAll()
阅读全部:
r.Comma = '='
r.TrimLeadingSpace = true
结果为 [][]string
,输入顺序被保留,在 The Go Playground 上尝试:
package main
import (
"encoding/csv"
"fmt"
"strings"
)
func main() {
text := `Var1=Value1
Var2=Value2
Var3=Value3`
r := csv.NewReader(strings.NewReader(text))
r.Comma = '='
r.TrimLeadingSpace = true
all, err := r.ReadAll()
if err != nil {
panic(err)
}
fmt.Println(all)
}
输出:
[[Var1 Value1] [Var2 Value2] [Var3 Value3]]
2- 您可以微调 csv.ReadAll()
以将输出转换为地图,但 顺序未保留 ,请在 The Go Playground 上尝试:
package main
import (
"encoding/csv"
"fmt"
"io"
"strings"
)
func main() {
text := `Var1=Value1
Var2=Value2
Var3=Value3`
r := csv.NewReader(strings.NewReader(text))
r.Comma = '='
r.TrimLeadingSpace = true
all, err := ReadAll(r)
if err != nil {
panic(err)
}
fmt.Println(all)
}
func ReadAll(r *csv.Reader) (map[string]string, error) {
m := make(map[string]string)
for {
tmp, err := r.Read()
if err == io.EOF {
return m, nil
}
if err != nil {
return nil, err
}
m[tmp[0]] = tmp[1]
}
}
输出:
map[Var2:Value2 Var3:Value3 Var1:Value1]
首先,我建议阅读这个相关问题:How to handle configuration in Go
接下来,我真的会考虑以另一种格式存储您的配置。因为你提出的不是标准。它接近 Java's property file format (.properties
),但即使 属性 文件也可能包含 Unicode 序列,因此您的代码不是有效的 .properties
格式解析器,因为它根本不处理 Unicode 序列。
相反,我建议使用 JSON,因此您可以使用 Go 或任何其他语言轻松解析它,并且有很多工具可以编辑 JSON 文本,而且它仍然是人类-友好。
使用 JSON 格式,将其解码为 map
只是一个函数调用:json.Unmarshal()
。它可能看起来像这样:
text := `{"Var1":"Value1", "Var2":"Value2", "Var3":"Value3"}`
var m map[string]string
if err := json.Unmarshal([]byte(text), &m); err != nil {
fmt.Println("Invalid config file:", err)
return
}
fmt.Println(m)
输出(在 Go Playground 上尝试):
map[Var1:Value1 Var2:Value2 Var3:Value3]
json
包将为您处理格式化和转义,因此您不必担心这些。它还会为您检测并报告错误。另外 JSON 更灵活,您的配置可能包含数字、文本、数组等。所有这些都用于 "free" 只是因为您选择了 JSON 格式。
另一种流行的配置格式是 YAML, but the Go standard library does not include a YAML parser. See Go implementation github.com/go-yaml/yaml
。
如果您不想更改格式,那么我将只使用您发布的代码,因为它完全按照您的要求执行:逐行处理输入,然后解析 name = value
每行的一对。 而且它以一种清晰明了的方式做到了。为此目的使用 CSV 或任何其他 reader 是不好的,因为它们隐藏了引擎盖下的内容(它们有意且正确地隐藏了格式特定的细节和转换)。一个CSV reader 是一个CSV reader 首先;即使您更改了制表符/逗号:它 将 解释某些转义序列,并可能为您提供与您在纯文本编辑器中看到的不同的数据。从您的角度来看,这是一种意外行为,但是嘿,您的输入是 而不是 CSV 格式,但您要求 reader 将其解释为 CSV!
我要添加到您的解决方案中的一项改进是使用 bufio.Scanner
。它可用于逐行读取输入,并处理不同样式的换行序列。它可能看起来像这样:
text := `Var1=Value1
Var2=Value2
Var3=Value3`
scanner := bufio.NewScanner(strings.NewReader(text))
m := map[string]string{}
for scanner.Scan() {
parts := strings.Split(scanner.Text(), "=")
if len(parts) == 2 {
m[strings.TrimSpace(parts[0])] = strings.TrimSpace(parts[1])
}
}
if err := scanner.Err(); err != nil {
fmt.Println("Error encountered:", err)
}
fmt.Println(m)
输出相同。在 Go Playground.
上试试
使用bufio.Scanner
还有一个好处:bufio.NewScanner()
accepts an io.Reader
, the general interface for "all things being a source of bytes". This means if your config is stored in a file, you don't even have to read all the config into the memory, you can just open the file e.g. with os.Open()
which returns a value of *os.File
也实现了io.Reader
,所以你可以直接将*os.File
值传递给bufio.NewScanner()
(等等bufio.Scanner
将从文件中读取,而不是像上例中那样从内存缓冲区中读取。
Go 相当新,基本上在我正在编写的实际代码中,我打算从一个包含环境变量的文件中读取,即 API_KEY=XYZ
。意味着我可以让它们不受版本控制。下面的解决方案 'works' 但我觉得可能有更好的方法。
最终目标是能够像这样访问文件中的元素
m["API_KEY"]
将打印 XYZ
。这甚至可能已经存在,我正在重新发明轮子,我看到 Go 有环境变量,但它似乎并不是我特别想要的。
非常感谢您的帮助。
代码:
package main
import (
"fmt"
"strings"
)
var m = make(map[string]string)
func main() {
text := `Var1=Value1
Var2=Value2
Var3=Value3`
arr := strings.Split(text, "\n")
for _, value := range arr {
tmp := strings.Split(value, "=")
m[strings.TrimSpace(tmp[0])] = strings.TrimSpace(tmp[1])
}
fmt.Println(m)
}
1- 您可以使用 encoding/csv
中的 csv.NewReader
使用 r.ReadAll()
一个函数调用 r.ReadAll()
阅读全部:
r.Comma = '='
r.TrimLeadingSpace = true
结果为 [][]string
,输入顺序被保留,在 The Go Playground 上尝试:
package main
import (
"encoding/csv"
"fmt"
"strings"
)
func main() {
text := `Var1=Value1
Var2=Value2
Var3=Value3`
r := csv.NewReader(strings.NewReader(text))
r.Comma = '='
r.TrimLeadingSpace = true
all, err := r.ReadAll()
if err != nil {
panic(err)
}
fmt.Println(all)
}
输出:
[[Var1 Value1] [Var2 Value2] [Var3 Value3]]
2- 您可以微调 csv.ReadAll()
以将输出转换为地图,但 顺序未保留 ,请在 The Go Playground 上尝试:
package main
import (
"encoding/csv"
"fmt"
"io"
"strings"
)
func main() {
text := `Var1=Value1
Var2=Value2
Var3=Value3`
r := csv.NewReader(strings.NewReader(text))
r.Comma = '='
r.TrimLeadingSpace = true
all, err := ReadAll(r)
if err != nil {
panic(err)
}
fmt.Println(all)
}
func ReadAll(r *csv.Reader) (map[string]string, error) {
m := make(map[string]string)
for {
tmp, err := r.Read()
if err == io.EOF {
return m, nil
}
if err != nil {
return nil, err
}
m[tmp[0]] = tmp[1]
}
}
输出:
map[Var2:Value2 Var3:Value3 Var1:Value1]
首先,我建议阅读这个相关问题:How to handle configuration in Go
接下来,我真的会考虑以另一种格式存储您的配置。因为你提出的不是标准。它接近 Java's property file format (.properties
),但即使 属性 文件也可能包含 Unicode 序列,因此您的代码不是有效的 .properties
格式解析器,因为它根本不处理 Unicode 序列。
相反,我建议使用 JSON,因此您可以使用 Go 或任何其他语言轻松解析它,并且有很多工具可以编辑 JSON 文本,而且它仍然是人类-友好。
使用 JSON 格式,将其解码为 map
只是一个函数调用:json.Unmarshal()
。它可能看起来像这样:
text := `{"Var1":"Value1", "Var2":"Value2", "Var3":"Value3"}`
var m map[string]string
if err := json.Unmarshal([]byte(text), &m); err != nil {
fmt.Println("Invalid config file:", err)
return
}
fmt.Println(m)
输出(在 Go Playground 上尝试):
map[Var1:Value1 Var2:Value2 Var3:Value3]
json
包将为您处理格式化和转义,因此您不必担心这些。它还会为您检测并报告错误。另外 JSON 更灵活,您的配置可能包含数字、文本、数组等。所有这些都用于 "free" 只是因为您选择了 JSON 格式。
另一种流行的配置格式是 YAML, but the Go standard library does not include a YAML parser. See Go implementation github.com/go-yaml/yaml
。
如果您不想更改格式,那么我将只使用您发布的代码,因为它完全按照您的要求执行:逐行处理输入,然后解析 name = value
每行的一对。 而且它以一种清晰明了的方式做到了。为此目的使用 CSV 或任何其他 reader 是不好的,因为它们隐藏了引擎盖下的内容(它们有意且正确地隐藏了格式特定的细节和转换)。一个CSV reader 是一个CSV reader 首先;即使您更改了制表符/逗号:它 将 解释某些转义序列,并可能为您提供与您在纯文本编辑器中看到的不同的数据。从您的角度来看,这是一种意外行为,但是嘿,您的输入是 而不是 CSV 格式,但您要求 reader 将其解释为 CSV!
我要添加到您的解决方案中的一项改进是使用 bufio.Scanner
。它可用于逐行读取输入,并处理不同样式的换行序列。它可能看起来像这样:
text := `Var1=Value1
Var2=Value2
Var3=Value3`
scanner := bufio.NewScanner(strings.NewReader(text))
m := map[string]string{}
for scanner.Scan() {
parts := strings.Split(scanner.Text(), "=")
if len(parts) == 2 {
m[strings.TrimSpace(parts[0])] = strings.TrimSpace(parts[1])
}
}
if err := scanner.Err(); err != nil {
fmt.Println("Error encountered:", err)
}
fmt.Println(m)
输出相同。在 Go Playground.
上试试使用bufio.Scanner
还有一个好处:bufio.NewScanner()
accepts an io.Reader
, the general interface for "all things being a source of bytes". This means if your config is stored in a file, you don't even have to read all the config into the memory, you can just open the file e.g. with os.Open()
which returns a value of *os.File
也实现了io.Reader
,所以你可以直接将*os.File
值传递给bufio.NewScanner()
(等等bufio.Scanner
将从文件中读取,而不是像上例中那样从内存缓冲区中读取。