日常開發中讀取配置文件包含以下幾種格式:
- json 格式字符串
- K=V 鍵值對
- xml 文件
- yml 格式文件
- toml 格式文件
前面兩種書寫簡單,解析過程也比較簡單。xml形式書寫比較累贅,yml是樹形結構,為簡化配置而生,toml是一種有着自己語法規則的配置文件格式,我們一一來看使用方式,各位看官自行比較哪種更加實用。
1.讀取json格式的文件
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
"sync"
)
type Configs map[string]json.RawMessage
var configPath string = "c:/test.json"
type MainConfig struct {
Port string `json:"port"`
Address string `json:"address"`
}
var conf *MainConfig
var confs Configs
var instanceOnce sync.Once
//從配置文件中載入json字符串
func LoadConfig(path string) (Configs, *MainConfig) {
buf, err := ioutil.ReadFile(path)
if err != nil {
log.Panicln("load config conf failed: ", err)
}
mainConfig := &MainConfig{}
err = json.Unmarshal(buf, mainConfig)
if err != nil {
log.Panicln("decode config file failed:", string(buf), err)
}
allConfigs := make(Configs, 0)
err = json.Unmarshal(buf, &allConfigs)
if err != nil {
log.Panicln("decode config file failed:", string(buf), err)
}
return allConfigs, mainConfig
}
//初始化 可以運行多次
func SetConfig(path string) {
allConfigs, mainConfig := LoadConfig(path)
configPath = path
conf = mainConfig
confs = allConfigs
}
// 初始化,只能運行一次
func Init(path string) *MainConfig {
if conf != nil && path != configPath {
log.Printf("the config is already initialized, oldPath=%s, path=%s", configPath, path)
}
instanceOnce.Do(func() {
allConfigs, mainConfig := LoadConfig(path)
configPath = path
conf = mainConfig
confs = allConfigs
})
return conf
}
//初始化配置文件 為 struct 格式
func Instance() *MainConfig {
if conf == nil {
Init(configPath)
}
return conf
}
//初始化配置文件 為 map格式
func AllConfig() Configs {
if conf == nil {
Init(configPath)
}
return confs
}
//獲取配置文件路徑
func ConfigPath() string {
return configPath
}
//根據key獲取對應的值,如果值為struct,則繼續反序列化
func (cfg Configs) GetConfig(key string, config interface{}) error {
c, ok := cfg[key]
if ok {
return json.Unmarshal(c, config)
} else {
return fmt.Errorf("fail to get cfg with key: %s", key)
}
}
func main() {
path := ConfigPath()
fmt.Println("path: ",path)
Init(path)
value := confs["port"]
fmt.Println(string(value))
}
json格式文件內容:
{
"port": "7788",
"address": "47.95.34.2"
}
運行結果:
path: c:/test.json
"7788"
2. 讀取key=value類型的配置文件
package main
import (
"bufio"
"io"
"os"
"strings"
)
//讀取key=value類型的配置文件
func InitConfig(path string) map[string]string {
config := make(map[string]string)
f, err := os.Open(path)
defer f.Close()
if err != nil {
panic(err)
}
r := bufio.NewReader(f)
for {
b, _, err := r.ReadLine()
if err != nil {
if err == io.EOF {
break
}
panic(err)
}
s := strings.TrimSpace(string(b))
index := strings.Index(s, "=")
if index < 0 {
continue
}
key := strings.TrimSpace(s[:index])
if len(key) == 0 {
continue
}
value := strings.TrimSpace(s[index+1:])
if len(value) == 0 {
continue
}
config[key] = value
}
return config
}
func main() {
config := InitConfig("c:/1.txt")
ip := config["ip"]
port := config["port"]
fmt.Println("ip=",string(ip)," port=",string(port))
}
配置文件類容:
ip=127.0.0.1
port=3344
運行結果:
ip=127.0.0.1 port=3344
3. 讀取yml格式文件
Java中SpringBoot支持使用yml格式的配置文件作為替代properties文件的一種方式。跟properties文件相比,好處就是層級目錄,相同的前綴都在該前綴下,前綴只用寫一次即可。Go也支持yml文件解析,只是麻煩的程度真的是,,,不想寫!
我們先定義一個yml文件:
port: 8080
ip: 127.0.0.1
host: www.baidu.com
spring:
redis:
host: redis.dns.baidu.com
port: 6379
dataBase: 0
timeout: 2000
解析代碼如下:
package utils
import (
"fmt"
"gopkg.in/yaml.v2"
"io/ioutil"
)
//解析yml文件
type BaseInfo struct {
Port string `yaml:"port"`
Ip string `yaml:"ip"`
Host string `yaml:"host"`
Spring RedisEntity `yaml:"spring"`
}
type RedisEntity struct {
Redis RedisData `yaml:"redis"`
}
type RedisData struct {
Host string `yaml:"host"`
Port string `yaml:"port"`
DataBase string `yaml:"dataBase"`
Timeout string `yaml:"timeout"`
}
func (c *BaseInfo) GetConf() *BaseInfo {
yamlFile, err := ioutil.ReadFile("c:/1.yml")
if err != nil {
fmt.Println(err.Error())
}
err = yaml.Unmarshal(yamlFile, c)
if err != nil {
fmt.Println(err.Error())
}
return c
}
解釋一下:以上yml文件中是有三層目錄結構,所以需要定義三個struct,每個struct分別包含每一層的字段。這樣說你應該就能明白如何解析。
運行一下test方法來驗證上面程序:
package main
import (
"fmt"
"goProject/src/utils"
"testing"
)
func Test(t *testing.T) {
info := utils.BaseInfo{}
conf := info.GetConf()
fmt.Println(conf.Host)
}
可以看到將yml中的樹形結構解析為BaseInfo
對象。
4. 讀取toml格式文件
仿佛我感覺這個比讀取yml文件更為痛苦,因為在寫toml文件的時候,還有一定的語法規則,仿佛在告訴你別停,繼續學習。
TOML 的全稱是Tom’s Obvious, Minimal Language,因為它的作者是 GitHub聯合創始人Tom Preston-Werner 。 TOML 的目標是成為一個極簡的配置文件格式。TOML 被設計成可以無歧義地被映射為哈希表的結構,從而可以被多種語言解析。
舉個例子:
port=8080
[user]
name="xiaoming"
age=14
sex=1
[database]
servers=["127.0.0.1","127.0.0.2","127.0.0.3"]
connection_max=5000
enabled=true
[servers]
# 你可以依照你的意願縮進。使用空格或Tab。TOML不會在意。
[servers.a]
ip="34.23.1.4"
port=6379
[servers.b]
ip="34.23.1.6"
port=9921
#嵌套
[nest]
data=[["n1","n2"],[1,2]]
# 在數組里換行沒有關系。
names = [
"li",
"wang"
]
下面來解釋一下toml格式的書寫規范。
首先:TOML 是大小寫敏感的。
常見的語法規則:
-
注釋
使用 # 表示注釋。
-
字符串
字符串以""
包裹,里面的字符必須是 UTF-8 格式。引號、反斜杠和控制字符(U+0000 到 U+001F)需要轉義。
常用的轉義序列:
\b - backspace (U+0008)
\t - tab (U+0009)
\n - linefeed (U+000A)
\f - form feed (U+000C)
\r - carriage return (U+000D)
\" - quote (U+0022)
\/ - slash (U+002F)
\\ - backslash (U+005C)
\uXXXX - unicode (U+XXXX)
-
布爾值
true
false
-
日期
使用ISO8601格式日期:
2019-05-03T22:44:26Z
-
數組
數組使用方括號包裹。空格會被忽略。元素使用逗號分隔。注意,不允許混用數據類型。
[ 1, 2, 3 ] [ "red", "yellow", "green" ] [ [ 1, 2 ], [3, 4, 5] ] [ [ 1, 2 ], ["a", "b", "c"] ] # 這是可以的。 [ 1, 2.0 ] # 注意:這是不行的。
-
數值類型
數值類型嚴格區分整數和浮點數。這兩個不是一種類型。
-
字典對象
多個kv集合在一起組成字典對象。字典對象的名字用
[]
包含起來,單獨作為一行。解釋一下這種格式對應的json格式:
[servers.a] ip="34.23.1.4" port=6379 { "servers": { "a": { "ip": "34.23.1.4","port":6379}}}
我們寫一個小程序來解析上面的toml文件:
package utils import ( "fmt" "github.com/BurntSushi/toml" "io/ioutil" "os" ) type BaseData struct { Db DataBase `toml:"dataBase"` Se Servers `toml:"servers"` } type DataBase struct { Servers []string `toml:"servers"` ConnectionMax int `toml:"connection_max"` Enabled bool `toml:"enabled"` } type Servers struct { A ServerEn `toml:"a"` B ServerEn `toml:"b"` } type ServerEn struct { IP string `toml:"ip"` Port int `toml:"port"` } func ReadConf(fname string) (p *BaseData, err error) { var ( fp *os.File fcontent []byte ) p = new(BaseData) if fp, err = os.Open(fname); err != nil { fmt.Println("open error ", err) return } if fcontent, err = ioutil.ReadAll(fp); err != nil { fmt.Println("ReadAll error ", err) return } if err = toml.Unmarshal(fcontent, p); err != nil { fmt.Println("toml.Unmarshal error ", err) return } return }
注意看我定義的對象,也是逐層解析。
測試類:
package main import ( "fmt" "goProject/src/utils" "testing" ) func Test(t *testing.T) { p, _ := utils.ReadConf("c:/1.toml") fmt.Println(p) }