首页
网站导航
关于
Search
1
解决Typecho Joe主题访问fastly.jsdelivr.net速度慢的方案 | 快速优化技巧
2,188 阅读
2
解决WSL2内存不释放问题的最佳指南
1,398 阅读
3
如何在 Typecho Joe 主题的文章中增加目录
1,170 阅读
4
如何解决Win11电脑桌面上方显示横线问题 | 窗口11教程
1,003 阅读
5
GO语言环境的搭建教程 - 完全指南
1,002 阅读
默认分类
编程语言
GO语言
PHP
Node
javascript
html
rust
java
Css
Python
资源分享
chrome插件
阅读思考
运维架构
redis
Nginx
linux
memcached
mongodb
mysql
windows
docker
k8s
Mq
apache
CI
Git
swoole
elk
系统设计
thinkPhp
beego
登录
Search
标签搜索
重要
go基础
git 命令
go包
phpstorm
sublime
thinkphp6
mysql问题
软件分享
redis命令
php基础
thinkphp3.2
php第三扩展包
小蚯蚓博客
累计撰写
333
篇文章
累计收到
48
条评论
首页
栏目
默认分类
编程语言
GO语言
PHP
Node
javascript
html
rust
java
Css
Python
资源分享
chrome插件
阅读思考
运维架构
redis
Nginx
linux
memcached
mongodb
mysql
windows
docker
k8s
Mq
apache
CI
Git
swoole
elk
系统设计
thinkPhp
beego
页面
网站导航
关于
搜索到
10
篇与
的结果
2024-03-23
Sonic - 高性能JSON库
介绍 我们在日常开发中,常常会对JSON进行序列化和反序列化。Golang提供了encoding/json包对JSON进行Marshal/Unmarshal操作。但是在大规模数据场景下,该包的性能和开销确实会有点不够看。在生产环境下,JSON 序列化和反序列化会被频繁的使用到。在测试中,CPU使用率接近 10%,其中极端情况下超过 40%。因此,JSON 库的性能是提高机器利用率的关键问题。 Sonic是一款由字节跳动开发的一个全新的高性能、适用广泛的 JSON 库。在设计上借鉴了多款JSON库,同时为了实现对标准库的真正插拔式替换,Sonic使用了 JIT** (即时编译)**。 Sonic的特色 我们可以看出:Sonic是一个主打快的JSON库。 运行时对象绑定,无需代码生成 完备的 JSON 操作 API 快,更快,还要更快! Sonic的设计 针对编解码动态汇编的函数调用开销,使用 JIT 技术在运行时组装与模式对应的字节码(汇编指令),最终将其以 Golang 函数的形式缓存在堆外内存上。 针对大数据和小数据共存的实际场景,使用预处理判断(字符串大小、浮点数精度等)将 SIMD 与标量指令相结合,从而实现对实际情况的最佳适应。 对于 Golang 语言编译优化的不足,使用 C/Clang 编写和编译核心计算函数,并且开发了一套 asm2asm 工具,将经过充分优化的 x86 汇编代码转换为 Plan9 格式,最终加载到 Golang 运行时中。 考虑到解析和跳过解析之间的速度差异很大, 惰性加载机制当然也在 AST 解析器中使用了,但以一种更具适应性和高效性的方式来降低多键查询的开销。 在细节上,Sonic进行了一些进一步的优化: 由于 Golang 中的原生汇编函数不能被内联,发现其成本甚至超过了 C 编译器的优化所带来的改善。所以在 JIT 中重新实现了一组轻量级的函数调用: 全局函数表+静态偏移量,用于调用指令 使用寄存器传递参数 Sync.Map 一开始被用来缓存编解码器,但是对于准静态(读远多于写),元素较少(通常不足几十个)的场景,它的性能并不理想,所以使用开放寻址哈希和 RCU 技术重新实现了一个高性能且并发安全的缓存。 详细设计文档可查阅introduction 安装使用 当前我使用的go version是1.21。 官方建议的版本: Go 1.16~1.21 Linux / MacOS / Windows(需要 Go1.17 以上) Amd64 架构 # 下载sonic依赖 $ go get github.com/bytedance/sonic 基本使用 sonic提供了许多功能。本文仅列举其中较为有特色的功能。感兴趣的同学可以去看一下官方的examples 序列化/反序列化 sonic的使用类似于标准包encoding/json包的使用. func base() { m := map[string]interface{}{ "name": "z3", "age": 20, } // sonic序列化 byt, err := sonic.Marshal(&m) if err != nil { log.Println(err) } fmt.Printf("json: %+v\n", string(byt)) // sonic反序列化 um := make(map[string]interface{}) err = sonic.Unmarshal(byt, &um) if err != nil { log.Println(err) } fmt.Printf("unjson: %+v\n", um) } // print // json: {"name":"z3","age":20} // unjson: map[age:20 name:z3] sonic还支持流式的输入输出 Sonic 支持解码 io.Reader 中输入的 json,或将对象编码为 json 后输出至 io.Writer,以处理多个值并减少内存消耗 func base() { m := map[string]interface{}{ "name": "z3", "age": 20, } // 流式io编解码 // 编码 var encbuf bytes.Buffer enc := sonic.ConfigDefault.NewEncoder(&encbuf) if err := enc.Encode(m); err != nil { log.Fatal(err) } else { fmt.Printf("cutomize encoder: %+v", encbuf.String()) } // 解码 var decbuf bytes.Buffer decbuf.WriteString(encbuf.String()) clear(m) dec := sonic.ConfigDefault.NewDecoder(&decbuf) if err := dec.Decode(&m); err != nil { log.Fatal(err) } else { fmt.Printf("cutomize decoder: %+v\n", m) } } // print // cutomize encoder: {"name":"z3","age":20} // cutomize decoder: map[age:20 name:z3] 配置 在上面的自定义流式编码解码器,细心的朋友可能看到我们创建编码器和解码器的时候,是通过sonic.ConfigDefault.NewEncoder() / sonic.ConfigDefault.NewDecoder()这两个函数进行调用的。那么sonic.ConfigDefault是什么? 我们可以通过查看源码: var ( // ConfigDefault is the default config of APIs, aiming at efficiency and safty. // ConfigDefault api的默认配置,针对效率和安全。 ConfigDefault = Config{}.Froze() // ConfigStd is the standard config of APIs, aiming at being compatible with encoding/json. // ConfigStd是api的标准配置,旨在与encoding/json兼容。 ConfigStd = Config{ EscapeHTML : true, SortMapKeys: true, CompactMarshaler: true, CopyString : true, ValidateString : true, }.Froze() // ConfigFastest is the fastest config of APIs, aiming at speed. // ConfigFastest是api的最快配置,旨在提高速度。 ConfigFastest = Config{ NoQuoteTextMarshaler: true, }.Froze() ) sonic提供了三种常用的Config配置。这些配置中对一些场景已经预定义好了对应的Config。 其实我们使用的sonic.Marshal()函数就是调用了默认的ConfigDefault // Marshal returns the JSON encoding bytes of v. func Marshal(val interface{}) ([]byte, error) { return ConfigDefault.Marshal(val) } // Unmarshal parses the JSON-encoded data and stores the result in the value pointed to by v. // NOTICE: This API copies given buffer by default, // if you want to pass JSON more efficiently, use UnmarshalString instead. func Unmarshal(buf []byte, val interface{}) error { return ConfigDefault.Unmarshal(buf, val) } 但是在一些场景下我们不满足于sonic预定义的三个Config。此时我们可以定义自己的Config进行个性化的编码和解码。 首先先看一下Config的结构。 // Config is a combination of sonic/encoder.Options and sonic/decoder.Options type Config struct { // EscapeHTML indicates encoder to escape all HTML characters // after serializing into JSON (see https://pkg.go.dev/encoding/json#HTMLEscape). // WARNING: This hurts performance A LOT, USE WITH CARE. EscapeHTML bool // SortMapKeys indicates encoder that the keys of a map needs to be sorted // before serializing into JSON. // WARNING: This hurts performance A LOT, USE WITH CARE. SortMapKeys bool // CompactMarshaler indicates encoder that the output JSON from json.Marshaler // is always compact and needs no validation CompactMarshaler bool // NoQuoteTextMarshaler indicates encoder that the output text from encoding.TextMarshaler // is always escaped string and needs no quoting NoQuoteTextMarshaler bool // NoNullSliceOrMap indicates encoder that all empty Array or Object are encoded as '[]' or '{}', // instead of 'null' NoNullSliceOrMap bool // UseInt64 indicates decoder to unmarshal an integer into an interface{} as an // int64 instead of as a float64. UseInt64 bool // UseNumber indicates decoder to unmarshal a number into an interface{} as a // json.Number instead of as a float64. UseNumber bool // UseUnicodeErrors indicates decoder to return an error when encounter invalid // UTF-8 escape sequences. UseUnicodeErrors bool // DisallowUnknownFields indicates decoder to return an error when the destination // is a struct and the input contains object keys which do not match any // non-ignored, exported fields in the destination. DisallowUnknownFields bool // CopyString indicates decoder to decode string values by copying instead of referring. CopyString bool // ValidateString indicates decoder and encoder to valid string values: decoder will return errors // when unescaped control chars(\u0000-\u001f) in the string value of JSON. ValidateString bool } 由于字段较多。笔者就选择几个字段进行演示,其他字段使用方式都是一致。 假设我们希望对JSON序列化按照key进行排序以及将JSON编码成紧凑的格式。我们可以配置Config进行Marshal操作 func base() { snc := sonic.Config{ CompactMarshaler: true, SortMapKeys: true, }.Froze() snc.Marshal(obj) } 考虑到排序带来的性能损失(约 10% ), sonic 默认不会启用这个功能。 Sonic 默认将基本类型( struct , map 等)编码为紧凑格式的 JSON ,除非使用 json.RawMessage or json.Marshaler 进行编码: sonic 确保输出的 JSON 合法,但出于性能考虑,不会加工成紧凑格式。我们提供选项 encoder.CompactMarshaler 来添加此过程, Ast.Node sonic提供了Ast.Node的功能。Sonic/ast.Node 是完全独立的 JSON 抽象语法树库。它实现了序列化和反序列化,并提供了获取和修改通用数据的鲁棒的 API。 先来简单介绍一下Ast.Node:ast.Node 通常指的是编程语言中的抽象语法树(Abstract Syntax Tree)节点。抽象语法树是编程语言代码在编译器中的内部表示,它以树状结构展现代码的语法结构,便于编译器进行语法分析、语义分析、优化等操作。 在很多编程语言的编译器或解释器实现中,抽象语法树中的每个元素(节点)都会有对应的数据结构表示,通常这些数据结构会被称为 ast.Node 或类似的名字。每个 ast.Node 表示源代码中的一个语法结构,如表达式、语句、函数声明等。 抽象语法树的节点可以包含以下信息: 节点的类型:例如表达式、语句、函数调用等。 节点的内容:节点所代表的源代码的内容。 子节点:一些节点可能包含子节点,这些子节点也是抽象语法树的节点,用于构建更复杂的语法结构。 属性:一些节点可能会包含附加的属性,如变量名、操作符类型等。 我们通过几个案例理解一下Ast.Node的使用。 准备数据 data := `{"name": "z3","info":{"num": [11,22,33]}}` 将数据转换为Ast.Node 通过传入bytes或者string返回一个Ast.Node。其中你可以指定path获取JSON中的子路径元素。 每个路径参数必须是整数或者字符串 整数是目标索引(>=0),表示以数组形式搜索当前节点。 字符串为目标key,表示搜索当前节点为对象。 // 函数签名: func Get(src []byte, path ...interface{}) (ast.Node, error) { return GetFromString(string(src), path...) } // GetFromString与Get相同,只是src是字符串,这样可以减少不必要的内存拷贝。 func GetFromString(src string, path ...interface{}) (ast.Node, error) { return ast.NewSearcher(src).GetByPath(path...) } 获取当前节点的完整数据 func base() { data := `{"name": "z3","info":{"num": [11,22,33]}}` // no path return all json string root, err := sonic.GetFromString(data) if err != nil { log.Panic(err) } if raw, err := root.Raw(); err != nil { log.Panic(err) } else { log.Println(raw) } } // print // 2023/08/26 17:15:52 {"name": "z3","info":{"num": [11,22,33]}} 根据path或者索引获取数据 func base() { data := `{"name": "z3","info":{"num": [11,22,33]}}` // no path return all json string root, err := sonic.GetFromString(data) if err != nil { log.Panic(err) } // according to path(根据key,查询当前node下的元素) if path, err := root.GetByPath("name").Raw(); err != nil { log.Panic(err) } else { log.Println(path) } // indexOrget (同时提供index和key进行索引和key的匹配) if path, err := root.IndexOrGet(1, "info").Raw(); err != nil { log.Panic(err) } else { log.Println(path) } // index (按照index进行查找当前node下的元素) // root.Index(1).Index(0).Raw()意味着 // root.Index(1) == "info" // root.Index(1).Index(0) == "num" // root.Index(1).Index(0).Raw() == "[11,22,33]" if path, err := root.Index(1).Index(0).Raw(); err != nil { log.Panic(err) } else { log.Println(path) } } // print // 2023/08/26 17:17:49 "z3" // 2023/08/26 17:17:49 {"num": [11,22,33]} // 2023/08/26 17:17:49 [11,22,33] Ast.Node支持链式调用。故我们可以从root node节点,根据path路径向下搜索指定的元素。 index和key混用 user := root.GetByPath("statuses", 3, "user") // === root.Get("status").Index(3).Get("user") 根据path进行修改数据 func base() { data := `{"name": "z3","info":{"num": [11,22,33]}}` // no path return all json string root, err := sonic.GetFromString(data) if err != nil { log.Panic(err) } // according to path if path, err := root.GetByPath("name").Raw(); err != nil { log.Panic(err) } else { log.Println(path) } // indexOrget (同时提供index和key进行索引和key的匹配) if path, err := root.IndexOrGet(1, "info").Raw(); err != nil { log.Panic(err) } else { log.Println(path) } // index if path, err := root.Index(1).Index(0).Raw(); err != nil { log.Panic(err) } else { log.Println(path) } // set // ast提供了很多go类型转换node的函数 if _, err := root.Index(1).SetByIndex(0, ast.NewArray([]ast.Node{ ast.NewNumber("101"), ast.NewNumber("202"), })); err != nil { log.Panic(err) } raw, _ := root.Raw() log.Println(raw) } // print // 2023/08/26 17:23:55 "z3" // 2023/08/26 17:23:55 {"num": [11,22,33]} // 2023/08/26 17:23:55 [11,22,33] // 2023/08/26 17:23:55 {"name":"z3","info":{"num":[101,202]}} 序列化 func base() { data := `{"name": "z3","info":{"num": [11,22,33]}}` // no path return all json string root, err := sonic.GetFromString(data) if err != nil { log.Panic(err) } bts, _ := root.MarshalJSON() log.Println("Ast.Node(Marshal): ", string(bts)) btes, _ := json.Marshal(&root) log.Println("encoding/json (Marshal): ", string(btes)) } // print // 2023/08/26 17:39:06 Ast.Node(Marshal): {"name": "z3","info":{"num": [11,22,33]}} // 2023/08/26 17:39:06 encoding/json (Marshal): {"name":"z3","info":{"num":[11,22,33]}} ⚠: 使用json.Marshal() (必须传递指向节点的指针) API Ast.Node提供了许多有特色的API,感兴趣的朋友可以去试一下。 合法性检查: Check(), Error(), Valid(), Exist() 索引: Index(), Get(), IndexPair(), IndexOrGet(), GetByPath() 转换至 go 内置类型: Int64(), Float64(), String(), Number(), Bool(), Map[UseNumber|UseNode](), Array[UseNumber|UseNode](), Interface[UseNumber|UseNode]() go 类型打包: NewRaw(), NewNumber(), NewNull(), NewBool(), NewString(), NewObject(), NewArray() 迭代: Values(), Properties(), ForEach(), SortKeys() 修改: Set(), SetByIndex(), Add() 最佳实践 预热 由于 Sonic 使用 golang-asm 作为 JIT 汇编器,这个库并不适用于运行时编译,第一次运行一个大型模式可能会导致请求超时甚至进程内存溢出。为了更好地稳定性,我们建议在运行大型模式或在内存有限的应用中,在使用 Marshal()/Unmarshal() 前运行 Pretouch()。 拷贝字符串 当解码 没有转义字符的字符串时, sonic 会从原始的 JSON 缓冲区内引用而不是复制到新的一个缓冲区中。这对 CPU 的性能方面很有帮助,但是可能因此在解码后对象仍在使用的时候将整个 JSON 缓冲区保留在内存中。实践中我们发现,通过引用 JSON 缓冲区引入的额外内存通常是解码后对象的 20% 至 80% ,一旦应用长期保留这些对象(如缓存以备重用),服务器所使用的内存可能会增加。我们提供了选项 decoder.CopyString() 供用户选择,不引用 JSON 缓冲区。这可能在一定程度上降低 CPU 性能 func base() { // 在sonic.Config中进行配置 snc := sonic.Config{ CopyString: true, }.Froze() } 传递字符串还是字节数组 为了和 encoding/json 保持一致,我们提供了传递 []byte 作为参数的 API ,但考虑到安全性,字符串到字节的复制是同时进行的,这在原始 JSON 非常大时可能会导致性能损失。因此,你可以使用 UnmarshalString() 和 GetFromString() 来传递字符串,只要你的原始数据是字符串,或零拷贝类型转换对于你的字节数组是安全的。我们也提供了 MarshalString() 的 API ,以便对编码的 JSON 字节数组进行零拷贝类型转换,因为 sonic 输出的字节始终是重复并且唯一的,所以这样是安全的。 零拷贝类型转换是一种技术,它允许你在不进行实际数据复制的情况下,将一种数据类型转换为另一种数据类型。这种转换通过操作原始内存块的指针和切片来实现,避免了额外的数据复制,从而提高性能并减少内存开销. 需要注意的是,零拷贝类型转换虽然可以提高性能,但也可能引入一些安全和可维护性的问题,特别是当直接操作指针或内存映射时。 性能优化 在 完全解析的场景下, Unmarshal() 表现得比 Get()+Node.Interface() 更好。 func base() { data := `{"name": "z3","info":{"num": [11,22,33]}}` // complete parsing m := map[string]interface{}{} sonic.Unmarshal([]byte(data), &m) } 但是如果你只有特定 JSON的部分模式,你可以将 Get() 和 Unmarshal() 结合使用: func base() { data := `{"name": "z3","info":{"num": [11,22,33]}}` // complete parsing m := map[string]interface{}{} sonic.Unmarshal([]byte(data), &m) // partial parsing clear(m) node, err := sonic.GetFromString(data, "info", "num", 1) if err != nil { panic(err) } log.Println(node.Raw()) } 参考 github.com/sonic
2024年03月23日
55 阅读
0 评论
0 点赞
2023-12-15
Go-Cache 缓存包 - 高效的Golang 缓存解决方案
第三方包github.com/patrickmn/go-cache 代码实现示例 package cache import ( gocache "github.com/patrickmn/go-cache" "strings" "sync" "time" ) const ( BaseKey = "platform-idstore" ) var ( once sync.Once instance *cache ) var NewCache = New() type Cache interface { Set(k string, v interface{}, d time.Duration) Get(k string) (interface{}, bool) Delete(k string) MatchDelete(matchKey string) Flush() } type cache struct { client gocache.Cache } func (c *cache) addBaseKey(k string) (key string) { return BaseKey + ":" + k } func (c *cache) Set(k string, v interface{}, d time.Duration) { key := c.addBaseKey(k) c.client.Set(key, v, d) } func (c *cache) Get(k string) (interface{}, bool) { key := c.addBaseKey(k) return c.client.Get(key) } func (c *cache) Delete(k string) { key := c.addBaseKey(k) c.client.Delete(key) } func (c *cache) MatchDelete(matchKey string) { keys := c.getMatchKeys(matchKey) for _, key := range keys { c.client.Delete(key) } } func (c *cache) getMatchKeys(match string) (keys []string){ items := c.client.Items() for key, _ := range items { matchKey := c.addBaseKey(match) if strings.Index(key, matchKey) == 0 { keys = append(keys, key) } } return } func (c *cache) Flush() { items := c.client.Items() for key, _ := range items { if strings.Index(key, BaseKey) == 0 { c.Delete(strings.Replace(key, BaseKey + ":", "", 1)) } } } func New() Cache { once.Do(func() { instance = &cache{ client: *gocache.New(1*time.Hour, 6*time.Hour), } }) return instance }
2023年12月15日
86 阅读
1 评论
0 点赞
2023-12-15
Go语言Sort包的深入解析及常见用法
SliceStable 实例讲解 本文章向大家介绍GO SliceStable实例讲解,主要分析其语法、参数、返回值和注意事项,并结合实例形式分析了其使用技巧,希望通过本文能帮助到大家理解应用这部分内容。 GO语言"sort"包中"SliceStable"函数的用法及代码示例。 用法: func SliceStable(x any, less func(i, j int) bool) SliceStable 使用提供的 less 函数对切片 x 进行排序,保持相等元素的原始顺序。如果 x 不是切片,它会Panics。 less 函数必须满足与接口类型的 Less 方法相同的要求。 例子: package main import ( "fmt" "sort" ) func main() { people := []struct { Name string Age int }{ {"Alice", 25}, {"Elizabeth", 75}, {"Alice", 75}, {"Bob", 75}, {"Alice", 75}, {"Bob", 25}, {"Colin", 25}, {"Elizabeth", 25}, } // Sort by name, preserving original order sort.SliceStable(people, func(i, j int) bool { return people[i].Name < people[j].Name }) fmt.Println("By name:", people) // Sort by age preserving name order sort.SliceStable(people, func(i, j int) bool { return people[i].Age < people[j].Age }) fmt.Println("By age,name:", people) } 输出: By name: [{Alice 25} {Alice 75} {Alice 75} {Bob 75} {Bob 25} {Colin 25} {Elizabeth 75} {Elizabeth 25}] By age,name: [{Alice 25} {Bob 25} {Colin 25} {Elizabeth 25} {Alice 75} {Alice 75} {Bob 75} {Elizabeth 75}]
2023年12月15日
120 阅读
1 评论
0 点赞
2023-12-07
GORM简单使用指南 | Golang ORM 框架
// 删除表 gorm.Migrator().DropTable(dst...) // 自动创建表 db.Migrator().AutoMigrate(dst...) // 转成sql字符串 tx.ToSQL()
2023年12月07日
117 阅读
0 评论
0 点赞
2023-12-04
Go语言Exc包使用指南
输出到文件 cmd := exec.Command( appCfg.MysqlCfg.DumpFilePath, "-h", appCfg.MysqlCfg.Host, "-u", appCfg.MysqlCfg.User, fmt.Sprintf("-p%s", appCfg.MysqlCfg.Password), "-P", appCfg.MysqlCfg.Port, "-B", appCfg.MysqlCfg.Database, ) stdout, err := os.OpenFile("./backup/databases_backup.sql", os.O_CREATE|os.O_WRONLY, 0600) if err != nil { err = fmt.Errorf("创建./backup/databases_backup.sql文件失败,Err%s", err.Error()) return } defer stdout.Close() // 重定向标准输出到文件 cmd.Stdout = stdout // 执行命令 if err = cmd.Start(); err != nil { err = fmt.Errorf("准备执行备份数据库失败,Err%s", err.Error()) return } if err = cmd.Wait(); err != nil { err = fmt.Errorf("执行mysqldump备份数据库失败,Err%s", err.Error()) return } 标准输出 cmd := exec.Command("zip", "-r", "./backup/backup_project.zip", appCfg.ToZipDir) // // 不需要cmd.Run() _, err = cmd.Output() if err != nil { err = fmt.Errorf("执行 zip -r ./backup/backup_project.zip 命令失败,Err%s", err.Error()) return } return
2023年12月04日
123 阅读
0 评论
0 点赞
2023-12-04
Go CloudStore - 云储存集成
国内各大云存储服务接口集成,让云存储使用更方便简单。 GitHub 地址:https://github.com/TruthHun/CloudStore 国内各大云存储服务接口集成,让云存储使用更方便简单。 目前集成的有:阿里云OSS, 百度云BOS、腾讯云COS、华为云OBS、七牛云、又拍云、Minio
2023年12月04日
117 阅读
0 评论
0 点赞
2022-07-25
Go Flag包详细使用指南 - Go语言编程资源
概述 简单的实现了命令行参数的解析,复杂的命令解析方式可以使用以下两个库 https://github.com/urfave/cli https://github.com/spf13/cobra 定义 flag.Xxx() 方式,Xxx 可以是 Int、String 等类型,返回一个相应类型的指针 func main() { var name = flag.String("name", "", "名字") var age = flag.Int("age", 0, "年龄") flag.Parse() // 名字是: zhangSan 年龄是: 30 fmt.Println("名字是:", *name, "年龄是:", *age) } flag.XxxVar()方式,绑定到一个变量上,也是传入变量的指针 func main() { var name string var age int flag.StringVar(&name,"name", "", "名字") flag.IntVar(&age,"age", 0, "年龄") flag.Parse() // go run main.go -name 张三 -age 30 // 名字是: 张三 年龄是: 30 fmt.Println("名字是:", name, "年龄是:", age) } flag.Var()方式,自定义类型,要实现 flag.Value 接口即可 // 自定义类型 type hobbyValues []string // Set String 实现了flag.value 接口 func (s *hobbyValues) Set(val string) error { *s = hobbyValues(strings.Split(val, ",")) return nil } func (s *hobbyValues) String() string { return strings.Join([]string(*s), ",") } // Get 实现了flag.Getter接口,这个接口可以不实现 func (s *hobbyValues) Get() interface{} { return []string(*s) } func main() { var hobby hobbyValues flag.Var(&hobby, "hobby", "兴趣爱好,多个的话用,分隔") flag.Parse() // go run main.go -hobby 游泳,看书 // 兴趣爱好是: [游泳 看书] fmt.Println("兴趣爱好是:", hobby) } 解析 在所有的 flag 定义完成之后,可以通过调用 flag.Parse() 进行解析,语法有三种形式 // 只支持 bool 类型,省略的话就是默认值 -flag // true -flag=false // 可以使用的值有:1, 0, t, f, true, false, TRUE, FALSE, True, False // bool 和非 bool 类型都支持这个写法 -flag=x // 只支持非 bool 类型 -flag x 类型 ErrorHandling 类型,该类型定义了在参数解析出错时错误处理方式 type ErrorHandling int const ( ContinueOnError ErrorHandling = iota // 错误的话是打印详情 ExitOnError // 错误的话是退出程序 PanicOnError // 错误的话是异常 ) Flag 类型, 代表一个 flag 的状态 // flag.Parse() 之后,会存各个命令参数的值信息 type Flag struct { Name string // name as it appears on command line Usage string // help message Value Value // value as set DefValue string // default value (as text); for usage message } FlagSet 类,flag 集合的信息,flag 集合的信息 // A FlagSet represents a set of defined flags. type FlagSet struct { // Usage is the function called when an error occurs while parsing flags. // The field is a function (not a method) that may be changed to point to // a custom error handler. Usage func() name string // FlagSet 的名字。CommandLine 给的是 os.Args[0] parsed bool // 是否执行过 Parse() actual map[string]*Flag // 存放实际传递了的参数(即命令行参数) formal map[string]*Flag // 存放所有已定义命令行参数 args []string // arguments after flags // 开始存放所有参数,最后保留 非 flag(non-flag)参数 exitOnError bool // does the program exit if there's an error? errorHandling ErrorHandling // 当解析出错时,处理错误的方式 output io.Writer // nil means stderr; use out() accessor } Value 接口,所有参数类型需要实现 Value 接口,flag 包中,为 int、float、bool 等实现了该接口,借助该接口,我们可以自定义 flag // Value is the interface to the dynamic value stored in a flag. // (The default value is represented as a string.) type Value interface { String() string Set(string) error } 函数 go 标准库中,经常这么做,一个包中有个类型,为了方便使用会示例一个该类型的实例 // Flag 包里实例化FlagSet的实例 var CommandLine = NewFlagSet(os.Args[0], ExitOnError) 封装 func Parse() bool { return CommandLine.Parse() } // FlagSet类的方法 func (f *FlagSet) Parsed() bool { return f.parsed } 这样就方便的调用到 flagSet 类的 parse 方法了 // 实际上是调用flag.CommandLine.Parse() 方法 flag.Parse() 主要方法 NewFlagSet() 用于实例化 FlagSet // 默认的 FlagSet 实例在解析出错时会退出程序 var CommandLine = NewFlagSet(os.Args[0], ExitOnError) Parse() 解析参数 func Parse() { // 方法参数 arguments 不包括命令名,即应该是 os.Args[1:] CommandLine.Parse(os.Args[1:]) } 调用真正解析参数的非导出方法: parseOne func (f *FlagSet) Parse(arguments []string) error { seen, err := f.parseOne() } Parse 出错时默认是会退出程序 func (f *FlagSet) Parse(arguments []string) error { switch f.errorHandling { case ContinueOnError: return err case ExitOnError: if err == ErrHelp { os.Exit(0) } os.Exit(2) case PanicOnError: panic(err) } } 解析什么时候停止?当 parseOne 返回 false, nil 时,Parse 解析终止,当遇到单独的一个 "-" 或不是 "-" 开始时,会停止解析 // 单独的一个- ./nginx - -c // build,不是-开始 ./nginx build -c 单独--开头的 ./nginx -- 或者 ./nginx -c default.confg -- test ./nginx -c -- //这种不算,这种--相当于-c的值 Arg(i int) 和 Args() Arg(i int) 和 Args() 这两个方法就是获取 non-flag 参数的 NArg() NArg() 获得 non-flag 的个数 NFlag() NFlag() 获得 FlagSet 中 actual 长度(即被设置了的参数个数) Visit/VisitAll 这两个函数分别用于访问 FlatSet 的 actual 和 formal 中的 Flag,而具体的访问方式由调用者决定 PrintDefaults() 打印所有已定义参数的默认值(调用 VisitAll 实现),默认输出到标准错误,除非指定了 FlagSet 的 output(通过 SetOutput() 设置) Set(name, value string) 设置某个 flag 的值(通过 name 查找到对应的 Flag)
2022年07月25日
219 阅读
0 评论
0 点赞
2022-07-25
Go语言Os包教程及使用指南
Os.Args 获取命令行参数 func main() { //os.Args是一个[]string if len(os.Args) > 0 { for index, arg := range os.Args { fmt.Printf("args[%d]=%v\n", index, arg) } } } 执行./main.exe -name 张三 -age 30输出,就是一个按空格分隔的切片 args[0]=D:\wnmp\www\go\silver\databaseView\main.exe args[1]=-name args[2]=张三 args[3]=-age args[4]=30 Os.Getenv 获取环境遍历的值 config := os.Getenv("path") fmt.Println(config) 删除文件或者目录 _ = os.RemoveAll("./backup") // 删除目录 _ = os.Remove("./backup.zip") // 删除文件 读取文件内容 content, err := os.ReadFile("./cfg.json") 创建目录 err = os.Mkdir("./backup", os.ModePerm) 创建文件 stdout, err := os.OpenFile("./backup/databases_backup.sql", os.O_CREATE|os.O_WRONLY, 0600) if err != nil { err = fmt.Errorf("创建./backup/databases_backup.sql文件失败,Err%s", err.Error()) return } defer stdout.Close()
2022年07月25日
305 阅读
0 评论
0 点赞
2022-07-25
Go语言标准库之fmt包的深度使用 - 详细指南
概述 fmt 包实现了类似 C 语言 printf 和 scanf 的格式化。 主要分为两个部分: 向外输出内容 获取输入内容 向外输出 Print 系列函数 功能:将内容输出到系统的标准输出 主要有以下三个: Print 函数直接输出内容 Printf 函数支持格式化输出字符串 Println 函数会在输出内容的结尾添加一个换行符 func main() { fmt.Print("在终端打印该信息。") name := "沙河小王子" fmt.Printf("我是:%s\n", name) fmt.Println("在终端打印单独一行显示") } Fprint 系列函数 主要功能: 将内容输出到一个 io.Writer 接口类型的变量 w 中,只要满足 io.Writer 接口的类型都支持写入 我们通常用这个函数往文件中写入内容 和 Print 的一样,也有三个函数: Fprint Fprintf Fprintln func main() { // 向标准输出写入内容 fmt.Fprintln(os.Stdout, "向标准输出写入内容") // 向打开的文件句柄中写入内容 fileObj, err := os.OpenFile("./xx.txt", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) if err != nil { fmt.Println("打开文件出错,err:", err) return } name := "沙河小王子" fmt.Fprintf(fileObj, "往文件中写如信息:%s", name) } Sprint 系列函数 主要功能: 把传入的数据生成并返回一个字符串 也就说,返回一个格式化的字符串给变量 三个函数: Sprint Sprintf Sprintln func main() { s1 := fmt.Sprint("沙河小王子") name := "沙河小王子" age := 18 s2 := fmt.Sprintf("name:%s,age:%d", name, age) s3 := fmt.Sprintln("沙河小王子") // 沙河小王子 name:沙河小王子,age:18 沙河小王子 fmt.Println(s1, s2, s3) } 格式化占位符 *printf 系列函数都支持 format 格式化参数 %v,值的默认格式表示 %+v,类似 %v,但输出结构体时会添加字段名 %#v,值的 Go 语法表示 %T,打印值的类型 %%,百分号
2022年07月25日
221 阅读
0 评论
0 点赞
2022-07-04
Go语言标准库之时间处理 - Time
如何获取当前时间信息? 获取当前时间对象信息 now := time.Now() //获取当前时间 通过时间戳解析成时间对象 timeObj := time.Unix(1669449581, 0) 通过时间文本解析成时间对象 // 加载时区 loc, err := time.LoadLocation("Asia/Shanghai") if err != nil { fmt.Println(err) return } // 按照指定时区和指定格式解析字符串时间,如果不需要指定时区的话直接用Parse() timeObj, err := time.ParseInLocation("2006/01/02 15:04:05", "2019/08/04 14:15:20", loc) if err != nil { fmt.Println(err) return } 时间对象的一些常用的方法? 获取当前时间对象上的年月日 // 年月日信息 year := now.Year() //2022 month := now.Month() //July,输出了英文 day := now.Day() //28 hour := now.Hour() //17 minute := now.Minute() //1,没有占位 second := now.Second() //5,没有占位 获取时间对象上的时间戳 // 时间戳信息 timestamp1 := now.Unix() //时间戳 timestamp2 := now.UnixNano() //纳秒时间戳 时间对象格式化输出 // 格式化输出 fmt.Println(now.Format("2006-01-02 15:04:05")) // 24小时制 2022-11-26 15:59:41 fmt.Println(now.Format("2006年01月02 15点04分05秒")) // 24小时制 2022年11月26 15点59分41秒 fmt.Println(now.Format("2006-01-02 03:04:05")) // 12小时制 2022-11-26 03:59:41 如何阻塞延迟? 主要是用 time 包的 Sleep 方法 time.Sleep(time.Millisecond * 1000) time.Sleep(time.Second) 如何输出两个时间的间隔? 可以通过 since 方法来实现 start := time.Now() time.Sleep(time.Second * 2) end := time.Since(start) fmt.Println("耗时:", end.String()) // 输出2s 还可以通过 sub 方法计算两个时间的间隔 now := time.Now() later := now.Add(time.Hour) // 当前时间加1小时后的时间 before := later.Sub(now) fmt.Println(before) // 1h0m0s 如何对时间进行加减? 使用 time 包的 Add 方法,可以增加制定时间,并且返回新的时间对象 timeObj := time.Now() later := timeObj.Add(time.Hour) fmt.Println(later.Format("2006-01-02 15:04:05")) 如何实现定时器操作? 使用 time.Tick(时间间隔) 来设置定时器,定时器的本质上是一个通道(channel) func main() { ticker := time.Tick(time.Second) //定义一个1秒间隔的定时器 for i := range ticker { fmt.Println(i) //每秒都会执行的任务 } } 获取当地时间 ime.Now().Local() 时间的其他操作? 判断两个时间对象是否相等 Equal 方法 判断一个时间是否在另外一个时间之前 before 方法 判断一个时间是否在另外一个时间之后 after 方法 如何构造时间 startTime := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
2022年07月04日
246 阅读
0 评论
0 点赞