Go中的 struct:
可将类型分为命名和未命名两大类。命名类型包括 bool、int、string 等,而 array、slice、map 等和具体元素类型、长度等有关,属于未命名类型。
具有相同声明的未命名类型被视为同一类型。
~~~
• 具有相同基类型的指针。
• 具有相同元素类型和长度的 array。
• 具有相同元素类型的 slice。
• 具有相同键值类型的 map。
• 具有相同元素类型和传送方向的 channel。
• 具有相同字段序列 (字段名、类型、标签、顺序) 的匿名 struct。
• 签名相同 (参数和返回值,不包括参数名称) 的 function。
• 方法集相同 ( 方法名、方法签名相同,和次序无关) 的 interface。
~~~
struct 特点:
~~~
1. 用来自定义复杂数据结构
2. struct里面可以包含多个字段(属性)
3. struct类型可以定义方法,注意和函数的区分
4. struct类型是值类型
5. struct类型可以嵌套
6. Go语言没有class类型,只有struct类型
7. 结构体是用户单独定义的类型,不能和其他类型进行强制转换
8. golang中的struct没有构造函数,一般可以使用工厂模式来解决这个问题。
9. 我们可以为struct中的每个字段,写上一个tag。这个tag可以通过反射的机制获取到,最常用的场景就是json序列化和反序列化。
~~~
概述
与C语言 struct 一样,与java/php等class类似,在Go中,用于扩展类型,面向对象编程(本部分暂未做详细介绍)等
可用 type 在全局或函数内定义新类型。
新类型声明格式:(是类型的组合)
~~~
package main
import (
"fmt"
"reflect"
)
type bigint int64
func main() {
var x bigint = 100
fmt.Printf("x 的值是:%v\n", x)
fmt.Printf("x 的类型是:%v\n", reflect.TypeOf(x))
type smallint int8
var y smallint = 1
fmt.Printf("y 的值是:%v\n", y)
fmt.Printf("y 的类型是:%v\n", reflect.TypeOf(y))
}
~~~
输出结果:
~~~
x 的值是:100
x 的类型是:main.bigint
y 的值是:1
y 的类型是:main.smallint
~~~
新类型不是原类型的别名,除拥有相同数据存储结构外,它们之间没有任何关系,不会持有原类型任何信息。除非目标类型是未命名类型,否则必须显式转换。
~~~
package main
import (
"fmt"
"reflect"
)
type bigint int64
type myslice []int
func main() {
x := 1234
var b bigint = bigint(x) // 必须显式转换,除非是常量。
var b2 int64 = int64(b)
fmt.Printf("b2 的值是:%v , b2 的类型是:%v\n", b2, reflect.TypeOf(b2))
var s myslice = []int{1, 2, 3} // 未命名类型,隐式转换。
var s2 []int = s
fmt.Printf("s2 的值是:%v , s2 的类型是:%v\n", s2, reflect.TypeOf(s2))
}
~~~
输出结果:
~~~
b2 的值是:1234 , b2 的类型是:int64
s2 的值是:[1 2 3] , s2 的类型是:[]int
~~~
struct声明及初始化:
声明:
~~~
type typeName struct {
//...
}
~~~
~~~
package main
type global struct{}
func main() {
type local struct{}
}
~~~
初始化:
方法有几种:
~~~
package main
import (
"fmt"
)
type Test struct {
int
string
}
var a Test
func main() {
b := new(Test) //同 var b *Test = new(Test)
c := Test{1, "c"}
d := Test{}
e := &Test{}
f := &Test{2, "f"} //同 var d *Test = &Test{2, "f"}
fmt.Println(a, b, c, d, e, f)
// 注: a b c d 返回 Test 类型变量;e f 返回 *Test 类型变量;若无初始化值,则默认为零值
}
~~~
输出结果:
~~~
{0 } &{0 } {1 c} {0 } &{0 } &{2 f}
~~~
初始化值可以分为两种:
~~~
a. 有序: typeName{value1, value2, ...} 必须一一对应
b. 无序: typeName{field1:value1, field2:value2, ...} 可初始化部分值
~~~
例:
~~~
package main
import (
"fmt"
)
func main() {
type Person struct {
name string
age int
}
p := Person{"James", 23} //有序
// 顺序初始化必须包含全部字段,否则会出错。
// p1 := Person{"James"} // Error: too few values in struct initializer
p2 := Person{age: 23} //无序
fmt.Println(p)
fmt.Println(p2)
}
~~~
输出结果:
~~~
{James 23}
{ 23}
~~~
操作
~~~
声明的struct与普通类型一样
访问结构体中的一个变量名, 用 "." 来连接:
varName.field 或 (*varName).field
如操作上面 Person 结构体中的 age : p.age = 35
也可以作为函数中的参数,返回值类型
~~~
代码:
~~~
package main
import "fmt"
//1. 声明一个自定义类型名为 Person 的结构体
type Person struct {
name string
age int
}
func main() {
//2. 初始化
var p1 Person
p2 := Person{}
p3 := Person{"James", 23}
p4 := Person{age: 23}
fmt.Println(p1, p2, p3, p4)
p5 := new(Person)
p6 := &Person{}
p7 := &Person{"James", 23}
p8 := &Person{age: 23}
fmt.Println(p5, p6, p7, p8)
//3. 操作
p1.age = 50
p2.age = 25
if compareAge(p1, p2) {
fmt.Println("p1 is older than p2")
} else {
fmt.Println("p2 is older than p1")
}
}
func compareAge(p1, p2 Person) bool {
if p1.age > p2.age {
return true
}
return false
}
~~~
输出:
~~~
{ 0} { 0} {James 23} { 23}
&{ 0} &{ 0} &{James 23} &{ 23}
p1 is older than p2
~~~
struct的内存布局
struct中的所有字段在内存是连续的,布局如下:
![](https://box.kancloud.cn/d8cd52d7dcb76134bbfda7f1b789af04_816x367.png)
匿名字段:
声明一个 struct1 可以包含已经存在的 struct2 或者go语言中内置类型作为内置字段,称为匿名字段,即只写了 typeName,无 varName,但是 typeName 不能重复。
匿名字段与面向对象程序语言中的继承
声明及初始化:
~~~
package main
import (
"fmt"
)
type Person struct {
name string
age int
addr string
}
type Employee struct {
Person //匿名字段
salary int
int //用内置类型作为匿名字段
addr string //类似于重载
}
func main() {
em1 := Employee{Person{"rain", 23, "qingyangqu"}, 5000, 100, "gaoxingqu"}
fmt.Println(em1)
// var em2 Person = em1
// Error: cannot use em1 (type Employee) as type Person in assignment (没有继承, 然也不会有多态)
var em2 Person = em1.Person // 同类型拷贝。
fmt.Println(em2)
}
~~~
输出结果:
~~~
{{Murphy 23 帝都} 5000 100 北京}
{Murphy 23 帝都}
~~~
操作
~~~
访问方式也是通过 "." 来连接
相同字段采用最外层优先访问,类似于重载
em1.addr 访问的是 Employee 中最外层的 addr
em1.Person.addr 访问的是 Employee 中 Person 中的 addr
~~~
~~~
package main
import "fmt"
type Person struct {
name string
age int
addr string
}
type Employee struct {
Person //匿名字段
salary int
int //用内置类型作为匿名字段
addr string //类似于重载
}
func main() {
/*
var em1 Employee = Employee{}
em1.Person = Person{"rain", 23, "帝都"}
em1.salary = 5000
em1.int = 100 //使用时注意其意义,此处无
em1.addr = "北京"
*/
//em1 := Employee{Person{"rain", 23, "帝都"}, 5000, 100, "北京"}
//初始化方式不一样,但是结果一样
em1 := Employee{Person: Person{"Murphy", 23, "帝都"}, salary: 5000, int: 100, addr: "北京"}
fmt.Println(em1)
fmt.Println("live addr(em1.addr) = ", em1.addr)
fmt.Println("work addr(em1.Person.addr) = ", em1.Person.addr)
em1.int = 200 //修改匿名字段的值
}
~~~
输出:
~~~
{{Murphy 23 帝都} 5000 100 北京}
live addr(em1.addr) = 北京
work addr(em1.Person.addr) = 帝都
~~~
空结构 "节省" 内存, 如用来实现 set 数据结构,或者实现没有 "状态" 只有方法的 "静态类"。
~~~
package main
func main() {
var null struct{}
set := make(map[string]struct{})
set["a"] = null
}
~~~
不能同时嵌入某一类型和其指针类型,因为它们名字相同。
~~~
package main
type Resource struct {
id int
}
type User struct {
*Resource
// Resource // Error: duplicate field Resource
name string
}
func main() {
u := User{
&Resource{1},
"Administrator",
}
println(u.id)
println(u.Resource.id)
}
~~~
输出结果:
~~~
1
1
~~~
struct与tag应用
在处理json格式字符串的时候,经常会看到声明struct结构的时候,属性的右侧还有小米点括起来的内容。形如:
~~~
type User struct {
UserId int `json:"user_id" bson:"user_id"`
UserName string `json:"user_name" bson:"user_name"`
}
~~~
这个小米点里的内容是用来干什么的呢?
struct成员变量标签(Tag)说明
要比较详细的了解这个,要先了解一下golang的基础,在golang中,命名都是推荐都是用驼峰方式,并且在首字母大小写有特殊的语法含义:包外无法引用。但是由经常需要和其它的系统进行数据交互,例如转成json格式,存储到mongodb啊等等。这个时候如果用属性名来作为键值可能不一定会符合项目要求。
所以呢就多了小米点的内容,在golang中叫标签(Tag),在转换成其它数据格式的时候,会使用其中特定的字段作为键值。例如上例在转成json格式:
~~~
package main
import (
"encoding/json"
"fmt"
)
type User struct {
UserId int
UserName string
}
type UserTag struct {
UserId int `json:"user_id" bson:"user_id"`
UserName string `json:"user_name" bson:"user_name"`
}
func main() {
u := &User{UserId: 1, UserName: "Murphy"}
j, _ := json.Marshal(u)
fmt.Printf("struct User echo : %v\n", string(j))
u_tag := &UserTag{UserId: 1, UserName: "Murphy"}
j_tag, _ := json.Marshal(u_tag)
fmt.Printf("struct UserTag echo : %v\n", string(j_tag))
}
~~~
输出结果:
~~~
struct User echo : {"UserId":1,"UserName":"Murphy"}
struct UserTag echo : {"user_id":1,"user_name":"Murphy"}
~~~
可以看到直接用struct的属性名做键值。
其中还有一个bson的声明,这个是用在将数据存储到mongodb使用的。
标签是类型的组成部分。
~~~
package main
var a struct {
x int `a`
}
var b struct {
x int `ab`
}
func main() {
a = b
}
~~~
输出结果:
~~~
./main.go:11:4: cannot use b (type struct { x int "ab" }) as type struct { x int "a" } in assignment
~~~
~~~
package main
var u1 struct {
name string "username"
}
var u2 struct{ name string }
func main() {
u1 = u2
}
~~~
输出结果:
~~~
./main.go:9:5: cannot use u2 (type struct { name string }) as type struct { name string "username" } in assignment
~~~
struct成员变量标签(Tag)获取
那么当我们需要自己封装一些操作,需要用到Tag中的内容时,咋样去获取呢?这边可以使用反射包(reflect)中的方法来获取:
~~~
t := reflect.TypeOf(u)
field := t.Elem().Field(0)
fmt.Println(field.Tag.Get("json"))
fmt.Println(field.Tag.Get("bson"))
~~~
完整代码如下:
~~~
package main
import (
"encoding/json"
"fmt"
"reflect"
)
func main() {
type User struct {
//多个Key使用空格进行分开,然后使用Get方法获取不同Key的值。
UserId int `json:"user_json_id" xml:"user_xml_id"`
UserName string `json:"user_json_name" xml:"user_xml_name"`
}
// 输出json格式
u := &User{UserId: 1, UserName: "tony"}
j, _ := json.Marshal(u)
fmt.Println(string(j))
// 获取tag中的内容
t := reflect.TypeOf(u)
fmt.Println(t)
field0 := t.Elem().Field(0)
fmt.Println(field0.Tag.Get("json"))
fmt.Println(field0.Tag.Get("xml"))
field1 := t.Elem().Field(1)
fmt.Println(field1.Tag.Get("json"))
fmt.Println(field1.Tag.Get("xml"))
}
~~~
输出结果:
~~~
{"user_json_id":1,"user_json_name":"tony"}
*main.User
user_json_id
user_xml_id
user_json_name
user_xml_name
~~~
有意思的struct大小
我们定义一个struct,这个struct有3个字段,它们的类型有byte,int32以及int64,但是这三个字段的顺序我们可以任意排列,那么根据顺序的不同,一共有6种组合。
~~~
type user1 struct {
b byte
i int32
j int64
}
type user2 struct {
b byte
j int64
i int32
}
type user3 struct {
i int32
b byte
j int64
}
type user4 struct {
i int32
j int64
b byte
}
type user5 struct {
j int64
b byte
i int32
}
type user6 struct {
j int64
i int32
b byte
}
~~~
根据这6种组合,定义了6个struct,分别位user1,user2,…,user6,那么现在大家猜测一下,这6种类型的struct占用的内存是多少,就是unsafe.Sizeof()的值。
大家可能猜测1+4+8=13,因为byte的大小为1,int32大小为4,int64大小为8,而struct其实就是一个字段的组合,所以猜测struct大小为字段大小之和也很正常。
但是,但是,我可以明确的说,这是错误的。
为什么是错误的,因为有内存对齐存在,编译器使用了内存对齐,那么最后的大小结果就不一样了。现在我们正式验证下,这几种struct的值。
~~~
package main
import (
"fmt"
"unsafe"
)
type user1 struct {
b byte
i int32
j int64
}
type user2 struct {
b byte
j int64
i int32
}
type user3 struct {
i int32
b byte
j int64
}
type user4 struct {
i int32
j int64
b byte
}
type user5 struct {
j int64
b byte
i int32
}
type user6 struct {
j int64
i int32
b byte
}
func main() {
var u1 user1
var u2 user2
var u3 user3
var u4 user4
var u5 user5
var u6 user6
fmt.Println("u1 size is ", unsafe.Sizeof(u1))
fmt.Println("u2 size is ", unsafe.Sizeof(u2))
fmt.Println("u3 size is ", unsafe.Sizeof(u3))
fmt.Println("u4 size is ", unsafe.Sizeof(u4))
fmt.Println("u5 size is ", unsafe.Sizeof(u5))
fmt.Println("u6 size is ", unsafe.Sizeof(u6))
}
~~~
输出结果:
~~~
u1 size is 16
u2 size is 24
u3 size is 16
u4 size is 24
u5 size is 16
u6 size is 16
~~~
结果出来了(我的电脑的结果,Mac64位,你的可能不一样),4个16字节,2个24字节,既不一样,又不相同,这说明:
内存对齐影响struct的大小
struct的字段顺序影响struct的大小
综合以上两点,我们可以得知,不同的字段顺序,最终决定struct的内存大小,所以有时候合理的字段顺序可以减少内存的开销。
内存对齐会影响struct的内存占用大小,现在我们就详细分析下,为什么字段定义的顺序不同会导致struct的内存占用不一样。
在分析之前,我们先看下内存对齐的规则:
对于具体类型来说,对齐值=min(编译器默认对齐值,类型大小Sizeof长度)。也就是在默认设置的对齐值和类型的内存占用大小之间,取最小值为该类型的对齐值。我的电脑默认是8,所以最大值不会超过8.
struct在每个字段都内存对齐之后,其本身也要进行对齐,对齐值=min(默认对齐值,字段最大类型长度)。这条也很好理解,struct的所有字段中,最大的那个类型的长度以及默认对齐值之间,取最小的那个。
以上这两条规则要好好理解,理解明白了才可以分析下面的struct结构体。在这里再次提醒,对齐值也叫对齐系数、对齐倍数,对齐模数。这就是说,每个字段在内存中的偏移量是对齐值的倍数即可。
我们知道byte,int32,int64的对齐值分别为1,4,8,占用内存大小也是1,4,8。那么对于第一个structuser1,它的字段顺序是byte、int32、int64,我们先使用第1条内存对齐规则进行内存对齐,其内存结构如下。
bxxx|iiii|jjjj|jjjj
user1类型,第1个字段byte,对齐值1,大小1,所以放在内存布局中的第1位。
第2个字段int32,对齐值4,大小4,所以它的内存偏移值必须是4的倍数,在当前的user1中,就不能从第2位开始了,必须从第5位开始,也就是偏移量为4。第2,3,4位由编译器进行填充,一般为值0,也称之为内存空洞。所以第5位到第8位为第2个字段i。
第3字段,对齐值为8,大小也是8。因为user1前两个字段已经排到了第8位,所以下一位的偏移量正好是8,是第3个字段对齐值的倍数,不用填充,可以直接排列第3个字段,也就是从第9位到第16位为第3个字段j。
现在第一条内存对齐规则后,内存长度已经为16个字节,我们开始使用内存的第2条规则进行对齐。根据第二条规则,默认对齐值8,字段中最大类型长度也是8,所以求出结构体的对齐值位8,我们目前的内存长度为16,是8的倍数,已经实现了对齐。
所以到此为止,结构体user1的内存占用大小为16字节。
现在我们再分析一个user2类型,它的大小是24,只是调换了一下字段i和j的顺序,就多占用了8个字节,我们看看为什么?还是先使用我们的内存第1条规则分析。
bxxx|xxxx|jjjj|jjjj|iiii
按对齐值和其占用的大小,第1个字段b偏移量为0,占用1个字节,放在第1位。
第2个字段j,是int64,对齐值和大小都是8,所以要从偏移量8开始,也就是第9到16位为j,这也就意味着第2到8位被编译器填充。
目前整个内存布局已经偏移了16位,正好是第3个字段i的对齐值4的倍数,所以不用填充,可以直接排列,第17到20位为i。
现在所有字段对齐好了,整个内存大小为1+7+8+4=20个字节,我们开始使用内存对齐的第2条规则,也就是结构体的对齐,通过默认对齐值和最大的字段大小,求出结构体的对齐值为8。
现在我们的整个内存布局大小为20,不是8的倍数,所以我们需要进行内存填充,补足到8的倍数,最小的就是24,所以对齐后整个内存布局为
bxxx|xxxx|jjjj|jjjj|iiii|xxxx
所以这也是为什么我们最终获得的user2的大小为24的原因。
基于以上办法,我们可以得出其他几个struct的内存布局。
user3
iiii|bxxx|jjjj|jjjj
user4
iiii|xxxx|jjjj|jjjj|bxxx|xxxx
user5
jjjj|jjjj|bxxx|iiii
user6
jjjj|jjjj|iiii|bxxx
以上给出了答案,推到过程大家可以参考user1和user2试试。
- 序言
- 目录
- 环境搭建
- Linux搭建golang环境
- Windows搭建golang环境
- Mac搭建golang环境
- 介绍
- 1.Go语言的主要特征
- 2.golang内置类型和函数
- 3.init函数和main函数
- 4.包
- 1.工作空间
- 2.源文件
- 3.包结构
- 4.文档
- 5.编写 Hello World
- 6.Go语言 “ _ ”(下划线)
- 7.运算符
- 8.命令
- 类型
- 1.变量
- 2.常量
- 3.基本类型
- 1.基本类型介绍
- 2.字符串String
- 3.数组Array
- 4.类型转换
- 4.引用类型
- 1.引用类型介绍
- 2.切片Slice
- 3.容器Map
- 4.管道Channel
- 5.指针
- 6.自定义类型Struct
- 编码格式转换
- 流程控制
- 1.条件语句(if)
- 2.条件语句 (switch)
- 3.条件语句 (select)
- 4.循环语句 (for)
- 5.循环语句 (range)
- 6.循环控制Goto、Break、Continue
- 函数
- 1.函数定义
- 2.参数
- 3.返回值
- 4.匿名函数
- 5.闭包、递归
- 6.延迟调用 (defer)
- 7.异常处理
- 8.单元测试
- 压力测试
- 方法
- 1.方法定义
- 2.匿名字段
- 3.方法集
- 4.表达式
- 5.自定义error
- 接口
- 1.接口定义
- 2.执行机制
- 3.接口转换
- 4.接口技巧
- 面向对象特性
- 并发
- 1.并发介绍
- 2.Goroutine
- 3.Chan
- 4.WaitGroup
- 5.Context
- 应用
- 反射reflection
- 1.获取基本类型
- 2.获取结构体
- 3.Elem反射操作基本类型
- 4.反射调用结构体方法
- 5.Elem反射操作结构体
- 6.Elem反射获取tag
- 7.应用
- json协议
- 1.结构体转json
- 2.map转json
- 3.int转json
- 4.slice转json
- 5.json反序列化为结构体
- 6.json反序列化为map
- 终端读取
- 1.键盘(控制台)输入fmt
- 2.命令行参数os.Args
- 3.命令行参数flag
- 文件操作
- 1.文件创建
- 2.文件写入
- 3.文件读取
- 4.文件删除
- 5.压缩文件读写
- 6.判断文件或文件夹是否存在
- 7.从一个文件拷贝到另一个文件
- 8.写入内容到Excel
- 9.日志(log)文件
- server服务
- 1.服务端
- 2.客户端
- 3.tcp获取网页数据
- 4.http初识-浏览器访问服务器
- 5.客户端访问服务器
- 6.访问延迟处理
- 7.form表单提交
- web模板
- 1.渲染终端
- 2.渲染浏览器
- 3.渲染存储文件
- 4.自定义io.Writer渲染
- 5.模板语法
- 时间处理
- 1.格式化
- 2.运行时间
- 3.定时器
- 锁机制
- 互斥锁
- 读写锁
- 性能比较
- sync.Map
- 原子操作
- 1.原子增(减)值
- 2.比较并交换
- 3.导入、导出、交换
- 加密解密
- 1.md5
- 2.base64
- 3.sha
- 4.hmac
- 常用算法
- 1.冒泡排序
- 2.选择排序
- 3.快速排序
- 4.插入排序
- 5.睡眠排序
- 设计模式
- 创建型模式
- 单例模式
- 抽象工厂模式
- 工厂方法模式
- 原型模式
- 结构型模式
- 适配器模式
- 桥接模式
- 合成/组合模式
- 装饰模式
- 外观模式
- 享元模式
- 代理模式
- 行为性模式
- 职责链模式
- 命令模式
- 解释器模式
- 迭代器模式
- 中介者模式
- 备忘录模式
- 观察者模式
- 状态模式
- 策略模式
- 模板模式
- 访问者模式
- 数据库操作
- golang操作MySQL
- 1.mysql使用
- 2.insert操作
- 3.select 操作
- 4.update 操作
- 5.delete 操作
- 6.MySQL事务
- golang操作Redis
- 1.redis介绍
- 2.golang链接redis
- 3.String类型 Set、Get操作
- 4.String 批量操作
- 5.设置过期时间
- 6.list队列操作
- 7.Hash表
- 8.Redis连接池
- golang操作ETCD
- 1.etcd介绍
- 2.链接etcd
- 3.etcd存取
- 4.etcd监听Watch
- golang操作kafka
- 1.kafka介绍
- 2.写入kafka
- 3.kafka消费
- golang操作ElasticSearch
- 1.ElasticSearch介绍
- 2.kibana介绍
- 3.写入ElasticSearch
- NSQ
- 安装
- 生产者
- 消费者
- beego框架
- 1.beego框架环境搭建
- 2.参数配置
- 1.默认参数
- 2.自定义配置
- 3.config包使用
- 3.路由设置
- 1.自动匹配
- 2.固定路由
- 3.正则路由
- 4.注解路由
- 5.namespace
- 4.多种数据格式输出
- 1.直接输出字符串
- 2.模板数据输出
- 3.json格式数据输出
- 4.xml格式数据输出
- 5.jsonp调用
- 5.模板处理
- 1.模板语法
- 2.基本函数
- 3.模板函数
- 6.请求处理
- 1.GET请求
- 2.POST请求
- 3.文件上传
- 7.表单验证
- 1.表单验证
- 2.定制错误信息
- 3.struct tag 验证
- 4.XSRF过滤
- 8.静态文件处理
- 1.layout设计
- 9.日志处理
- 1.日志处理
- 2.logs 模块
- 10.会话控制
- 1.会话控制
- 2.session 包使用
- 11.ORM 使用
- 1.链接数据库
- 2. CRUD 操作
- 3.原生 SQL 操作
- 4.构造查询
- 5.事务处理
- 6.自动建表
- 12.beego 验证码
- 1.验证码插件
- 2.验证码使用
- beego admin
- 1.admin安装
- 2.admin开发
- beego 热升级
- gin框架
- 安装使用
- 项目
- 秒杀项目
- 日志收集
- 面试题
- 面试题一
- 面试题二
- 错题集
- Go语言陷阱和常见错误
- 常见语法错误
- 初级
- 中级
- 高级
- Go高级应用
- goim
- goim 启动流程
- goim 工作流程
- goim 结构体
- gopush
- gopush工作流程
- gopush启动流程
- gopush业务流程
- gopush应用
- gopush新添功能
- rpc
- HTTP RPC
- TCP RPC
- JSON RPC
- 常见RPC开源框架
- pprof
- pprof介绍
- pprof应用
- 封装 websocket
- zookeeper
- 基本操作测试
- 简单的分布式server
- Zookeeper命令行使用
- cgo
- Go语言 demo
- 用Go语言计算一个人的年龄,生肖,星座
- 超简易Go语言实现的留言板代码
- 信号处理模块,可用于在线加载配置,配置动态加载的信号为SIGHUP
- 阳历和阴历相互转化的工具类 golang版本
- 错误总结