Go语言配置文件解析器,类似于Windows下的INI文件.
最后更新于:2022-04-01 11:59:37
# config
Package config is a Configuration file parser for INI format
包 config 是一个简洁方的,支持注释的Go语言配置文件解析器,类似于Windows下的INI文件.
配置文件形式为`[section]` 的段构成, 内部使用 `name=value`键值对
如果为指定段节点,则默认放入名为`[default]`的段当中.
“#”为注释的开头,可以放置于任意的单独一行中.
## 安装
~~~
go get github.com/lxmgo/config
~~~
## 示例
请查看 [conf.ini](http://blog.csdn.net/liuxinmingcode/article/details/testdata/testini.ini) 文件作为使用示例
## 使用规范
示例配置文件:
~~~
[DEFAULT]
host = act.wiki
port = 8080
f64 = 64.1
[mysql]
host = 127.0.0.1
[mongodb]
host = 127.0.0.2
[redis]
host = 127.0.0.3
push_key = key1,key2,key3,...
[memcache]
host = 127.0.0.4
~~~
加载配置文件:
~~~
config, err := NewConfig("testdata/testini.ini")
c.Int("port")
// result is int 8080
c.Int64("port")
// result is int64 8080
c.Float64("f64")
// result is float64 64.1
c.String("host")
// result is string "act.wiki"
c.String("mysql.host")
// result is string "127.0.0.1"
c.String("redis.host")
// result is string "127.0.0.3"
c.Strings("redis.key")
// result is []string{"key1","key2","key3",...}
~~~
## config APIS:
~~~
String(key string) string
Strings(key string) []string
Bool(key string) (bool, error)
Int(key string) (int, error)
Int64(key string) (int64, error)
Float64(key string) (float64,error)
Set(key string, value string) error
~~~
## 更多信息
所有字符解析均使用小写的!
## 源码
[https://github.com/lxmgo/config](https://github.com/lxmgo/config)
## Go交流群
185521558
Golang之反射reflect包
最后更新于:2022-04-01 11:59:34
# 反射规则
在计算机科学领域,反射是指一类应用,它们能够自描述和自控制。也就是说,这类应用通过采用某种机制来实现对自己行为的描述(self-representation)和监测(examination),并能根据自身行为的状态和结果,调整或修改应用所描述行为的状态和相关的语义。
> 每个语言的反射模型都不同(同时许多语言根本不支持反射)。
Go语言实现了反射,所谓反射就是动态运行时的状态。
我们用到的包是reflect。
# 从接口值到反射对象的反射
> 反射仅是一种用来检测存储在接口变量内部(值value,实例类型concret type)pair对的一种机制。
在reflect反射包中有两种类型让我们可以访问接口变量内容
- reflect.ValueOf()
~~~
// ValueOf returns a new Value initialized to the concrete value
// stored in the interface i. ValueOf(nil) returns the zero Value.
func ValueOf(i interface{}) Value {...}
~~~
- reflect.TypeOf()
~~~
// TypeOf returns the reflection Type that represents the dynamic type of i.
// If i is a nil interface value, TypeOf returns nil.
func TypeOf(i interface{}) Type {...}
~~~
一个简单的案例:
~~~
package main
import (
"reflect"
"fmt"
)
type ControllerInterface interface {
Init(action string, method string)
}
type Controller struct {
Action string
Method string
Tag string `json:"tag"`
}
func (c *Controller) Init(action string, method string){
c.Action = action
c.Method = method
}
func main(){
//初始化
runController := &Controller{
Action:"Run1",
Method:"GET",
}
//Controller实现了ControllerInterface方法,因此它就实现了ControllerInterface接口
var i ControllerInterface
i = runController
// 得到实际的值,通过v我们获取存储在里面的值,还可以去改变值
v := reflect.ValueOf(i)
fmt.Println("value:",v)
// 得到类型的元数据,通过t我们能获取类型定义里面的所有元素
t := reflect.TypeOf(i)
fmt.Println("type:",t)
// 转化为reflect对象之后我们就可以进行一些操作了,也就是将reflect对象转化成相应的值
......
}
~~~
> 打印结果:
value: &{Run1 GET}
type: *main.Controller
- v 是i接口为指向main包下struct Controller的指针(runController)目前的所存储值
- t 是i接口为指向main包下struct Controller的指针类型。
### 转换reflect对象之后操作
### 接口i所指向的对象的值操作
~~~
// Elem返回值v包含的接口
controllerValue := v.Elem()
fmt.Println("controllerType(reflect.Value):",controllerType)
//获取存储在第一个字段里面的值
fmt.Println("Action:", controllerValue.Field(0).String())
~~~
> controllerType(reflect.Value): {Run1 GET }
Action: Run1
### 接口i所指向的对象的类型操作
获取定义在struct里面的标签
~~~
// Elem返回类型的元素类型。
controllerType := t.Elem()
tag := controllerType.Field(2).Tag //Field(第几个字段,index从0开始)
fmt.Println("Tag:", tag)
~~~
> Tag: json:”tag”
### 通过reflect.TypeOf(i)获取对象方法的信息
~~~
method, _ := t.MethodByName("Init")
fmt.Println(method)
~~~
~~~
打印结果:
{Init func(*main.Controller, string, string) <func(*main.Controller, string, string) Value> 0}
~~~
它返回了一个对象方法的信息集合即Method,关于Method结构定义在reflect/type.go下具体为:
~~~
// Method represents a single method.
type Method struct {
// Name is the method name.
// PkgPath is the package path that qualifies a lower case (unexported)
// method name. It is empty for upper case (exported) method names.
// The combination of PkgPath and Name uniquely identifies a method
// in a method set.
// See http://golang.org/ref/spec#Uniqueness_of_identifiers
Name string
PkgPath string
Type Type // method type
Func Value // func with receiver as first argument
Index int // index for Type.Method
}
~~~
### 通过reflect.ValueOf(i)获取对象方法的信息
~~~
vMethod := v.MethodByName("Init")
fmt.Println(vMethod)
~~~
### 通过reflect.ValueOf(i)调用方法
~~~
package main
import (
"reflect"
"fmt"
)
type ControllerInterface interface {
Init(action string, method string)
}
type Controller struct {
Action string
Method string
Tag string `json:"tag"`
}
func (c *Controller) Init(action string, method string){
c.Action = action
c.Method = method
//增加fmt打印,便于看是否调用
fmt.Println("Init() is run.")
fmt.Println("c:",c)
}
//增加一个无参数的Func
func (c *Controller) Test(){
fmt.Println("Test() is run.")
}
func main(){
//初始化
runController := &Controller{
Action:"Run1",
Method:"GET",
}
//Controller实现了ControllerInterface方法,因此它就实现了ControllerInterface接口
var i ControllerInterface
i = runController
// 得到实际的值,通过v我们获取存储在里面的值,还可以去改变值
v := reflect.ValueOf(i)
fmt.Println("value:",v)
// 有输入参数的方法调用
// 构造输入参数
args1 := []reflect.Value{reflect.ValueOf("Run2"),reflect.ValueOf("POST")}
// 通过v进行调用
v.MethodByName("Init").Call(args1)
// 无输入参数的方法调用
// 构造zero value
args2 := make([]reflect.Value, 0)
// 通过v进行调用
v.MethodByName("Test").Call(args2)
}
~~~
~~~
打印结果:
value: &{Run1 GET }
//有参数的Func
Init() is run.
c: &{Run2 POST }
//无参数的Func
Test() is run.
~~~
# 从反射对象到接口值的反射
# 修改反射对象
最近在写ATC框架,路由第一版构思中用到了反射就整理了下,不过经过几个莫名夜晚纠结,最终第二版构思实践中又把所有反射干掉了(泪…, 讨论群中一致认为反射对性能影响较大,能避免则避免,不能避免就少用。),目前思路和框架已定型,一个轻量级简单的RESTful go 框架 即将发布…
不早了,先到这里吧,米西米西了。空了在补充,还有很多反射内容待进一步完善……
有空的话就把beego框架中用到的反射知识整理分享下,利己利人。
# 参考资料
示例代码:
[https://github.com/lxmgo/learngo/blob/master/reflect/reflect.go](https://github.com/lxmgo/learngo/blob/master/reflect/reflect.go)
Golang之字符串格式化
最后更新于:2022-04-01 11:59:32
# 字符串格式化
~~~
// Go 之 字符串格式化
//
// Copyright (c) 2015 - Batu
//
package main
import (
"fmt"
)
type point struct {
x, y int
}
func main(){
// 格式化整型,使用`%d`是一种
// 标准的以十进制来输出整型的方式
// 有符号十进制整数(int)(%ld、%Ld:长整型数据(long),%hd:输出短整形。)
fmt.Println("=====%d,输出十进制====")
fmt.Printf("%d\n", 110)
// 输出整型的二进制表示方式
fmt.Println("=====%b,输出二进制====")
fmt.Printf("%b\n", 110)
// 输出整型数值所对应的字符(char):一个字节,占8位
// 可参考 ASCII
fmt.Println("=====%c,输出一个值的字符(char)====")
fmt.Printf("%c\n",97)
// 输出一个值的十六进制,每个字符串的字节用两个字符输出
fmt.Println("=====%x,输出一个值的十六进制,每个字符串的字节用两个字符输出====")
fmt.Printf("0x%x\n", 10)
fmt.Printf("%x\n", "abc")
// 输出浮点型数值
fmt.Println("=====%f,输出浮点型数值====")
fmt.Printf("%f\n", 27.89)
// 输出基本的字符串
fmt.Println("=====%s,输出基本字符串====")
fmt.Printf("%s-%s-%s\n","I","am","batu")
// 输出带双引号的字符串
fmt.Println("=====%q,输出带双引号的字符串====")
fmt.Printf("%q\n","string")
// Go提供了几种打印格式,用来格式化一般的Go值
p := point{1, 2}
fmt.Println("=====%p,输出一个指针的值====")
fmt.Printf("%p\n", &p)
fmt.Println("=====%v,输出结构体的对象值====")
fmt.Printf("%v\n", p)
// 如果所格式化的值是一个结构体对象,那么`%+v`的格式化输出
fmt.Println("=====%+v,输出结构体的成员名称和值====")
fmt.Printf("%+v\n", p)
fmt.Println("=====%#v,输出一个值的Go语法表示方式====")
fmt.Printf("%#v\n",p)
fmt.Println("=====%T,输出一个值的数据类型====")
fmt.Printf("%T\n",p)
// 当输出数字的时候,经常需要去控制输出的宽度和精度。
// 可以使用一个位于%后面的数字来控制输出的宽度,默认情况下输出是右对齐的,左边加上空格
fmt.Println("=====控制输出的宽度和精度====")
fmt.Printf("|%5d|%5d|\n", 12, 345)
fmt.Println("=====输出宽度,同时指定浮点数====")
fmt.Printf("|%5.2f|%5.2f|\n", 1.2, 3.45)
fmt.Println("=====左对齐====")
fmt.Printf("|%-5.2f|%-5.2f|\n", 1.2, 3.45)
}
~~~
=====%d,输出十进制====
110
=====%b,输出二进制====
1101110
=====%c,输出一个值的字符(char)====
a
=====%x,输出一个值的十六进制,每个字符串的字节用两个字符输出====
0xa
616263
=====%f,输出浮点型数值====
27.890000
=====%s,输出基本字符串====
I-am-batu
=====%q,输出带双引号的字符串====
“string”
=====%p,输出一个指针的值====
0xc82000a410
=====%v,输出结构体的对象值====
{1 2}
=====%+v,输出结构体的成员名称和值====
{x:1 y:2}
=====%#v,输出一个值的Go语法表示方式====
main.point{x:1, y:2}
=====%T,输出一个值的数据类型====
main.point
=====控制输出的宽度和精度====
| 12| 345|
=====输出宽度,同时指定浮点数====
| 1.20| 3.45|
=====输出宽度,同时指定浮点数====
|1.20 |3.45 |
Golang之bytes.buffer
最后更新于:2022-04-01 11:59:30
> bytes.buffer是一个缓冲byte类型的缓冲器存放着都是byte
Buffer 是 bytes 包中的一个 type Buffer struct{…}
> A buffer is a variable-sized buffer of bytes with Read and Write methods. The zero value for Buffer is an empty buffer ready to use.
(是一个变长的 buffer,具有 Read 和Write 方法。 Buffer 的 零值 是一个 空的 buffer,但是可以使用)
Buffer 就像一个集装箱容器,可以存东西,取东西(存取数据)
- 创建 一个 Buffer (其实底层就是一个 []byte, 字节切片)
- 向其中写入数据 (Write mtheods)
- 从其中读取数据 (Write methods)
# 创建 Buffer缓冲器
~~~
var b bytes.Buffer //直接定义一个 Buffer 变量,而不用初始化
b.Writer([]byte("Hello ")) // 可以直接使用
b1 := new(bytes.Buffer) //直接使用 new 初始化,可以直接使用
// 其它两种定义方式
func NewBuffer(buf []byte) *Buffer
func NewBufferString(s string) *Buffer
~~~
### NewBuffer
~~~
// NewBuffer creates and initializes a new Buffer using buf as its initial
// contents. It is intended to prepare a Buffer to read existing data. It
// can also be used to size the internal buffer for writing. To do that,
// buf should have the desired capacity but a length of zero.
//
// In most cases, new(Buffer) (or just declaring a Buffer variable) is
// sufficient to initialize a Buffer.
func NewBuffer(buf []byte) *Buffer { return &Buffer{buf: buf} }
~~~
- NewBuffer使用buf作为参数初始化Buffer,
- Buffer既可以被读也可以被写
- 如果是读Buffer,buf需填充一定的数据
- 如果是写,buf需有一定的容量(capacity),当然也可以通过new(Buffer)来初始化Buffer。另外一个方法NewBufferString用一个string来初始化可读Buffer,并用string的内容填充Buffer.
~~~
func IntToBytes(n int) []byte {
x := int32(n)
//创建一个内容是[]byte的slice的缓冲器
//与bytes.NewBufferString("")等效
bytesBuffer := bytes.NewBuffer([]byte{})
binary.Write(bytesBuffer, binary.BigEndian, x)
return bytesBuffer.Bytes()
}
~~~
### NewBufferString
- 方法NewBufferString用一个string来初始化可读Buffer,并用string的内容填充Buffer.
- 用法和NewBuffer没有太大区别
~~~
// NewBufferString creates and initializes a new Buffer using string s as its
// initial contents. It is intended to prepare a buffer to read an existing
// string.
//
// In most cases, new(Buffer) (or just declaring a Buffer variable) is
// sufficient to initialize a Buffer.
func NewBufferString(s string) *Buffer {
return &Buffer{buf: []byte(s)}
}
~~~
~~~
func TestBufferString(){
buf1:=bytes.NewBufferString("swift")
buf2:=bytes.NewBuffer([]byte("swift"))
buf3:=bytes.NewBuffer([]byte{'s','w','i','f','t'})
fmt.Println("===========以下buf1,buf2,buf3等效=========")
fmt.Println("buf1:", buf1)
fmt.Println("buf2:", buf2)
fmt.Println("buf3:", buf3)
fmt.Println("===========以下创建空的缓冲器等效=========")
buf4:=bytes.NewBufferString("")
buf5:=bytes.NewBuffer([]byte{})
fmt.Println("buf4:", buf4)
fmt.Println("buf5:", buf5)
}
~~~
输出:
> ===========以下buf1,buf2,buf3等效=========
buf1: swift
buf2: swift
buf3: swift
===========以下创建空的缓冲器等效=========
buf4:
buf5:
# 向 Buffer 中写入数据
### Write
把字节切片 p 写入到buffer中去。
~~~
// Write appends the contents of p to the buffer, growing the buffer as
// needed. The return value n is the length of p; err is always nil. If the
// buffer becomes too large, Write will panic with ErrTooLarge.
func (b *Buffer) Write(p []byte) (n int, err error) {
b.lastRead = opInvalid
m := b.grow(len(p))
return copy(b.buf[m:], p), nil
}
~~~
~~~
fmt.Println("===========以下通过Write把swift写入Learning缓冲器尾部=========")
newBytes := []byte("swift")
//创建一个内容Learning的缓冲器
buf := bytes.NewBuffer([]byte("Learning"))
//打印为Learning
fmt.Println(buf.String())
//将newBytes这个slice写到buf的尾部
buf.Write(newBytes)
fmt.Println(buf.String())
~~~
打印:
> ===========以下通过Write把swift写入Learning缓冲器尾部=========
Learning
Learningswift
### WriteString
使用WriteString方法,将一个字符串放到缓冲器的尾部
~~~
// WriteString appends the contents of s to the buffer, growing the buffer as
// needed. The return value n is the length of s; err is always nil. If the
// buffer becomes too large, WriteString will panic with ErrTooLarge.
func (b *Buffer) WriteString(s string) (n int, err error) {
b.lastRead = opInvalid
m := b.grow(len(s))
return copy(b.buf[m:], s), nil
}
~~~
~~~
fmt.Println("===========以下通过WriteString把swift写入Learning缓冲器尾部=========")
newString := "swift"
//创建一个string内容Learning的缓冲器
buf := bytes.NewBufferString("Learning")
//打印为Learning
fmt.Println(buf.String())
//将newString这个string写到buf的尾部
buf.WriteString(newString)
fmt.Println(buf.String())
~~~
打印:
> ===========以下通过Write把swift写入Learning缓冲器尾部=========
Learning
Learningswift
### WriteByte
将一个byte类型的数据放到缓冲器的尾部
~~~
// WriteByte appends the byte c to the buffer, growing the buffer as needed.
// The returned error is always nil, but is included to match bufio.Writer's
// WriteByte. If the buffer becomes too large, WriteByte will panic with
// ErrTooLarge.
func (b *Buffer) WriteByte(c byte) error {
b.lastRead = opInvalid
m := b.grow(1)
b.buf[m] = c
return nil
}
~~~
~~~
fmt.Println("===========以下通过WriteByte把!写入Learning缓冲器尾部=========")
var newByte byte = '!'
//创建一个string内容Learning的缓冲器
buf := bytes.NewBufferString("Learning")
//打印为Learning
fmt.Println(buf.String())
//将newString这个string写到buf的尾部
buf.WriteByte(newByte)
fmt.Println(buf.String())
~~~
打印:
> ===========以下通过WriteByte把swift写入Learning缓冲器尾部=========
Learning
Learning!
### WriteRune
将一个rune类型的数据放到缓冲器的尾部
~~~
// WriteRune appends the UTF-8 encoding of Unicode code point r to the
// buffer, returning its length and an error, which is always nil but is
// included to match bufio.Writer's WriteRune. The buffer is grown as needed;
// if it becomes too large, WriteRune will panic with ErrTooLarge.
func (b *Buffer) WriteRune(r rune) (n int, err error) {
if r < utf8.RuneSelf {
b.WriteByte(byte(r))
return 1, nil
}
n = utf8.EncodeRune(b.runeBytes[0:], r)
b.Write(b.runeBytes[0:n])
return n, nil
}
~~~
~~~
fmt.Println("===========以下通过WriteRune把\"好\"写入Learning缓冲器尾部=========")
var newRune = '好'
//创建一个string内容Learning的缓冲器
buf := bytes.NewBufferString("Learning")
//打印为Learning
fmt.Println(buf.String())
//将newString这个string写到buf的尾部
buf.WriteRune(newRune)
fmt.Println(buf.String())
~~~
打印:
> ===========以下通过WriteRune把”好”写入Learning缓冲器尾部=========
Learning
Learning好
### 完整示例
~~~
package main
import (
"bytes"
"encoding/binary"
"fmt"
)
func main() {
//newBuffer 整形转换成字节
var n int = 10000
intToBytes := IntToBytes(n)
fmt.Println("==========int to bytes========")
fmt.Println(intToBytes)
//NewBufferString
TestBufferString()
//write
BufferWrite()
//WriteString
BufferWriteString()
//WriteByte
BufferWriteByte()
//WriteRune
BufferWriteRune()
}
func IntToBytes(n int) []byte {
x := int32(n)
//创建一个内容是[]byte的slice的缓冲器
//与bytes.NewBufferString("")等效
bytesBuffer := bytes.NewBuffer([]byte{})
binary.Write(bytesBuffer, binary.BigEndian, x)
return bytesBuffer.Bytes()
}
func TestBufferString(){
buf1:=bytes.NewBufferString("swift")
buf2:=bytes.NewBuffer([]byte("swift"))
buf3:=bytes.NewBuffer([]byte{'s','w','i','f','t'})
fmt.Println("===========以下buf1,buf2,buf3等效=========")
fmt.Println("buf1:", buf1)
fmt.Println("buf2:", buf2)
fmt.Println("buf3:", buf3)
fmt.Println("===========以下创建空的缓冲器等效=========")
buf4:=bytes.NewBufferString("")
buf5:=bytes.NewBuffer([]byte{})
fmt.Println("buf4:", buf4)
fmt.Println("buf5:", buf5)
}
func BufferWrite(){
fmt.Println("===========以下通过Write把swift写入Learning缓冲器尾部=========")
newBytes := []byte("swift")
//创建一个内容Learning的缓冲器
buf := bytes.NewBuffer([]byte("Learning"))
//打印为Learning
fmt.Println(buf.String())
//将newBytes这个slice写到buf的尾部
buf.Write(newBytes)
fmt.Println(buf.String())
}
func BufferWriteString(){
fmt.Println("===========以下通过Write把swift写入Learning缓冲器尾部=========")
newString := "swift"
//创建一个string内容Learning的缓冲器
buf := bytes.NewBufferString("Learning")
//打印为Learning
fmt.Println(buf.String())
//将newString这个string写到buf的尾部
buf.WriteString(newString)
fmt.Println(buf.String())
}
func BufferWriteByte(){
fmt.Println("===========以下通过WriteByte把swift写入Learning缓冲器尾部=========")
var newByte byte = '!'
//创建一个string内容Learning的缓冲器
buf := bytes.NewBufferString("Learning")
//打印为Learning
fmt.Println(buf.String())
//将newString这个string写到buf的尾部
buf.WriteByte(newByte)
fmt.Println(buf.String())
}
func BufferWriteRune(){
fmt.Println("===========以下通过WriteRune把\"好\"写入Learning缓冲器尾部=========")
var newRune = '好'
//创建一个string内容Learning的缓冲器
buf := bytes.NewBufferString("Learning")
//打印为Learning
fmt.Println(buf.String())
//将newString这个string写到buf的尾部
buf.WriteRune(newRune)
fmt.Println(buf.String())
}
~~~
# 向 Buffer 中读取数据
### Read
给Read方法一个容器p,读完后,p就满了,缓冲器相应的减少了,返回的n为成功读的数量
~~~
// Read reads the next len(p) bytes from the buffer or until the buffer
// is drained. The return value n is the number of bytes read. If the
// buffer has no data to return, err is io.EOF (unless len(p) is zero);
// otherwise it is nil.
func (b *Buffer) Read(p []byte) (n int, err error) {}
~~~
~~~
func Read(){
bufs := bytes.NewBufferString("Learning swift.")
fmt.Println(bufs.String())
//声明一个空的slice,容量为8
l := make([]byte, 8)
//把bufs的内容读入到l内,因为l容量为8,所以只读了8个过来
bufs.Read(l)
fmt.Println("::bufs缓冲器内容::")
fmt.Println(bufs.String())
//空的l被写入了8个字符,所以为 Learning
fmt.Println("::l的slice内容::")
fmt.Println(string(l))
//把bufs的内容读入到l内,原来的l的内容被覆盖了
bufs.Read(l)
fmt.Println("::bufs缓冲器被第二次读取后剩余的内容::")
fmt.Println(bufs.String())
fmt.Println("::l的slice内容被覆盖,由于bufs只有7个了,因此最后一个g被留下来了::")
fmt.Println(string(l))
}
~~~
打印:
> =======Read=======
Learning swift.
::bufs缓冲器内容::
swift.
::l的slice内容::
Learning
::bufs缓冲器被第二次读取后剩余的内容::
> ::l的slice内容被覆盖::
swift.g
### ReadByte
返回缓冲器头部的第一个byte,缓冲器头部第一个byte被拿掉
~~~
// ReadByte reads and returns the next byte from the buffer.
// If no byte is available, it returns error io.EOF.
func (b *Buffer) ReadByte() (c byte, err error) {}
~~~
~~~
func ReadByte(){
bufs := bytes.NewBufferString("Learning swift.")
fmt.Println(bufs.String())
//读取第一个byte,赋值给b
b, _ := bufs.ReadByte()
fmt.Println(bufs.String())
fmt.Println(string(b))
}
~~~
打印:
> =======ReadByte===
Learning swift.
earning swift.
L
### ReadRune
ReadRune和ReadByte很像
返回缓冲器头部的第一个rune,缓冲器头部第一个rune被拿掉
~~~
// ReadRune reads and returns the next UTF-8-encoded
// Unicode code point from the buffer.
// If no bytes are available, the error returned is io.EOF.
// If the bytes are an erroneous UTF-8 encoding, it
// consumes one byte and returns U+FFFD, 1.
func (b *Buffer) ReadRune() (r rune, size int, err error) {}
~~~
~~~
func ReadRune(){
bufs := bytes.NewBufferString("学swift.")
fmt.Println(bufs.String())
//读取第一个rune,赋值给r
r,z,_ := bufs.ReadRune()
//打印中文"学",缓冲器头部第一个被拿走
fmt.Println(bufs.String())
//打印"学","学"作为utf8储存占3个byte
fmt.Println("r=",string(r),",z=",z)
}
~~~
### ReadBytes
ReadBytes需要一个byte作为分隔符,读的时候从缓冲器里找第一个出现的分隔符(delim),找到后,把从缓冲器头部开始到分隔符之间的所有byte进行返回,作为byte类型的slice,返回后,缓冲器也会空掉一部分
~~~
// ReadBytes reads until the first occurrence of delim in the input,
// returning a slice containing the data up to and including the delimiter.
// If ReadBytes encounters an error before finding a delimiter,
// it returns the data read before the error and the error itself (often io.EOF).
// ReadBytes returns err != nil if and only if the returned data does not end in
// delim.
func (b *Buffer) ReadBytes(delim byte) (line []byte, err error) {}
~~~
~~~
func ReadBytes(){
bufs := bytes.NewBufferString("现在开始 Learning swift.")
fmt.Println(bufs.String())
var delim byte = 'L'
line, _ := bufs.ReadBytes(delim)
fmt.Println(bufs.String())
fmt.Println(string(line))
}
~~~
打印:
> =======ReadBytes==
现在开始 Learning swift.
earning swift.
现在开始 L
### ReadString
ReadString需要一个byte作为分隔符,读的时候从缓冲器里找第一个出现的分隔符(delim),找到后,把从缓冲器头部开始到分隔符之间的所有byte进行返回,作为字符串,返回后,缓冲器也会空掉一部分
和ReadBytes类似
~~~
// ReadString reads until the first occurrence of delim in the input,
// returning a string containing the data up to and including the delimiter.
// If ReadString encounters an error before finding a delimiter,
// it returns the data read before the error and the error itself (often io.EOF).
// ReadString returns err != nil if and only if the returned data does not end
// in delim.
func (b *Buffer) ReadString(delim byte) (line string, err error) {}
~~~
### ReadFrom
从一个实现io.Reader接口的r,把r里的内容读到缓冲器里,n返回读的数量
~~~
// ReadFrom reads data from r until EOF and appends it to the buffer, growing
// the buffer as needed. The return value n is the number of bytes read. Any
// error except io.EOF encountered during the read is also returned. If the
// buffer becomes too large, ReadFrom will panic with ErrTooLarge.
func (b *Buffer) ReadFrom(r io.Reader) (n int64, err error) {}
~~~
~~~
func ReadFrom(){
//test.txt 内容是 "未来"
file, _ := os.Open("learngo/bytes/text.txt")
buf := bytes.NewBufferString("Learning swift.")
buf.ReadFrom(file) //将text.txt内容追加到缓冲器的尾部
fmt.Println(buf.String())
}
~~~
打印:
> =======ReadFrom===
Learning swift.未来
### Reset
将数据清空,没有数据可读
~~~
// Reset resets the buffer so it has no content.
// b.Reset() is the same as b.Truncate(0).
func (b *Buffer) Reset() { b.Truncate(0) }
~~~
~~~
func Reset(){
bufs := bytes.NewBufferString("现在开始 Learning swift.")
fmt.Println(bufs.String())
bufs.Reset()
fmt.Println("::已经清空了bufs的缓冲内容::")
fmt.Println(bufs.String())
}
~~~
打印:
> =======Reset======
现在开始 Learning swift.
::已经清空了bufs的缓冲内容::
### string
将未读取的数据返回成 string
~~~
// String returns the contents of the unread portion of the buffer
// as a string. If the Buffer is a nil pointer, it returns "<nil>".
func (b *Buffer) String() string {}
~~~
Golang同步:原子操作使用
最后更新于:2022-04-01 11:59:28
> 原子操作即是进行过程中不能被中断的操作。针对某个值的原子操作在被进行的过程中,CPU绝不会再去进行其他的针对该值的操作。
为了实现这样的严谨性,原子操作仅会由一个独立的CPU指令代表和完成。
- GO语言提供的原子操作都是非入侵式的,由标准库sync/atomic中的众多函数代表
- 类型包括int32,int64,uint32,uint64,uintptr,unsafe.Pointer,共六个。
- 这些函数提供的原子操作共有五种:增或减,比较并交换,载入,存储和交换
# int各种类型取值范围
| 类型 | 长度(字节) | 值范围 |
|-----|-----|-----|
| int8 | 1 | -128 ~ 127 |
| uint8(byte) | 1 | 0 ~ 255 |
| int16 | 2 | 32768~32767 |
| uint16 | 2 | 0~65535 |
| int32 | 4 | 2147483648~2147483647 |
| uint32 | 4 | 0~4294967295 |
| int64 | 8 | -9223372036854775808~9223372036854775807 |
| uint64 | 8 | 0~18446744073709551615 |
| int | 平台相关 | 平台相关 |
| uint | 平台相关 | 平台相关 |
| uintptr | 同指针 | 在32位平 下为4字节,64位平 下为8字节 |
# 增或减Add
函数名称都以Add为前缀,并后跟针对的具体类型的名称。
- 被操作的类型只能是数值类型
- int32,int64,uint32,uint64,uintptr类型可以使用原子增或减操作
- 第一个参数值必须是一个指针类型的值,以便施加特殊的CPU指令
- 第二个参数值的类型和第一个被操作值的类型总是相同的。
### 示例
~~~
package main
import (
"fmt"
"sync/atomic"
)
func main(){
var i32 int32
fmt.Println("=====old i32 value=====")
fmt.Println(i32)
//第一个参数值必须是一个指针类型的值,因为该函数需要获得被操作值在内存中的存放位置,以便施加特殊的CPU指令
//结束时会返回原子操作后的新值
newI32 := atomic.AddInt32(&i32,3)
fmt.Println("=====new i32 value=====")
fmt.Println(i32)
fmt.Println(newI32)
var i64 int64
fmt.Println("=====old i64 value=====")
fmt.Println(i64)
newI64 := atomic.AddInt64(&i64,-3)
fmt.Println("=====new i64 value=====")
fmt.Println(i64)
fmt.Println(newI64)
}
~~~
结果:
/usr/local/go/bin/go run /Users/liuxinming/go/src/free/learngo/atomic/add/add.go
=====old i32 value=====
0
=====new i32 value=====
-3
-3
=====old i64 value=====
0
=====new i64 value=====
-3
-3
# 比较并交换CAS
Compare And Swap 简称CAS,在sync/atomic包种,这类原子操作由名称以‘CompareAndSwap’为前缀的若干个函数代表。
- 声明如下
~~~
func CompareAndSwapInt32(addr *int32, old, new int32) (swapped bool)
~~~
- 调用函数后,会先判断参数addr指向的被操作值与参数old的值是否相等
- 仅当此判断得到肯定的结果之后,才会用参数new代表的新值替换掉原先的旧值,否则操作就会被忽略。
-
so, 需要用for循环不断进行尝试,直到成功为止
-
使用锁的做法趋于悲观
- 我们总假设会有并发的操作要修改被操作的值,并使用锁将相关操作放入临界区中加以保护
- 使用CAS操作的做法趋于乐观
- 总是假设被操作值未曾被改变(即与旧值相等),并一旦确认这个假设的真实性就立即进行值替换。
### 示例
~~~
package main
import (
"fmt"
"sync/atomic"
)
var value int32
func main() {
fmt.Println("======old value=======")
fmt.Println(value)
fmt.Println("======CAS value=======")
addValue(3)
fmt.Println(value)
}
//不断地尝试原子地更新value的值,直到操作成功为止
func addValue(delta int32){
//在被操作值被频繁变更的情况下,CAS操作并不那么容易成功
//so 不得不利用for循环以进行多次尝试
for {
v := value
if atomic.CompareAndSwapInt32(&value, v, (v + delta)){
//在函数的结果值为true时,退出循环
break
}
//操作失败的缘由总会是value的旧值已不与v的值相等了.
//CAS操作虽然不会让某个Goroutine阻塞在某条语句上,但是仍可能会使流产的执行暂时停一下,不过时间大都极其短暂.
}
}
~~~
结果:
======old value=======
0
======CAS value=======
3
# 载入Load
上面的比较并交换案例总 v:= value为变量v赋值,但… 要注意,在进行读取value的操作的过程中,其他对此值的读写操作是可以被同时进行的,那么这个读操作很可能会读取到一个只被修改了一半的数据.
- so so so , 我们要使用sync/atomic代码包同样为我们提供了一系列的函数,以Load为前缀(载入),来确保这样的糟糕事情发生。
### 示例
~~~
package main
import (
"fmt"
"sync/atomic"
)
var value int32
func main() {
fmt.Println("======old value=======")
fmt.Println(value)
fmt.Println("======CAS value=======")
addValue(3)
fmt.Println(value)
}
//不断地尝试原子地更新value的值,直到操作成功为止
func addValue(delta int32){
//在被操作值被频繁变更的情况下,CAS操作并不那么容易成功
//so 不得不利用for循环以进行多次尝试
for {
//v := value
//在进行读取value的操作的过程中,其他对此值的读写操作是可以被同时进行的,那么这个读操作很可能会读取到一个只被修改了一半的数据.
//因此我们要使用载入
v := atomic.LoadInt32(&value)
if atomic.CompareAndSwapInt32(&value, v, (v + delta)){
//在函数的结果值为true时,退出循环
break
}
//操作失败的缘由总会是value的旧值已不与v的值相等了.
//CAS操作虽然不会让某个Goroutine阻塞在某条语句上,但是仍可能会使流产的执行暂时停一下,不过时间大都极其短暂.
}
}
~~~
- atomic.LoadInt32接受一个*int32类型的指针值
- 返回该指针指向的那个值
# 存储Store
> 与读取操作相对应的是写入操作。 而sync/atomic包也提供了与原子的载入函数相对应的原子的值存储函数。 以Store为前缀
- 在原子地存储某个值的过程中,任何CPU都不会进行针对同一个值的读或写操作。
- 原子的值存储操作总会成功,因为它并不会关心被操作值的旧值是什么
- 和CAS操作有着明显的区别
~~~
fmt.Println("======Store value=======")
atomic.StoreInt32(&value, 10)
fmt.Println(value)
~~~
# 交换Swap
- 与CAS操作不同,原子交换操作不会关心被操作的旧值。
- 它会直接设置新值
- 它会返回被操作值的旧值
- 此类操作比CAS操作的约束更少,同时又比原子载入操作的功能更强
# 实际案例(继续改造上一版代码)
~~~
//数据文件的实现类型
type myDataFile struct {
f *os.File //文件
fmutex sync.RWMutex //被用于文件的读写锁
rcond *sync.Cond //读操作需要用到的条件变量
woffset int64 // 写操作需要用到的偏移量
roffset int64 // 读操作需要用到的偏移量
dataLen uint32 //数据块长度
}
//此处省略...
func (df *myDataFile) Read() (rsn int64, d Data, err error){
// 读取并更新读偏移量
var offset int64
for {
offset = atomic.LoadInt64(&df.roffset)
if atomic.CompareAndSwapInt64(&df.roffset, offset, (offset + int64(df.dataLen))){
break
}
}
//读取一个数据块,最后读取的数据块序列号
rsn = offset / int64(df.dataLen)
bytes := make([]byte, df.dataLen)
//读写锁:读锁定
df.fmutex.RLock()
defer df.fmutex.RUnlock()
for {
_, err = df.f.ReadAt(bytes, offset)
if err != nil {
if err == io.EOF {
//暂时放弃fmutex的 读锁,并等待通知的到来
df.rcond.Wait()
continue
}
}
break
}
d = bytes
return
}
func (df *myDataFile) Write(d Data) (wsn int64, err error){
//读取并更新写的偏移量
var offset int64
for {
offset = atomic.LoadInt64(&df.woffset)
if atomic.CompareAndSwapInt64(&df.woffset, offset, (offset + int64(df.dataLen))){
break
}
}
//写入一个数据块,最后写入数据块的序号
wsn = offset / int64(df.dataLen)
var bytes []byte
if len(d) > int(df.dataLen){
bytes = d[0:df.dataLen]
}else{
bytes = d
}
df.fmutex.Lock()
defer df.fmutex.Unlock()
_, err = df.f.Write(bytes)
//发送通知
df.rcond.Signal()
return
}
func (df *myDataFile) Rsn() int64{
offset := atomic.LoadInt64(&df.roffset)
return offset / int64(df.dataLen)
}
func (df *myDataFile) Wsn() int64{
offset := atomic.LoadInt64(&df.woffset)
return offset / int64(df.dataLen)
}
~~~
完整代码放在GITHUB:
[https://github.com/lxmgo/learngo](https://github.com/lxmgo/learngo)
一些学习过程整理,希望对大家学习Go语言有所帮助。
Golang同步:条件变量和锁组合使用
最后更新于:2022-04-01 11:59:25
> 条件变量的作用并不是保证在同一时刻仅有一个线程访问某一个共享数据,而是在对应的共享数据的状态发生变化时,通知其他因此而被阻塞的线程。
- 条件变量与互斥量组合使用
- 互斥量为共享数据的访问提供互斥支持
- 条件变量就状态的变化向相关线程发出通知
# 三种操作方法
- 等待通知: wait
- 阻塞当前线程,直到收到该条件变量发来的通知
- 单发通知: signal
- 让该条件变量向至少一个正在等待它的通知的线程发送通知,表示共享数据的状态已经改变。
- 广播通知: broadcast
- 让条件变量给正在等待它的通知的所有线程都发送通知。
# 声明
> func NewCond(l Locker) *Cond
# 示例
改造上一节的锁使用代码
Golang同步:锁的使用案例详解
传送门:[http://blog.csdn.net/liuxinmingcode/article/details/50044327](http://blog.csdn.net/liuxinmingcode/article/details/50044327)
**Read()方法改造如下:**
~~~
func (df *myDataFile) Read() (rsn int64, d Data, err error){
// 读取并更新读偏移量
var offset int64
// 读互斥锁定
df.rmutex.Lock()
offset = df.roffset
// 更改偏移量, 当前偏移量+数据块长度
df.roffset += int64(df.dataLen)
// 读互斥解锁
df.rmutex.Unlock()
//读取一个数据块,最后读取的数据块序列号
rsn = offset / int64(df.dataLen)
bytes := make([]byte, df.dataLen)
//读写锁:读锁定
df.fmutex.RLock()
defer df.fmutex.RUnlock()
for {
_, err = df.f.ReadAt(bytes, offset)
if err != nil {
if err == io.EOF {
//暂时放弃fmutex的 读锁,并等待通知的到来
df.rcond.Wait()
continue
}
}
return
}
d = bytes
return
}
~~~
**Write()方法改造如下:**
~~~
func (df *myDataFile) Write(d Data) (wsn int64, err error){
//读取并更新写的偏移量
var offset int64
df.wmutex.Lock()
offset = df.woffset
df.woffset += int64(df.dataLen)
df.wmutex.Unlock()
//写入一个数据块,最后写入数据块的序号
wsn = offset / int64(df.dataLen)
var bytes []byte
if len(d) > int(df.dataLen){
bytes = d[0:df.dataLen]
}else{
bytes = d
}
df.fmutex.Lock()
defer df.fmutex.Unlock()
_, err = df.f.Write(bytes)
//发送通知
df.rcond.Signal()
return
}
~~~
因为一个数据块只能有一个读操作读取,因此我们使用条件变量Signal方法通知某一个为此等待的Wait方法,唤醒一个相关的Goroutine。
还有一件事不能忘记,初始化rcond字段
~~~
func NewDataFile(path string, dataLen uint32) (DataFile, error){
//f, err := os.OpenFile(path, os.O_APPEND|os.O_RDWR|os.O_CREATE, 0666)
f,err := os.Create(path)
if err != nil {
fmt.Println("Fail to find", f, "cServer start Failed")
return nil, err
}
if dataLen == 0 {
return nil, errors.New("Invalid data length!")
}
df := &myDataFile{
f : f,
dataLen:dataLen,
}
//创建一个可用的条件变量(初始化),返回一个*sync.Cond类型的结果值,我们就可以调用该值拥有的三个方法Wait,Signal,Broadcast
df.rcond = sync.NewCond(df.fmutex.RLocker())
return df, nil
}
~~~
源代码:
[https://github.com/lxmgo/learngo](https://github.com/lxmgo/learngo)
Golang同步:锁的使用案例详解
最后更新于:2022-04-01 11:59:23
# 互斥锁
> 互斥锁是传统的并发程序对共享资源进行访问控制的主要手段。它由标准库代码包sync中的Mutex结构体类型代表。只有两个公开方法
- Lock
- Unlock
类型sync.Mutex的零值表示了未被锁定的互斥量。
~~~
var mutex sync.Mutex
mutex.Lock()
~~~
### 示例
~~~
// test for Go
//
// Copyright (c) 2015 - Batu <1235355@qq.com>
package main
import (
"fmt"
"sync"
"time"
)
func main(){
//声明
var mutex sync.Mutex
fmt.Println("Lock the lock. (G0)")
//加锁mutex
mutex.Lock()
fmt.Println("The lock is locked.(G0)")
for i := 1; i < 4; i++ {
go func(i int) {
fmt.Printf("Lock the lock. (G%d)\n", i)
mutex.Lock()
fmt.Printf("The lock is locked. (G%d)\n", i)
}(i)
}
//休息一会,等待打印结果
time.Sleep(time.Second)
fmt.Println("Unlock the lock. (G0)")
//解锁mutex
mutex.Unlock()
fmt.Println("The lock is unlocked. (G0)")
//休息一会,等待打印结果
time.Sleep(time.Second)
}
~~~
打印结果:
/usr/local/go/bin/go run /Users/liuxinming/go/src/example/test/test.go
Lock the lock. (G0)
The lock is locked.(G0)
Lock the lock. (G1)
Lock the lock. (G2)
Lock the lock. (G3)
Unlock the lock. (G0)
The lock is unlocked. (G0)
The lock is locked. (G1)
建议:同一个互斥锁的成对锁定和解锁操作放在同一层次的代码块中。
# 读写锁
> 针对读写操作的互斥锁,它可以分别针对读操作和写操作进行锁定和解锁操作。读写锁遵循的访问控制规则与互斥锁有所不同。
- 它允许任意读操作同时进行
- 同一时刻,只允许有一个写操作进行
==============华丽分割线============
- 并且一个写操作被进行过程中,读操作的进行也是不被允许的
- 读写锁控制下的多个写操作之间都是互斥的
- 写操作与读操作之间也都是互斥的
- 多个读操作之间却不存在互斥关系
### 读写锁由结构体类型sync.RWMutex代表
写操作的锁定和解锁
* func (*RWMutex) Lock
* func (*RWMutex) Unlock
读操作的锁定和解锁
* func (*RWMutex) Rlock
* func (*RWMutex) RUnlock
注意:
+ 写解锁在进行的时候会试图唤醒所有因欲进行读锁定而被阻塞的Goroutine.
+ 读解锁在进行的时候只会在已无任何读锁定的情况下试图唤醒一个因欲进行写锁定而被阻塞的Goroutine
+ 若对一个未被写锁定的读写锁进行写解锁,会引起一个运行时的恐慌
+ 而对一个未被读锁定的读写锁进行读解锁却不会如此
### 锁的完整示例
~~~
// test for Go
//
// Copyright (c) 2015 - Batu <1235355@qq.com>
//
// 创建一个文件存放数据,在同一时刻,可能会有多个Goroutine分别进行对此文件的写操作和读操作.
// 每一次写操作都应该向这个文件写入若干个字节的数据,作为一个独立的数据块存在,这意味着写操作之间不能彼此干扰,写入的内容之间也不能出现穿插和混淆的情况
// 每一次读操作都应该从这个文件中读取一个独立完整的数据块.它们读取的数据块不能重复,且需要按顺序读取.
// 例如: 第一个读操作读取了数据块1,第二个操作就应该读取数据块2,第三个读操作则应该读取数据块3,以此类推
// 对于这些读操作是否可以被同时执行,不做要求. 即使同时进行,也应该保持先后顺序.
package main
import (
"fmt"
"sync"
"time"
"os"
"errors"
"io"
)
//数据文件的接口类型
type DataFile interface {
// 读取一个数据块
Read() (rsn int64, d Data, err error)
// 写入一个数据块
Write(d Data) (wsn int64, err error)
// 获取最后读取的数据块的序列号
Rsn() int64
// 获取最后写入的数据块的序列号
Wsn() int64
// 获取数据块的长度
DataLen() uint32
}
//数据类型
type Data []byte
//数据文件的实现类型
type myDataFile struct {
f *os.File //文件
fmutex sync.RWMutex //被用于文件的读写锁
woffset int64 // 写操作需要用到的偏移量
roffset int64 // 读操作需要用到的偏移量
wmutex sync.Mutex // 写操作需要用到的互斥锁
rmutex sync.Mutex // 读操作需要用到的互斥锁
dataLen uint32 //数据块长度
}
//初始化DataFile类型值的函数,返回一个DataFile类型的值
func NewDataFile(path string, dataLen uint32) (DataFile, error){
f, err := os.OpenFile(path, os.O_APPEND|os.O_RDWR|os.O_CREATE, 0666)
//f,err := os.Create(path)
if err != nil {
fmt.Println("Fail to find", f, "cServer start Failed")
return nil, err
}
if dataLen == 0 {
return nil, errors.New("Invalid data length!")
}
df := &myDataFile{
f : f,
dataLen:dataLen,
}
return df, nil
}
//获取并更新读偏移量,根据读偏移量从文件中读取一块数据,把该数据块封装成一个Data类型值并将其作为结果值返回
func (df *myDataFile) Read() (rsn int64, d Data, err error){
// 读取并更新读偏移量
var offset int64
// 读互斥锁定
df.rmutex.Lock()
offset = df.roffset
// 更改偏移量, 当前偏移量+数据块长度
df.roffset += int64(df.dataLen)
// 读互斥解锁
df.rmutex.Unlock()
//读取一个数据块,最后读取的数据块序列号
rsn = offset / int64(df.dataLen)
bytes := make([]byte, df.dataLen)
for {
//读写锁:读锁定
df.fmutex.RLock()
_, err = df.f.ReadAt(bytes, offset)
if err != nil {
//由于进行写操作的Goroutine比进行读操作的Goroutine少,所以过不了多久读偏移量roffset的值就会大于写偏移量woffset的值
// 也就是说,读操作很快就没有数据块可读了,这种情况会让df.f.ReadAt方法返回的第二个结果值为代表的非nil且会与io.EOF相等的值
// 因此不应该把EOF看成错误的边界情况
// so 在读操作读完数据块,EOF时解锁读操作,并继续循环,尝试获取同一个数据块,直到获取成功为止.
if err == io.EOF {
//注意,如果在该for代码块被执行期间,一直让读写所fmutex处于读锁定状态,那么针对它的写操作将永远不会成功.
//切相应的Goroutine也会被一直阻塞.因为它们是互斥的.
// so 在每条return & continue 语句的前面加入一个针对该读写锁的读解锁操作
df.fmutex.RUnlock()
//注意,出现EOF时可能是很多意外情况,如文件被删除,文件损坏等
//这里可以考虑把逻辑提交给上层处理.
continue
}
}
break
}
d = bytes
df.fmutex.RUnlock()
return
}
func (df *myDataFile) Write(d Data) (wsn int64, err error){
//读取并更新写的偏移量
var offset int64
df.wmutex.Lock()
offset = df.woffset
df.woffset += int64(df.dataLen)
df.wmutex.Unlock()
//写入一个数据块,最后写入数据块的序号
wsn = offset / int64(df.dataLen)
var bytes []byte
if len(d) > int(df.dataLen){
bytes = d[0:df.dataLen]
}else{
bytes = d
}
df.fmutex.Lock()
df.fmutex.Unlock()
_, err = df.f.Write(bytes)
return
}
func (df *myDataFile) Rsn() int64{
df.rmutex.Lock()
defer df.rmutex.Unlock()
return df.roffset / int64(df.dataLen)
}
func (df *myDataFile) Wsn() int64{
df.wmutex.Lock()
defer df.wmutex.Unlock()
return df.woffset / int64(df.dataLen)
}
func (df *myDataFile) DataLen() uint32 {
return df.dataLen
}
func main(){
//简单测试下结果
var dataFile DataFile
dataFile,_ = NewDataFile("./mutex_2015_1.dat", 10)
var d=map[int]Data{
1:[]byte("batu_test1"),
2:[]byte("batu_test2"),
3:[]byte("test1_batu"),
}
//写入数据
for i:= 1; i < 4; i++ {
go func(i int){
wsn,_ := dataFile.Write(d[i])
fmt.Println("write i=", i,",wsn=",wsn, ",success.")
}(i)
}
//读取数据
for i:= 1; i < 4; i++ {
go func(i int){
rsn,d,_ := dataFile.Read()
fmt.Println("Read i=", i,",rsn=",rsn,",data=",d, ",success.")
}(i)
}
time.Sleep(10 * time.Second)
}
~~~
打印结果:
> /usr/local/go/bin/go run /Users/liuxinming/go/src/example/test/test.go
write i= 3 ,wsn= 1 ,success.
write i= 1 ,wsn= 0 ,success.
Read i= 1 ,rsn= 1 ,data= [116 101 115 116 49 95 98 97 116 117] ,success.
write i= 2 ,wsn= 2 ,success.
Read i= 2 ,rsn= 0 ,data= [98 97 116 117 95 116 101 115 116 49] ,success.
Read i= 3 ,rsn= 2 ,data= [98 97 116 117 95 116 101 115 116 50] ,success.
Golang time包的定时器/断续器
最后更新于:2022-04-01 11:59:21
# 定时器
在time包中有两个函数可以帮助我们初始化time.Timer
### time.Newtimer函数
初始化一个到期时间据此时的间隔为3小时30分的定时器
~~~
t := time.Newtimer(3*time.Hour + 30*time.Minute)
~~~
注意,这里的变量t是*time.NewTimer类型的,这个指针类型的方法集合包含两个方法
- Rest
- 用于重置定时器
- 该方法返回一个bool类型的值
- Stop
- 用来停止定时器
- 该方法返回一个bool类型的值,如果返回false,说明该定时器在之前已经到期或者已经被停止了,反之返回true。
通过定时器的字段C,我们可以及时得知定时器到期的这个事件来临,C是一个chan time.Time类型的缓冲通道,一旦触及到期时间,定时器就会向自己的C字段发送一个time.Time类型的元素值
示例一:一个简单定时器
~~~
package main
import (
"fmt"
"time"
)
func main(){
//初始化定时器
t := time.NewTimer(2 * time.Second)
//当前时间
now := time.Now()
fmt.Printf("Now time : %v.\n", now)
expire := <- t.C
fmt.Printf("Expiration time: %v.\n", expire)
}
~~~
> Now time : 2015-10-31 01:19:07.210771347 +0800 CST.
Expiration time: 2015-10-31 01:19:09.215489592 +0800 CST.
示例二:我们在改造下之前的那个简单超时操作
~~~
package main
import (
"fmt"
"time"
)
func main(){
//初始化通道
ch11 := make(chan int, 1000)
sign := make(chan byte, 1)
//给ch11通道写入数据
for i := 0; i < 1000; i++ {
ch11 <- i
}
//单独起一个Goroutine执行select
go func(){
var e int
ok := true
//首先声明一个*time.Timer类型的值,然后在相关case之后声明的匿名函数中尽可能的复用它
var timer *time.Timer
for{
select {
case e = <- ch11:
fmt.Printf("ch11 -> %d\n",e)
case <- func() <-chan time.Time {
if timer == nil{
//初始化到期时间据此间隔1ms的定时器
timer = time.NewTimer(time.Millisecond)
}else {
//复用,通过Reset方法重置定时器
timer.Reset(time.Millisecond)
}
//得知定时器到期事件来临时,返回结果
return timer.C
}():
fmt.Println("Timeout.")
ok = false
break
}
//终止for循环
if !ok {
sign <- 0
break
}
}
}()
//惯用手法,读取sign通道数据,为了等待select的Goroutine执行。
<- sign
}
~~~
### time.After函数
- time.After函数, 表示多少时间之后,但是在取出channel内容之前不阻塞,后续程序可以继续执行
- 鉴于After特性,其通常用来处理程序超时问题
~~~
package main
import (
"fmt"
"time"
)
func main(){
ch1 := make(chan int, 1)
ch2 := make(chan int, 1)
select {
case e1 := <-ch1:
//如果ch1通道成功读取数据,则执行该case处理语句
fmt.Printf("1th case is selected. e1=%v",e1)
case e2 := <-ch2:
//如果ch2通道成功读取数据,则执行该case处理语句
fmt.Printf("2th case is selected. e2=%v",e2)
case <- time.After(2 * time.Second):
fmt.Println("Timed out")
}
}
~~~
> Timed out
- time.Sleep函数,表示休眠多少时间,休眠时处于阻塞状态,后续程序无法执行.
### time.Afterfunc函数
示例三:自定义定时器
~~~
package main
import (
"fmt"
"time"
)
func main(){
var t *time.Timer
f := func(){
fmt.Printf("Expiration time : %v.\n", time.Now())
fmt.Printf("C`s len: %d\n", len(t.C))
}
t = time.AfterFunc(1*time.Second, f)
//让当前Goroutine 睡眠2s,确保大于内容的完整
//这样做原因是,time.AfterFunc的调用不会被阻塞。它会以一部的方式在到期事件来临执行我们自定义函数f。
time.Sleep(2 * time.Second)
}
~~~
> Expiration time : 2015-10-31 01:04:42.579988801 +0800 CST.
C`s len: 0
第二行打印内容说明:定时器的字段C并没有缓冲任何元素值。这也说明了,在给定了自定义函数后,默认的处理方法(向C发送代表绝对到期时间的元素值)就不会被执行了。
# 断续器
结构体类型time.Ticker表示了断续器的静态结构。
就是周期性的传达到期时间的装置。这种装置的行为方式与仅有秒针的钟表有些类似,只不过间隔时间可以不是1s。
初始化一个断续器
~~~
var ticker *timeTicker = time.NewTicker(time.Second)
~~~
### 示例一:使用时间控制停止ticke
~~~
package main
import (
"fmt"
"time"
)
func main(){
//初始化断续器,间隔2s
var ticker *time.Ticker = time.NewTicker(1 * time.Second)
go func() {
for t := range ticker.C {
fmt.Println("Tick at", t)
}
}()
time.Sleep(time.Second * 5) //阻塞,则执行次数为sleep的休眠时间/ticker的时间
ticker.Stop()
fmt.Println("Ticker stopped")
}
~~~
> Tick at 2015-10-31 01:29:34.41859284 +0800 CST
Tick at 2015-10-31 01:29:35.420131668 +0800 CST
Tick at 2015-10-31 01:29:36.420565647 +0800 CST
Tick at 2015-10-31 01:29:37.421038416 +0800 CST
Tick at 2015-10-31 01:29:38.41944582 +0800 CST
Ticker stopped
### 示例二:使用channel控制停止ticker
~~~
package main
import (
"fmt"
"time"
)
func main(){
//初始化断续器,间隔2s
var ticker *time.Ticker = time.NewTicker(100 * time.Millisecond)
//num为指定的执行次数
num := 2
c := make(chan int, num)
go func() {
for t := range ticker.C {
c <- 1
fmt.Println("Tick at", t)
}
}()
time.Sleep(time.Millisecond * 1500)
ticker.Stop()
fmt.Println("Ticker stopped")
}
~~~
Golang的select/非缓冲的Channel实例详解
最后更新于:2022-04-01 11:59:18
# select
> golang 的 select 就是监听 IO 操作,当 IO 操作发生时,触发相应的动作。
在执行select语句的时候,运行时系统会自上而下地判断每个case中的发送或接收操作是否可以被立即执行【立即执行:意思是当前Goroutine不会因此操作而被阻塞,还需要依据通道的具体特性(缓存或非缓存)】
- **每个case语句里必须是一个IO操作**
- **所有channel表达式都会被求值、所有被发送的表达式都会被求值**
- **如果任意某个case可以进行,它就执行(其他被忽略)**。
- **如果有多个case都可以运行,Select会随机公平地选出一个执行(其他不会执行)**。
- **如果有default子句,case不满足条件时执行该语句。**
- **如果没有default字句,select将阻塞,直到某个case可以运行;Go不会重新对channel或值进行求值。**
### select 语句用法
注意到 select 的代码形式和 switch 非常相似, 不过 select 的 case 里的操作语句只能是【IO 操作】 。
此示例里面 select 会一直等待等到某个 case 语句完成, 也就是等到成功从 ch1 或者 ch2 中读到数据,如果都不满足条件且存在default case, 那么default case会被执行。 则 select 语句结束。
示例:
~~~
package main
import (
"fmt"
)
func main(){
ch1 := make(chan int, 1)
ch2 := make(chan int, 1)
select {
case e1 := <-ch1:
//如果ch1通道成功读取数据,则执行该case处理语句
fmt.Printf("1th case is selected. e1=%v",e1)
case e2 := <-ch2:
//如果ch2通道成功读取数据,则执行该case处理语句
fmt.Printf("2th case is selected. e2=%v",e2)
default:
//如果上面case都没有成功,则进入default处理流程
fmt.Println("default!.")
}
}
~~~
### select分支选择规则
所有跟在case关键字右边的发送语句或接收语句中的通道表达式和元素表达式都会先被求值。无论它们所在的case是否有可能被选择都会这样。
> 求值顺序:自上而下、从左到右
示例:
~~~
package main
import (
"fmt"
)
//定义几个变量,其中chs和numbers分别代表了包含了有限元素的通道列表和整数列表
var ch1 chan int
var ch2 chan int
var chs = []chan int{ch1, ch2}
var numbers = []int{1,2,3,4,5}
func main(){
select {
case getChan(0) <- getNumber(2):
fmt.Println("1th case is selected.")
case getChan(1) <- getNumber(3):
fmt.Println("2th case is selected.")
default:
fmt.Println("default!.")
}
}
func getNumber(i int) int {
fmt.Printf("numbers[%d]\n", i)
return numbers[i]
}
func getChan(i int) chan int {
fmt.Printf("chs[%d]\n", i)
return chs[i]
}
~~~
输出:
> chs[0]
numbers[2]
chs[1]
numbers[3]
default!.
可以看出求值顺序。满足自上而下、自左而右这条规则。
### 随机执行case
如果同时有多个case满足条件,通过一个伪随机的算法决定哪一个case将会被执行。
示例:
~~~
package main
import (
"fmt"
)
func main(){
chanCap := 5
ch7 := make(chan int, chanCap)
for i := 0; i < chanCap; i++ {
select {
case ch7 <- 1:
case ch7 <- 2:
case ch7 <- 3:
}
}
for i := 0; i < chanCap; i++ {
fmt.Printf("%v\n", <-ch7)
}
}
~~~
输出:(注:每次运行都会不一样)
3
3
2
3
1
### 一些惯用手法示例
示例一:单独启用一个Goroutine执行select,等待通道关闭后结束循环
~~~
package main
import (
"fmt"
"time"
)
func main(){
//初始化通道
ch11 := make(chan int, 1000)
sign := make(chan int, 1)
//给ch11通道写入数据
for i := 0; i < 1000; i++ {
ch11 <- i
}
//关闭ch11通道
close(ch11)
//单独起一个Goroutine执行select
go func(){
var e int
ok := true
for{
select {
case e,ok = <- ch11:
if !ok {
fmt.Println("End.")
break
}
fmt.Printf("ch11 -> %d\n",e)
}
//通道关闭后退出for循环
if !ok {
sign <- 0
break
}
}
}()
//惯用手法,读取sign通道数据,为了等待select的Goroutine执行。
<- sign
}
~~~
> ch11 -> 0
ch11 -> 1
…
ch11 -> 999
End.
示例二:加以改进,我们不想等到通道被关闭后再退出循环,利用一个辅助通道模拟出操作超时。
~~~
package main
import (
"fmt"
"time"
)
func main(){
//初始化通道
ch11 := make(chan int, 1000)
sign := make(chan int, 1)
//给ch11通道写入数据
for i := 0; i < 1000; i++ {
ch11 <- i
}
//关闭ch11通道
close(ch11)
//我们不想等到通道被关闭之后再推出循环,我们创建并初始化一个辅助的通道,利用它模拟出操作超时行为
timeout := make(chan bool,1)
go func(){
time.Sleep(time.Millisecond) //休息1ms
timeout <- false
}()
//单独起一个Goroutine执行select
go func(){
var e int
ok := true
for{
select {
case e,ok = <- ch11:
if !ok {
fmt.Println("End.")
break
}
fmt.Printf("ch11 -> %d\n",e)
case ok = <- timeout:
//向timeout通道发送元素false后,该case几乎马上就会被执行, ok = false
fmt.Println("Timeout.")
break
}
//终止for循环
if !ok {
sign <- 0
break
}
}
}()
//惯用手法,读取sign通道数据,为了等待select的Goroutine执行。
<- sign
}
~~~
ch11 -> 0
ch11 -> 1
…
ch11 -> 691
Timeout.
示例三:上面实现了单个操作的超时,但是那个超时触发器开始计时有点早。
~~~
package main
import (
"fmt"
"time"
)
func main(){
//初始化通道
ch11 := make(chan int, 1000)
sign := make(chan int, 1)
//给ch11通道写入数据
for i := 0; i < 1000; i++ {
ch11 <- i
}
//关闭ch11通道
//close(ch11),为了看效果先注释掉
//单独起一个Goroutine执行select
go func(){
var e int
ok := true
for{
select {
case e,ok = <- ch11:
if !ok {
fmt.Println("End.")
break
}
fmt.Printf("ch11 -> %d\n",e)
case ok = <- func() chan bool {
//经过大约1ms后,该接收语句会从timeout通道接收到一个新元素并赋值给ok,从而恰当地执行了针对单个操作的超时子流程,恰当地结束当前for循环
timeout := make(chan bool,1)
go func(){
time.Sleep(time.Millisecond)//休息1ms
timeout <- false
}()
return timeout
}():
fmt.Println("Timeout.")
break
}
//终止for循环
if !ok {
sign <- 0
break
}
}
}()
//惯用手法,读取sign通道数据,为了等待select的Goroutine执行。
<- sign
}
~~~
ch11 -> 0
ch11 -> 1
…
ch11 -> 999
Timeout.
# 非缓冲的Channel
> 我们在初始化一个通道时将其容量设置成0,或者直接忽略对容量的设置,那么就称之为非缓冲通道
~~~
ch1 := make(chan int, 1) //缓冲通道
ch2 := make(chan int, 0) //非缓冲通道
ch3 := make(chan int) //非缓冲通道
~~~
- 向此类通道发送元素值的操作会被阻塞,直到至少有一个针对该通道的接收操作开始进行为止。
- 从此类通道接收元素值的操作会被阻塞,直到至少有一个针对该通道的发送操作开始进行为止。
- 针对非缓冲通道的接收操作会在与之相应的发送操作完成之前完成。
对于第三条要特别注意,发送操作在向非缓冲通道发送元素值的时候,会等待能够接收该元素值的那个接收操作。并且确保该元素值被成功接收,它才会真正的完成执行。而缓冲通道中,刚好相反,由于元素值的传递是异步的,所以发送操作在成功向通道发送元素值之后就会立即结束(它不会关心是否有接收操作)。
### 示例一
实现多个Goroutine之间的同步
~~~
package main
import (
"fmt"
"time"
)
func main(){
unbufChan := make(chan int)
//unbufChan := make(chan int, 1) 有缓冲容量
//启用一个Goroutine接收元素值操作
go func(){
fmt.Println("Sleep a second...")
time.Sleep(time.Second)//休息1s
num := <- unbufChan //接收unbufChan通道元素值
fmt.Printf("Received a integer %d.\n", num)
}()
num := 1
fmt.Printf("Send integer %d...\n", num)
//发送元素值
unbufChan <- num
fmt.Println("Done.")
}
~~~
> 缓冲channel输出结果如下:
Send integer 1…
Done.
======================
非缓冲channel输出结果如下:
Send integer 1…
Sleep a second…
Received a integer 1.
Done.
在非缓冲Channel中,从打印数据可以看出主Goroutine中的发送操作在等待一个能够与之配对的接收操作。配对成功后,元素值1才得以经由unbufChan通道被从主Goroutine传递至那个新的Goroutine.
# select与非缓冲通道
与操作缓冲通道的select相比,它被阻塞的概率一般会大很多。只有存在可配对的操作的时候,传递元素值的动作才能真正的开始。
示例:
> 发送操作间隔1s,接收操作间隔2s
分别向unbufChan通道发送小于10和大于等于10的整数,这样更容易从打印结果分辨出配对的时候哪一个case被选中了。下列案例两个case是被随机选择的。
~~~
package main
import (
"fmt"
"time"
)
func main(){
unbufChan := make(chan int)
sign := make(chan byte, 2)
go func(){
for i := 0; i < 10; i++ {
select {
case unbufChan <- i:
case unbufChan <- i + 10:
default:
fmt.Println("default!")
}
time.Sleep(time.Second)
}
close(unbufChan)
fmt.Println("The channel is closed.")
sign <- 0
}()
go func(){
loop:
for {
select {
case e, ok := <-unbufChan:
if !ok {
fmt.Println("Closed channel.")
break loop
}
fmt.Printf("e: %d\n",e)
time.Sleep(2 * time.Second)
}
}
sign <- 1
}()
<- sign
<- sign
}
~~~
default! //无法配对
e: 1
default!//无法配对
e: 3
default!//无法配对
e: 15
default!//无法配对
e: 17
default!//无法配对
e: 9
The channel is closed.
Closed channel.
default case会在收发操作无法配对的情况下被选中并执行。在这里它被选中的概率是50%。
- 上面的示例给予了我们这样一个启发:使用非缓冲通道能够让我们非常方便地在接收端对发送端的操作频率实施控制。
- 可以尝试去掉default case,看看打印结果,代码稍作修改如下:
~~~
package main
import (
"fmt"
"time"
)
func main(){
unbufChan := make(chan int)
sign := make(chan byte, 2)
go func(){
for i := 0; i < 10; i++ {
select {
case unbufChan <- i:
case unbufChan <- i + 10:
}
fmt.Printf("The %d select is selected\n",i)
time.Sleep(time.Second)
}
close(unbufChan)
fmt.Println("The channel is closed.")
sign <- 0
}()
go func(){
loop:
for {
select {
case e, ok := <-unbufChan:
if !ok {
fmt.Println("Closed channel.")
break loop
}
fmt.Printf("e: %d\n",e)
time.Sleep(2 * time.Second)
}
}
sign <- 1
}()
<- sign
<- sign
}
~~~
> e: 0
The 0 select is selected
e: 11
The 1 select is selected
e: 12
The 2 select is selected
e: 3
The 3 select is selected
e: 14
The 4 select is selected
e: 5
The 5 select is selected
e: 16
The 6 select is selected
e: 17
The 7 select is selected
e: 8
The 8 select is selected
e: 19
The 9 select is selected
The channel is closed.
Closed channel.
**总结:上面两个例子,第一个有default case 无法配对时执行该语句,而第二个没有default case ,无法配对case时select将阻塞,直到某个case可以运行(上述示例是直到unbufChan数据被读取操作),不会重新对channel或值进行求值。**
Go语言学习:Channel是什么?
最后更新于:2022-04-01 11:59:16
# Channel是什么
> 在Go语言中,Channel即指通道类型。有时也用它来直接指代可以传递某种类型的值的通道。
### 类型表示法
-
chan T
- 关键字chan代表了通道类型的关键字,T则代表了该通道类型的元素类型。
- 例如:type IntChan chan int 别名类型IntChan代表了元素类型为int的通道类型。我们可以直接声明一个chan int类型的变量:var IntChan chan int,在被初始化后,变量IntChan就可以被用来传递int类型的元素值了。
-
chan<- T
- 只能被用来发送值, <-表示发送操作符
- <-chan T
- 接收通道值, <-表示接收操作符
### 值表示法
### 属性和基本操作
- 基于通道的通讯是在多个Goroutine之间进行同步的重要手段。而针对通道的操作本身也是同步的。
- 在同一时刻,仅有一个Goroutine能向一个通道发送元素值
- 同时也仅有一个Goroutine能从它那里接收元素值。
- 通道相当于一个FIFO先进先出的消息队列。
- 通道中的元素值都具有原子性。它们是不可被分割的。通道中的每一个元素都只可能被某一个Goroutine接收。已被接收的元素值会立刻被从通道中删除。
### 初始化通道
> make(chan int, 10)
~ 表达式初始化了一个通道类型的值。传递给make函数的第一个参数表明此值的具体类型是元素类型为int的通道类型,而第二个参数则指该值在同一时刻最多可以容纳10个元素值。
~~~
package main
import (
"fmt"
)
type Person struct {
Name string
Age uint8
Address Addr
}
type Addr struct{
city string
district string
}
func main(){
persionChan := make(chan Person,1)
p1 := Person{"Harry",32,Addr{"Shanxi","Xian"}}
fmt.Printf("P1 (1): %v\n",p1)
persionChan <- p1
p1.Address.district = "shijingshan"
fmt.Printf("P2 (2): %v\n",p1)
p1_copy := <-persionChan
fmt.Printf("p1_copy: %v\n",p1_copy)
}
~~~
~~~
#go test.go 运行结果
P1 (1): {Harry 32 {Shanxi Xian}}
P2 (2): {Harry 32 {Shanxi shijingshan}}
p1_copy: {Harry 32 {Shanxi Xian}}
~~~
> 通道中的元素值丝毫没有受到外界的影响。这说明了,在发送过程中进行的元素值属于完全复制。这也保证了我们使用通道传递的值的不变性。
### 单向通道
单向channel只能用于发送或者接收数据
> var ch1 chan int // ch1是一个正常的channel,不是单向的
var ch2 chan<- float64// ch2是单向channel,只用于写float64数据
var ch3 <-chan int // ch3是单向channel,只用于读取int数据
channel是一个原生类型,因此不仅 支持被传递,还支持类型转换。只有在介绍了单向channel的概念后,读者才会明白类型转换对于
channel的意义:就是在单向channel和双向channel之间进行转换。
**示例如下:**
ch4 := make(chan int)
ch5 := <-chan int(ch4) // ch5就是一个单向的读取channel
ch6 := chan<- int(ch4) // ch6 是一个单向的写入channel
基于ch4,我们通过类型转换初始化了两个单向channel:单向读的ch5和单向写的ch6。
从设计的角度考虑,所有的代码应该都遵循“最小权限原则”
简单单向channel案例:
~~~
func Parse(ch <-chan int) {
for value := range ch {
fmt.Println("Parsing value", value)
}
}
~~~
### 关闭通道
> close(strChan)
我们应该先明确一点:无论怎么样都不应该在接收端关闭通道。因为在那里我们无法判断发送端是否还会向该通道发送元素值。
如何判断一个channel是否已经被关 闭?我们可以在读取的时候使用多重返回值的方式:
str, ok := strChan,只需要判断第二个bool返回值即可,false表示strChan已经被关闭。
~~~
package main
import (
"fmt"
"time"
)
func main(){
ch := make(chan int, 5)
sign := make(chan int, 2)
go func() {
for i :=0;i<5;i++ {
ch <- i
time.Sleep(1 * time.Second)
}
close(ch)
fmt.Println("The channel is closed.")
sign <- 0
}()
go func() {
for {
e, ok := <-ch
fmt.Printf("%d (%v)\n", e,ok)
if !ok {
break
}
time.Sleep(2 * time.Second)
}
fmt.Println("Done.")
sign <- 1
}()
<- sign
<- sign
}
~~~
> 运行结果:
0 (true)
1 (true)
2 (true)
The channel is closed.
3 (true)
4 (true)
0 (false)
Done.
运行时系统并没有在通道ch被关闭之后立即把false作为相应接收操作的第二个结果,而是等到接收端把已在通道中的所有元素值都接收到之后才这样做。这确保了在发送端关闭通道的安全性。
### 完整示例
~~~
package main
import (
"fmt"
//"time"
)
type Person struct {
Name string
Age uint8
Address Addr
}
type Addr struct{
city string
district string
}
type PersonHandler interface {
Batch(origs <-chan Person) <-chan Person
Handle(orig *Person)
}
//类型声明
type PersonHandlerImpl struct{}
func(handler PersonHandlerImpl) Batch(origs <-chan Person) <-chan Person{
//初始化通道dests
dests := make(chan Person, 100)
go func(){
//需要被更改的人员信息会通过origs单向通道传递进来,那么我们就应该不断的试图从该通道中接收它们。
for p := range origs {
//变更人员信息
handler.Handle(&p)
//把人员信息发送给通道dests
dests <- p
}
fmt.Println("All the information has been handled.")
//关闭通道dests
close(dests)
}()
return dests
}
func(handler PersonHandlerImpl) Handle(orig *Person){
//处理人员信息
if orig.Address.district == "Haidian"{
orig.Address.district = "ShiJingshan"
}
}
func getPersonHandler() PersonHandler{
return PersonHandlerImpl{}
}
var personTotal = 200
var persons []Person = make([]Person, personTotal)
var personCount int
func init(){
//初始化人员信息
for i := 0;i<personTotal;i++{
name := fmt.Sprintf("%s%d","P",i)
p := Person{name,24,Addr{"Beijing","Haidian"}}
persons[i] = p
}
}
func main(){
handler := getPersonHandler()
//初始化通道origs
origs := make(chan Person, 100)
//启用G2以处理人员信息
dests := handler.Batch(origs)
//启用G3以获取人员信息
fecthPerson(origs)
//启用G4以存储人员信息
sign := savePerson(dests)
<- sign
}
//接受一个参数 是只允许写入origs通道
func fecthPerson(origs chan<- Person){
go func(){
for _,p := range persons{
origs <- p
}
fmt.Println("All the information has been fetched.")
close(origs)
}()
}
//接受一个参数 是只允许读取dest通道 除非直接强制转换 要么你只能从channel中读取数据
func savePerson(dest <-chan Person) <-chan byte {
sign := make(chan byte,1)
go func(){
for{
p, ok := <-dest
if !ok {
fmt.Println("All the information has been saved.")
sign <- 0
break
}
savePerson1(p)
}
}()
return sign
}
func savePerson1(p Person) bool {
fmt.Println(p)
return true
}
~~~
运行后结果:
All the information has been fetched.
{P0 24 {Beijing ShiJingshan}}
{P1 24 {Beijing ShiJingshan}}
{P2 24 {Beijing ShiJingshan}}
{P3 24 {Beijing ShiJingshan}}
{P4 24 {Beijing ShiJingshan}}
{P5 24 {Beijing ShiJingshan}}
{P6 24 {Beijing ShiJingshan}}
{P7 24 {Beijing ShiJingshan}}
{P8 24 {Beijing ShiJingshan}}
{P9 24 {Beijing ShiJingshan}}
{P10 24 {Beijing ShiJingshan}}
{P11 24 {Beijing ShiJingshan}}
{P12 24 {Beijing ShiJingshan}}
{P13 24 {Beijing ShiJingshan}}
{P14 24 {Beijing ShiJingshan}}
{P15 24 {Beijing ShiJingshan}}
{P16 24 {Beijing ShiJingshan}}
{P17 24 {Beijing ShiJingshan}}
{P18 24 {Beijing ShiJingshan}}
{P19 24 {Beijing ShiJingshan}}
{P20 24 {Beijing ShiJingshan}}
{P21 24 {Beijing ShiJingshan}}
{P22 24 {Beijing ShiJingshan}}
{P23 24 {Beijing ShiJingshan}}
{P24 24 {Beijing ShiJingshan}}
{P25 24 {Beijing ShiJingshan}}
{P26 24 {Beijing ShiJingshan}}
{P27 24 {Beijing ShiJingshan}}
{P28 24 {Beijing ShiJingshan}}
{P29 24 {Beijing ShiJingshan}}
{P30 24 {Beijing ShiJingshan}}
{P31 24 {Beijing ShiJingshan}}
{P32 24 {Beijing ShiJingshan}}
{P33 24 {Beijing ShiJingshan}}
{P34 24 {Beijing ShiJingshan}}
{P35 24 {Beijing ShiJingshan}}
{P36 24 {Beijing ShiJingshan}}
{P37 24 {Beijing ShiJingshan}}
{P38 24 {Beijing ShiJingshan}}
{P39 24 {Beijing ShiJingshan}}
{P40 24 {Beijing ShiJingshan}}
{P41 24 {Beijing ShiJingshan}}
{P42 24 {Beijing ShiJingshan}}
{P43 24 {Beijing ShiJingshan}}
{P44 24 {Beijing ShiJingshan}}
{P45 24 {Beijing ShiJingshan}}
{P46 24 {Beijing ShiJingshan}}
{P47 24 {Beijing ShiJingshan}}
{P48 24 {Beijing ShiJingshan}}
{P49 24 {Beijing ShiJingshan}}
{P50 24 {Beijing ShiJingshan}}
{P51 24 {Beijing ShiJingshan}}
{P52 24 {Beijing ShiJingshan}}
{P53 24 {Beijing ShiJingshan}}
{P54 24 {Beijing ShiJingshan}}
{P55 24 {Beijing ShiJingshan}}
{P56 24 {Beijing ShiJingshan}}
{P57 24 {Beijing ShiJingshan}}
{P58 24 {Beijing ShiJingshan}}
{P59 24 {Beijing ShiJingshan}}
{P60 24 {Beijing ShiJingshan}}
{P61 24 {Beijing ShiJingshan}}
{P62 24 {Beijing ShiJingshan}}
{P63 24 {Beijing ShiJingshan}}
{P64 24 {Beijing ShiJingshan}}
{P65 24 {Beijing ShiJingshan}}
{P66 24 {Beijing ShiJingshan}}
{P67 24 {Beijing ShiJingshan}}
{P68 24 {Beijing ShiJingshan}}
{P69 24 {Beijing ShiJingshan}}
{P70 24 {Beijing ShiJingshan}}
{P71 24 {Beijing ShiJingshan}}
{P72 24 {Beijing ShiJingshan}}
{P73 24 {Beijing ShiJingshan}}
{P74 24 {Beijing ShiJingshan}}
{P75 24 {Beijing ShiJingshan}}
{P76 24 {Beijing ShiJingshan}}
{P77 24 {Beijing ShiJingshan}}
{P78 24 {Beijing ShiJingshan}}
{P79 24 {Beijing ShiJingshan}}
{P80 24 {Beijing ShiJingshan}}
{P81 24 {Beijing ShiJingshan}}
{P82 24 {Beijing ShiJingshan}}
{P83 24 {Beijing ShiJingshan}}
{P84 24 {Beijing ShiJingshan}}
{P85 24 {Beijing ShiJingshan}}
{P86 24 {Beijing ShiJingshan}}
{P87 24 {Beijing ShiJingshan}}
{P88 24 {Beijing ShiJingshan}}
{P89 24 {Beijing ShiJingshan}}
{P90 24 {Beijing ShiJingshan}}
{P91 24 {Beijing ShiJingshan}}
{P92 24 {Beijing ShiJingshan}}
{P93 24 {Beijing ShiJingshan}}
{P94 24 {Beijing ShiJingshan}}
{P95 24 {Beijing ShiJingshan}}
{P96 24 {Beijing ShiJingshan}}
{P97 24 {Beijing ShiJingshan}}
{P98 24 {Beijing ShiJingshan}}
{P99 24 {Beijing ShiJingshan}}
All the information has been handled.
{P100 24 {Beijing ShiJingshan}}
{P101 24 {Beijing ShiJingshan}}
{P102 24 {Beijing ShiJingshan}}
{P103 24 {Beijing ShiJingshan}}
{P104 24 {Beijing ShiJingshan}}
{P105 24 {Beijing ShiJingshan}}
{P106 24 {Beijing ShiJingshan}}
{P107 24 {Beijing ShiJingshan}}
{P108 24 {Beijing ShiJingshan}}
{P109 24 {Beijing ShiJingshan}}
{P110 24 {Beijing ShiJingshan}}
{P111 24 {Beijing ShiJingshan}}
{P112 24 {Beijing ShiJingshan}}
{P113 24 {Beijing ShiJingshan}}
{P114 24 {Beijing ShiJingshan}}
{P115 24 {Beijing ShiJingshan}}
{P116 24 {Beijing ShiJingshan}}
{P117 24 {Beijing ShiJingshan}}
{P118 24 {Beijing ShiJingshan}}
{P119 24 {Beijing ShiJingshan}}
{P120 24 {Beijing ShiJingshan}}
{P121 24 {Beijing ShiJingshan}}
{P122 24 {Beijing ShiJingshan}}
{P123 24 {Beijing ShiJingshan}}
{P124 24 {Beijing ShiJingshan}}
{P125 24 {Beijing ShiJingshan}}
{P126 24 {Beijing ShiJingshan}}
{P127 24 {Beijing ShiJingshan}}
{P128 24 {Beijing ShiJingshan}}
{P129 24 {Beijing ShiJingshan}}
{P130 24 {Beijing ShiJingshan}}
{P131 24 {Beijing ShiJingshan}}
{P132 24 {Beijing ShiJingshan}}
{P133 24 {Beijing ShiJingshan}}
{P134 24 {Beijing ShiJingshan}}
{P135 24 {Beijing ShiJingshan}}
{P136 24 {Beijing ShiJingshan}}
{P137 24 {Beijing ShiJingshan}}
{P138 24 {Beijing ShiJingshan}}
{P139 24 {Beijing ShiJingshan}}
{P140 24 {Beijing ShiJingshan}}
{P141 24 {Beijing ShiJingshan}}
{P142 24 {Beijing ShiJingshan}}
{P143 24 {Beijing ShiJingshan}}
{P144 24 {Beijing ShiJingshan}}
{P145 24 {Beijing ShiJingshan}}
{P146 24 {Beijing ShiJingshan}}
{P147 24 {Beijing ShiJingshan}}
{P148 24 {Beijing ShiJingshan}}
{P149 24 {Beijing ShiJingshan}}
{P150 24 {Beijing ShiJingshan}}
{P151 24 {Beijing ShiJingshan}}
{P152 24 {Beijing ShiJingshan}}
{P153 24 {Beijing ShiJingshan}}
{P154 24 {Beijing ShiJingshan}}
{P155 24 {Beijing ShiJingshan}}
{P156 24 {Beijing ShiJingshan}}
{P157 24 {Beijing ShiJingshan}}
{P158 24 {Beijing ShiJingshan}}
{P159 24 {Beijing ShiJingshan}}
{P160 24 {Beijing ShiJingshan}}
{P161 24 {Beijing ShiJingshan}}
{P162 24 {Beijing ShiJingshan}}
{P163 24 {Beijing ShiJingshan}}
{P164 24 {Beijing ShiJingshan}}
{P165 24 {Beijing ShiJingshan}}
{P166 24 {Beijing ShiJingshan}}
{P167 24 {Beijing ShiJingshan}}
{P168 24 {Beijing ShiJingshan}}
{P169 24 {Beijing ShiJingshan}}
{P170 24 {Beijing ShiJingshan}}
{P171 24 {Beijing ShiJingshan}}
{P172 24 {Beijing ShiJingshan}}
{P173 24 {Beijing ShiJingshan}}
{P174 24 {Beijing ShiJingshan}}
{P175 24 {Beijing ShiJingshan}}
{P176 24 {Beijing ShiJingshan}}
{P177 24 {Beijing ShiJingshan}}
{P178 24 {Beijing ShiJingshan}}
{P179 24 {Beijing ShiJingshan}}
{P180 24 {Beijing ShiJingshan}}
{P181 24 {Beijing ShiJingshan}}
{P182 24 {Beijing ShiJingshan}}
{P183 24 {Beijing ShiJingshan}}
{P184 24 {Beijing ShiJingshan}}
{P185 24 {Beijing ShiJingshan}}
{P186 24 {Beijing ShiJingshan}}
{P187 24 {Beijing ShiJingshan}}
{P188 24 {Beijing ShiJingshan}}
{P189 24 {Beijing ShiJingshan}}
{P190 24 {Beijing ShiJingshan}}
{P191 24 {Beijing ShiJingshan}}
{P192 24 {Beijing ShiJingshan}}
{P193 24 {Beijing ShiJingshan}}
{P194 24 {Beijing ShiJingshan}}
{P195 24 {Beijing ShiJingshan}}
{P196 24 {Beijing ShiJingshan}}
{P197 24 {Beijing ShiJingshan}}
{P198 24 {Beijing ShiJingshan}}
{P199 24 {Beijing ShiJingshan}}
All the information has been saved.
快速排序Quick sort – golang
最后更新于:2022-04-01 11:59:14
# 算法原理
一趟快速排序的算法是:
1)设置两个变量i、j,排序开始的时候:i=0,j=N-1;
2)以第一个数组元素作为关键数据,赋值给key,即key=A[0];
3)从j开始向前搜索,即由后开始向前搜索(j–),找到第一个小于key的值A[j],将A[j]和A[i]互换;
4)从i开始向后搜索,即由前开始向后搜索(i++),找到第一个大于key的A[i],将A[i]和A[j]互换;
5)重复第3、4步,直到i=j; (3,4步中,没找到符合条件的值,即3中A[j]不小于key,4中A[i]不大于key的时候改变j、i的值,使得j=j-1,i=i+1,直至找到为止。找到符合条件的值,进行交换的时候i, j指针位置不变。另外,i==j这一过程一定正好是i+或j-完成的时候,此时令循环结束)。
> 通过一趟扫描将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列
# golang代码
~~~
//quick.go
package quicksort
//快速排序方式一
func quickSort(values []int, left int, right int){
key := values[left] //取出第一项
p := left
i,j := left, right
for i <= j {
//由后开始向前搜索(j--),找到第一个小于key的值values[j]
for j >= p && values[j] >= key {
j--
}
//第一个小于key的值 赋给 values[p]
if j >= p {
values[p] = values[j]
p = j
}
if values[i] <= key && i <= p {
i ++
}
if i < p{
values[p] = values[i]
p = i
}
values[p] = key
if p - left > 1{
quickSort(values, left, p-1)
}
if right - p > 1 {
quickSort(values, p+1, right)
}
}
}
func QuickSort(values []int){
quickSort(values, 0, len(values) - 1)
}
//快速排序方式二
func quickSort2(sortArray []int, left, right int) {
if left < right {
key := sortArray[(left+right)/2]
i := left
j := right
for {
for sortArray[i] < key {
i++
}
for sortArray[j] > key {
j--
}
if i >= j {
break
}
sortArray[i], sortArray[j] = sortArray[j], sortArray[i]
}
quickSort2(sortArray, left, i-1)
quickSort2(sortArray, j+1, right)
}
}
func QuickSort2(values []int){
quickSort2(values, 0, len(values) - 1)
}
~~~
测试代码
~~~
//quick_test.go
package quicksort
import (
"testing"
)
func TestQuickSort1(t *testing.T){
values := []int{5,4,3,2,1}
QuickSort(values)
if values[0] != 1 || values[1] != 2 || values[2] != 3 || values[3] != 4 ||
values[4] !=5 {
t.Error("QuickSort() failed. Got", values, "Expected 1 2 3 4 5")
}
}
~~~
运行go test结果:
~~~
--- PASS: TestQuickSort1 (0.00s)
PASS
ok goyard/algorithm/quicksort 0.004s
~~~
冒泡排序Bubble sort-golang
最后更新于:2022-04-01 11:59:12
# 算法原理
1. 比较相邻的元素。如果第一个比第二个大,就交换他们两个。
1. 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。
1. 针对所有的元素重复以上的步骤,除了最后一个。
1. 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
> 冒泡排序就是把小的元素往前调或者把大的元素往后调。比较是相邻的两个元素比较,交换也发生在这两个元素之间。所以,如果两个元素相等,我想你是不会再无聊地把他们俩交换一下的;如果两个相等的元素没有相邻,那么即使通过前面的两两交换把两个相邻起来,这时候也不会交换,所以相同元素的前后顺序并没有改变,所以冒泡排序是一种稳定排序算法。
# golang代码
~~~
//bubble.go
package bubble
func BubbleSort(values []int){
flag := true
for i := 0; i < len(values) - 1; i ++ {
flag = true
for j := 0; j < len(values) - i - 1; j ++{
if values[j] > values[j + 1] {
values[j], values[j + 1] = values[j + 1], values[j]
flag = false
}
}
if flag == true {
break
}
}
}
~~~
测试代码
~~~
//bubble_test.go
package bubble
import "testing"
func TestBubbleSort1(t *testing.T){
values := []int{4,1,7,9,2,5,3}
BubbleSort(values)
if values[0] != 1 || values[1] != 2 || values[2] != 3 || values[3] != 4 ||
values[4] !=5 || values[5] !=7 || values[6] !=9 {
t.Error("BubbleSort() failed. Got", values, "Expected 1 2 3 4 5 7 9")
}
}
~~~
运行结果:
~~~
PASS
ok example/bubblesort 0.004s
~~~
![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-05-16_57396c2830f5b.gif "")
golang net/http包使用
最后更新于:2022-04-01 11:59:09
# http客户端
import “net/http”
http包提供了HTTP客户端和服务端的实现。
Get、Head、Post和PostForm函数发出HTTP/ HTTPS请求。
~~~
package main
import (
"fmt"
"io/ioutil"
"net/http"
)
func main() {
response, err := http.Get("http://www.baidu.com")
if err != nil {
// handle error
}
//程序在使用完回复后必须关闭回复的主体。
defer response.Body.Close()
body, _ := ioutil.ReadAll(response.Body)
fmt.Println(string(body))
}
~~~
~~~
package main
import (
"fmt"
"io/ioutil"
"net/http"
"bytes"
)
func main() {
body := "{\"action\":20}"
res, err := http.Post("http://xxx.com", "application/json;charset=utf-8", bytes.NewBuffer([]byte(body)))
if err != nil {
fmt.Println("Fatal error ", err.Error())
}
defer res.Body.Close()
content, err := ioutil.ReadAll(res.Body)
if err != nil {
fmt.Println("Fatal error ", err.Error())
}
fmt.Println(string(content))
}
~~~
还可以使用:
http.Client和http.NewRequest来模拟请求
~~~
package main
import (
"fmt"
"io/ioutil"
"net/http"
"net/url"
"strings"
)
func main() {
v := url.Values{}
v.Set("username", "xxxx")
v.Set("password", "xxxx")
//利用指定的method,url以及可选的body返回一个新的请求.如果body参数实现了io.Closer接口,Request返回值的Body 字段会被设置为body,并会被Client类型的Do、Post和PostFOrm方法以及Transport.RoundTrip方法关闭。
body := ioutil.NopCloser(strings.NewReader(v.Encode())) //把form数据编下码
client := &http.Client{}//客户端,被Get,Head以及Post使用
reqest, err := http.NewRequest("POST", "http://xxx.com/logindo", body)
if err != nil {
fmt.Println("Fatal error ", err.Error())
}
//给一个key设定为响应的value.
reqest.Header.Set("Content-Type", "application/x-www-form-urlencoded;param=value") //必须设定该参数,POST参数才能正常提交
resp, err := client.Do(reqest)//发送请求
defer resp.Body.Close()//一定要关闭resp.Body
content, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Println("Fatal error ", err.Error())
}
fmt.Println(string(content))
}
~~~
# 如何创建web服务端?
~~~
package main
import (
"net/http"
)
func SayHello(w http.ResponseWriter, req *http.Request) {
w.Write([]byte("Hello"))
}
func main() {
http.HandleFunc("/hello", SayHello)
http.ListenAndServe(":8001", nil)
}
~~~
首先调用Http.HandleFunc
按顺序做了几件事:
* 调用了DefaultServerMux的HandleFunc
* 调用了DefaultServerMux的Handle
* 往DefaultServeMux的map[string]muxEntry中增加对应的handler和路由规则
其次调用http.ListenAndServe(“:8001”, nil)
按顺序做了几件事情:
* 实例化Server
* 调用Server的ListenAndServe()
* 调用net.Listen(“tcp”, addr)监听端口
* 启动一个for循环,在循环体中Accept请求
* 对每个请求实例化一个Conn,并且开启一个goroutine为这个请求进行服务go c.serve()
* 读取每个请求的内容w, err := c.readRequest()
* 判断header是否为空,如果没有设置handler(这个例子就没有设置handler),handler就设置为DefaultServeMux
* 调用handler的ServeHttp
* 在这个例子中,下面就进入到DefaultServerMux.ServeHttp
* 根据request选择handler,并且进入到这个handler的ServeHTTP
~~~
mux.handler(r).ServeHTTP(w, r)
~~~
* 选择handler:
~~~
A 判断是否有路由能满足这个request(循环遍历ServerMux的muxEntry)
B 如果有路由满足,调用这个路由handler的ServeHttp
C 如果没有路由满足,调用NotFoundHandler的ServeHttp
~~~
Thrift RPC 使用指南实战(附golang&PHP代码)
最后更新于:2022-04-01 11:59:07
# Thrift RPC 框架指南
### 认识Thrift框架
thrift是一个软件框架,用来进行可扩展且跨语言的服务的开发。它结合了功能强大的软件堆栈和代码生成引擎,以构建在 C++, Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, JavaScript, Node.js, Smalltalk, and OCaml 这些编程语言间无缝结合的、高效的服务。
- **thrift最初由facebook开发,07年四月开放源码,08年5月进入apache孵化器。**
- **thrift允许定义一个简单的定义文件中的数据类型和服务接口,以作为输入文件,编译器生成代码用来方便地生成RPC客户端和服务器通信的无缝跨编程语言。**
- **类似Thrift的工具,还有Avro、protocol buffer,但相对于Thrift来讲,都没有Thrift支持全面和使用广泛。**
### Thrift自下到上可以分为4层
Server(single-threaded, event-driven etc)服务器进程调度Processor(compiler generated)RPC接口处理函数分发,IDL定义接口的实现将挂接到这里面Protocol (JSON, compact etc)协议Transport(raw TCP, HTTP etc)网络传输> Thrift实际上是实现了C/S模式,通过代码生成工具将接口定义文件生成服务器端和客户端代码(可以为不同语言),从而实现服务端和客户端跨语言的支持。用户在Thirft描述文件中声明自己的服务,这些服务经过编译后会生成相应语言的代码文件,然后用户实现服务(客户端调用服务,服务器端提服务)便可以了。其中protocol(协议层, 定义数据传输格式,可以为二进制或者XML等)和transport(传输层,定义数据传输方式,可以为TCP/IP传输,内存共享或者文件共享等)被用作运行时库。
### Thrift支持的传输及服务模型
### 支持的传输格式:
| 参数 | 描述 |
|-----|-----|
| TBinaryProtocol | 二进制格式 |
| TCompactProtocol | 压缩格式 |
| TJSONProtocol | JSON格式 |
| TSimpleJSONProtocol | 提供JSON只写协议, 生成的文件很容易通过脚本语言解析。 |
| TDebugProtocol | 使用易懂的可读的文本格式,以便于debug |
### 支持的数据传输方式:
| 参数 | 描述 |
|-----|-----|
| TSocket | 阻塞式socker |
| TFramedTransport | 以frame为单位进行传输,非阻塞式服务中使用。 |
| TFileTransport | 以文件形式进行传输。 |
| TMemoryTransport | 将内存用于I/O. java实现时内部实际使用了简单的ByteArrayOutputStream。 |
| TZlibTransport | 使用zlib进行压缩, 与其他传输方式联合使用。当前无java实现。 |
### 支持的服务模型:
| 参数 | 描述 |
|-----|-----|
| TSimpleServer | 简单的单线程服务模型,常用于测试 |
| TThreadPoolServer | 多线程服务模型,使用标准的阻塞式IO。 |
| TNonblockingServer | 多线程服务模型,使用非阻塞式IO(需使用TFramedTransport数据传输方式) |
# Thrift 下载及安装
### 如何获取Thrift
1. 官网:[http://thrift.apache.org/](http://thrift.apache.org/)
1. golang的Thrift包:
~~~
go get git.apache.org/thrift.git/lib/go/thrift
~~~
### 如何安装Thrift
mac下安装Thrift,[参考上一篇介绍](http://blog.csdn.net/liuxinmingcode/article/details/45567241)
其他平台安装自行挖掘,呵呵。
安装后通过
~~~
liuxinmingMacBook-Rro#:thrift -version
Thrift version 0.9.2 #看到这一行表示安装成功
~~~
# Golang、PHP通过Thrift调用
> 先发个官方各种语言DEMO地址 [https://git1-us-west.apache.org/repos/asf?p=thrift.git;a=tree;f=tutorial;h=d69498f9f249afaefd9e6257b338515c0ea06390;hb=HEAD](https://git1-us-west.apache.org/repos/asf?p=thrift.git;a=tree;f=tutorial;h=d69498f9f249afaefd9e6257b338515c0ea06390;hb=HEAD)
### Thrift的协议库IDL文件
### 语法参考
参考资料
[http://www.cnblogs.com/tianhuilove/archive/2011/09/05/2167669.html](http://www.cnblogs.com/tianhuilove/archive/2011/09/05/2167669.html)
[http://my.oschina.net/helight/blog/195015](http://my.oschina.net/helight/blog/195015)
#### 基本类型
- bool: **布尔值 (true or false), one byte**
- byte: **有符号字节**
- i16: **16位有符号整型**
- i32: **32位有符号整型**
- i64: **64位有符号整型**
- double: **64位浮点型**
- string: **Encoding agnostic text or binary string**
> 基本类型中基本都是有符号数,因为有些语言没有无符号数,所以Thrift不支持无符号整型。
#### 特殊类型
- binary: **Blob (byte array) a sequence of unencoded bytes**
> 这是string类型的一种变形,主要是为java使用
#### struct结构体
thrift中struct是定义为一种对象,和面向对象语言的class差不多.,但是struct有以下一些约束:
**struct不能继承,但是可以嵌套,不能嵌套自己。**
1. 其成员都是有明确类型
2. 成员是被正整数编号过的,其中的编号使不能重复的,这个是为了在传输过程中编码使用。
3. 成员分割符可以是逗号(,)或是分号(;),而且可以混用,但是为了清晰期间,建议在定义中只使用一种,比如C++学习者可以就使用分号(;)。
4. 字段会有optional和required之分和protobuf一样,但是如果不指定则为无类型–可以不填充该值,但是在序列化传输的时候也会序列化进去,
optional是不填充则部序列化。
required是必须填充也必须序列化。
5. 每个字段可以设置默认值
6. 同一文件可以定义多个struct,也可以定义在不同的文件,进行include引入。
~~~
struct Work {
1: i32 num1 = 0,
2: i32 num2,
3: Operation op,
4: optional string comment,
}
~~~
#### 容器(Containers)
Thrift3种可用容器类型:
- list(t): 元素类型为t的有序表,容许元素重复。
- set(t):元素类型为t的无序表,不容许元素重复。对应c++中的set,java中的HashSet,python中的set,php中没有set,则转换为list类型。
- map(t,t): 键类型为t,值类型为t的kv对,键不容许重复。对用c++中的map, Java的HashMap, PHP 对应 array, Python/Ruby 的dictionary。
> 容器中元素类型可以是除了service外的任何合法Thrift类型(包括结构体和异常)。为了最大的兼容性,map的key最好是thrift的基本类型,有些语言不支持复杂类型的key,JSON协议只支持那些基本类型的key。
> 容器都是同构容器,不失异构容器。
### 实现Thrift TDL文件
batu.thrift文件:
~~~
/**
* BatuThrift TDL
* @author liuxinming
* @time 2015.5.13
*/
namespace go batu.demo
namespace php batu.demo
/**
* 结构体定义
*/
struct Article{
1: i32 id,
2: string title,
3: string content,
4: string author,
}
const map<string,string> MAPCONSTANT = {'hello':'world', 'goodnight':'moon'}
service batuThrift {
list<string> CallBack(1:i64 callTime, 2:string name, 3:map<string, string> paramMap),
void put(1: Article newArticle),
}
~~~
### 编译IDL文件,生成相关代码
~~~
thrift -r --gen go batu.thrift
thrift -r --gen php batu.thrift
thrift -r --gen php:server batu.thrift #生成PHP服务端接口代码有所不一样
~~~
### Golang Service 实现
1.先按照golang的Thrift包
> go get git.apache.org/thrift.git/lib/go/thrift
2.将Thrift生成的开发库复制到GOPATH中
> cp -r /Users/liuxinming/wwwroot/testphp/gen-go/batu $GOPATH/src
3.开发Go server端代码(后面的代码,目录我们放在$GOPATH/src/thrift 中运行和演示)
test.go文件:
~~~
package main
import (
"batu/demo" #注意导入Thrift生成的接口包
"fmt"
"git.apache.org/thrift.git/lib/go/thrift"
"os"
"time"
)
const (
NetworkAddr = "127.0.0.1:9090" #监听地址&端口
)
type batuThrift struct {
}
func (this *batuThrift) CallBack(callTime int64, name string, paramMap map[string]string) (r []string, err error) {
fmt.Println("-->from client Call:", time.Unix(callTime, 0).Format("2006-01-02 15:04:05"), name, paramMap)
r = append(r, "key:"+paramMap["a"]+" value:"+paramMap["b"])
return
}
func (this *batuThrift) Put(s *demo.Article) (err error) {
fmt.Printf("Article--->id: %d\tTitle:%s\tContent:%t\tAuthor:%d\n", s.Id, s.Title, s.Content, s.Author)
return nil
}
func main() {
transportFactory := thrift.NewTFramedTransportFactory(thrift.NewTTransportFactory())
protocolFactory := thrift.NewTBinaryProtocolFactoryDefault()
//protocolFactory := thrift.NewTCompactProtocolFactory()
serverTransport, err := thrift.NewTServerSocket(NetworkAddr)
if err != nil {
fmt.Println("Error!", err)
os.Exit(1)
}
handler := &batuThrift{}
processor := demo.NewBatuThriftProcessor(handler)
server := thrift.NewTSimpleServer4(processor, serverTransport, transportFactory, protocolFactory)
fmt.Println("thrift server in", NetworkAddr)
server.Serve()
}
~~~
4.运行go服务端(监听9090端口)
> liuxinmingdeMacBook-Pro:thrift liuxinming$ go run test.go
thrift server in 127.0.0.1:9090
至此Go的Thrift服务端OK.
### Golang Client 实现
goClient.go文件:
~~~
package main
import (
"batu/demo"
"fmt"
"git.apache.org/thrift.git/lib/go/thrift"
"net"
"os"
"strconv"
"time"
)
const (
HOST = "127.0.0.1"
PORT = "9090"
)
func main() {
startTime := currentTimeMillis()
transportFactory := thrift.NewTFramedTransportFactory(thrift.NewTTransportFactory())
protocolFactory := thrift.NewTBinaryProtocolFactoryDefault()
transport, err := thrift.NewTSocket(net.JoinHostPort(HOST, PORT))
if err != nil {
fmt.Fprintln(os.Stderr, "error resolving address:", err)
os.Exit(1)
}
useTransport := transportFactory.GetTransport(transport)
client := demo.NewBatuThriftClientFactory(useTransport, protocolFactory)
if err := transport.Open(); err != nil {
fmt.Fprintln(os.Stderr, "Error opening socket to "+HOST+":"+PORT, " ", err)
os.Exit(1)
}
defer transport.Close()
for i := 0; i < 10; i++ {
paramMap := make(map[string]string)
paramMap["a"] = "batu.demo"
paramMap["b"] = "test" + strconv.Itoa(i+1)
r1, _ := client.CallBack(time.Now().Unix(), "go client", paramMap)
fmt.Println("GOClient Call->", r1)
}
model := demo.Article{1, "Go第一篇文章", "我在这里", "liuxinming"}
client.Put(&model)
endTime := currentTimeMillis()
fmt.Printf("本次调用用时:%d-%d=%d毫秒\n", endTime, startTime, (endTime - startTime))
}
func currentTimeMillis() int64 {
return time.Now().UnixNano() / 1000000
}
~~~
goClient运行后结果:
> liuxinmingdeMacBook-Pro:thrift liuxinming$ go run goClient.go
GOClient Call-> [key:batu.demo value:test1]
GOClient Call-> [key:batu.demo value:test2]
GOClient Call-> [key:batu.demo value:test3]
GOClient Call-> [key:batu.demo value:test4]
GOClient Call-> [key:batu.demo value:test5]
GOClient Call-> [key:batu.demo value:test6]
GOClient Call-> [key:batu.demo value:test7]
GOClient Call-> [key:batu.demo value:test8]
GOClient Call-> [key:batu.demo value:test9]
GOClient Call-> [key:batu.demo value:test10]
本次调用用时:1431583140857-1431583140855=2毫秒
### PHP Client 实现
1. 首先去下载Thrift,git库地址为:[https://github.com/apache/thrift](https://github.com/apache/thrift)
1. 新建项目目录testphp,然后把thrift/lib/php/lib复制到testphp目录下面
1. 复制生成的gen-php到testphp目录下面
1. 客户端代码
~~~
<?php
/**
* Thrift RPC - PHPClient
* @author liuxinming
* @time 2015.5.13
*/
namespace batu\testDemo;
header("Content-type: text/html; charset=utf-8");
$startTime = getMillisecond();//记录开始时间
$ROOT_DIR = realpath(dirname(__FILE__).'/');
$GEN_DIR = realpath(dirname(__FILE__).'/').'/gen-php';
require_once $ROOT_DIR . '/Thrift/ClassLoader/ThriftClassLoader.php';
use Thrift\ClassLoader\ThriftClassLoader;
use Thrift\Protocol\TBinaryProtocol;
use Thrift\Transport\TSocket;
use Thrift\Transport\TSocketPool;
use Thrift\Transport\TFramedTransport;
use Thrift\Transport\TBufferedTransport;
$loader = new ThriftClassLoader();
$loader->registerNamespace('Thrift',$ROOT_DIR);
$loader->registerDefinition('batu\demo', $GEN_DIR);
$loader->register();
$thriftHost = '127.0.0.1'; //UserServer接口服务器IP
$thriftPort = 9090; //UserServer端口
$socket = new TSocket($thriftHost,$thriftPort);
$socket->setSendTimeout(10000);#Sets the send timeout.
$socket->setRecvTimeout(20000);#Sets the receive timeout.
//$transport = new TBufferedTransport($socket); #传输方式:这个要和服务器使用的一致 [go提供后端服务,迭代10000次2.6 ~ 3s完成]
$transport = new TFramedTransport($socket); #传输方式:这个要和服务器使用的一致[go提供后端服务,迭代10000次1.9 ~ 2.1s完成,比TBuffer快了点]
$protocol = new TBinaryProtocol($transport); #传输格式:二进制格式
$client = new \batu\demo\batuThriftClient($protocol);# 构造客户端
$transport->open();
$socket->setDebug(TRUE);
for($i=1;$i<11;$i++){
$item = array();
$item["a"] = "batu.demo";
$item["b"] = "test"+$i;
$result = $client->CallBack(time(),"php client",$item); # 对服务器发起rpc调用
echo "PHPClient Call->".implode('',$result)."<br>";
}
$s = new \batu\demo\Article();
$s->id = 1;
$s->title = '插入一篇测试文章';
$s->content = '我就是这篇文章内容';
$s->author = 'liuxinming';
$client->put($s);
$s->id = 2;
$s->title = '插入二篇测试文章';
$s->content = '我就是这篇文章内容';
$s->author = 'liuxinming';
$client->put($s);
$endTime = getMillisecond();
echo "本次调用用时: :".$endTime."-".$startTime."=".($endTime-$startTime)."毫秒<br>";
function getMillisecond() {
list($t1, $t2) = explode(' ', microtime());
return (float)sprintf('%.0f', (floatval($t1) + floatval($t2)) * 1000);
}
$transport->close();
~~~
PHP运行后结果:
PHPClient Call->key:batu.demo value:1
PHPClient Call->key:batu.demo value:2
PHPClient Call->key:batu.demo value:3
PHPClient Call->key:batu.demo value:4
PHPClient Call->key:batu.demo value:5
PHPClient Call->key:batu.demo value:6
PHPClient Call->key:batu.demo value:7
PHPClient Call->key:batu.demo value:8
PHPClient Call->key:batu.demo value:9
PHPClient Call->key:batu.demo value:10
本次调用用时: :1431582183296-1431582183290=6毫秒
Go服务端看到打印数据:
–>from client Call: 2015-05-13 22:43:03 php client map[a:batu.demo b:1]
–>from client Call: 2015-05-13 22:43:03 php client map[a:batu.demo b:2]
–>from client Call: 2015-05-13 22:43:03 php client map[a:batu.demo b:3]
–>from client Call: 2015-05-13 22:43:03 php client map[a:batu.demo b:4]
–>from client Call: 2015-05-13 22:43:03 php client map[a:batu.demo b:5]
–>from client Call: 2015-05-13 22:43:03 php client map[a:batu.demo b:6]
–>from client Call: 2015-05-13 22:43:03 php client map[a:batu.demo b:7]
–>from client Call: 2015-05-13 22:43:03 php client map[b:8 a:batu.demo]
–>from client Call: 2015-05-13 22:43:03 php client map[a:batu.demo b:9]
–>from client Call: 2015-05-13 22:43:03 php client map[a:batu.demo b:10]
Article—>id: 1 Title:插入一篇测试文章 Content:我就是这篇文章内容 Author:liuxinming
Article—>id: 2 Title:插入二篇测试文章 Content:我就是这篇文章内容 Author:liuxinming
完结,至此一个Golang的Thrift服务端 和 PHP的Thrift客户端完成!
Mac install Thrift
最后更新于:2022-04-01 11:59:05
系统当前环境:OS X Yosemite 10.10.3
折腾时间:2015.5.7
刚开始尝试通过官方[http://thrift.apache.org/](http://thrift.apache.org/) 去编译安装,过程遇到坑无数。
其中提示bison版本过低,原因:Xcode中自带的bison是2.3的,而Thrift 0.9.2版本需要bison > 2.5版本。 折腾半天没解决
好人提供解决思路:
把xcode bison 改个名字,重新编译,然后再把名字改回来。 据说就好了! 本人未测试过。
最方便简单暴力的方式是用:brew
[https://github.com/apache/thrift](https://github.com/apache/thrift)
官方的安装实在是太麻烦了,我使用brew安装变得特别简单 [http://stackoverflow.com/questions/23455499/cant-install-thrift-on-mac-os-10-9-2](http://stackoverflow.com/questions/23455499/cant-install-thrift-on-mac-os-10-9-2)
先 sudo brew update
再 sudo brew install thrift
安装完成后,直接在
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-05-16_57396c27ed8e0.jpg)
并且系统还会在 usr/local/bin下面增加一个链接
(由于我自己将我们自己生成的thrift命令拷贝到了usr/local/bin下,所以会出现一个警告,说我已经有一个链接了,问我是否覆盖 ,覆盖用命令 brew link --overwrite thrift)
使用这个命令,直接覆盖了,生成一个软连接,到thrift
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-05-16_57396c28165e9.jpg)
使用的话 可以先用 thrift -help来查看命令
Mac OS 安装golang
最后更新于:2022-04-01 11:59:03
#下载golang安装包
下载地址:
http://www.golangtc.com/download
https://code.google.com/p/go/downloads/list
| go1.4.darwin-amd64-osx10.8.pkg | go1.4 Mac OS X (x86 64-bit) PKG installer |
|-----|-----|
# 设置环境变量
配置 GOROOT 和 GOPATH:
~~~
创建目录下的go文件夹: mkdir ~/go
下面的东西放到.bash_rc(也可能是.bash_profile或者.zshrc)
export GOROOT=/usr/local/go
export GOPATH=$HOME/go
export PATH=$PATH:$GOROOT/bin
~~~
下载pkg在mac下双击安装即可,超简单,默认安装路径:/usr/local/go
~~~
source ~/.bash_profile
~~~
至此golang安装完成,我们体验下吧:
~~~
go env
go version
~~~
~~~
运行结果:
liuxinmingdeMacBook-Pro:gotest liuxinming$ go env
GOARCH="amd64"
GOBIN=""
GOCHAR="6"
GOEXE=""
GOHOSTARCH="amd64"
GOHOSTOS="darwin"
GOOS="darwin"
GOPATH="/Users/liuxinming/go"
GORACE=""
GOROOT="/usr/local/go"
GOTOOLDIR="/usr/local/go/pkg/tool/darwin_amd64"
CC="clang"
GOGCCFLAGS="-fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fno-common"
CXX="clang++"
CGO_ENABLED="1"
===========华丽分割线===============
liuxinmingdeMacBook-Pro:gotest liuxinming$ go version
go version go1.4 darwin/amd64
liuxinmingdeMacBook-Pro:gotest liuxinming$
~~~
# 开发工具配置(Sublime Text 2)
Sublime Text 2 下载地址: http://www.sublimetext.com/2
下载完后直接安装即可。
Sublime Text 2可以免费使用,只是保存次数达到一定数量之后就会提示是否购买,点击取消可以继续用,和正式注册版本没有任何区别。
# 开始golang旅程
Command + Shift + P 打开Package Control,然后输入Go , 回车(这样就会出现代码提示了)
新建一个hello.go
~~~
package main
import "fmt"
func main() {
fmt.Printf("hello, world\n")
}
~~~
对于编写好的文件,使用快捷键Command + B 打开Sublime Text 2的终端,输入go build (名称)对其进行编译:【注:我测试,直接快捷键就自动编译啦,不需要任何输入】
问题来啦,默认安装的Sublime没有 go build ,因此需要自己创建一个。
# 为Sublime Text2添加Go语言的Build
Tools -> Build System -> New Build System
输入如下代码:
~~~
{
"cmd" : [ "/usr/local/go/bin/go" , "run" , "$file" ],
"file_regex" : "^(...*?):([0-9]*):?([0-9]*)" ,
"working_dir" : "${file_path}" ,
"selector" : "source.go"
}
~~~
然后选择Build的文件:go
然后Command + B 就有输出了。
Ubuntu 14.04/CentOS 6.5中安装GO LANG(GO语言)
最后更新于:2022-04-01 11:59:00
解压缩
tar zxvf go1.2.2.linux-amd64.tar.gz
移动(要有ROOT权限)
mv go /usr/local/go
以下全部都需要ROOT权限
1.Ubuntu 14.04安装个GO Lang(go 语言)
1.1设置系统环境变量
vi /etc/profile
在最后添加(按i键进入编辑状态,把光标拉倒最后,添加如下,最后按 :wq 键保存退出,其中:q! 是不保存强制退出)
export GOROOT=/usr/local/go
export GOBIN=$GOROOT/bin
export GOPKG=$GOROOT/pkg/tool/linux_amd64
export GOARCH=amd64
export GOOS=linux
export PATH=.:$PATH:$GOBIN:$GOPKG
1.2编译,使其生效
source /etc/profile
1.3测试
如果能看见版本号,那么配置成功!
go version
go version go1.2.2 linux/amd64
1.4 其他git和mercurial 安装
sudo apt-get install git
sudo apt-get install mercurial
在使用中发现,如果不安装以上两个,就得自己手动去下载一些文件
2.CentOS 6.5 安装 GO Lang (GO语言)
2.1设置系统环境变量
vi /etc/profile
在最后添加(按i键进入编辑状态,把光标拉倒最后,添加如下,最后按 :wq 键保存退出,其中:q! 是不保存强制退出)
export GOROOT=/usr/local/go
export GOBIN=$GOROOT/bin
export GOPKG=$GOROOT/pkg/tool/linux_amd64
export GOARCH=amd64
export GOOS=linux
export PATH=.:$PATH:$GOBIN:$GOPKG
2.2编译,使其生效
source/etc/profile
2.3测试
如果能看见版本号,那么配置成功!
go version
go version go1.2.2 linux/amd64
Go语言学习四:struct类型
最后更新于:2022-04-01 11:58:58
# struct
我们可以声明新的类型,作为其它类型的属性或字段容器。
如,创建一个自定义类型person代表一个人的实体。这个实体拥有属性:姓名&年龄。这样的类型我们称之为struct。
~~~
type person struct{
name string
age int
}
~~~
~~~
var P person // P 现在就是 person 类型的变量了
P.name = "Astaxie" // 赋值 "Astaxie" 给 P 的 name 属性 .
P.age = 25 // 赋值 "25" 给变量 P 的 age 属性
fmt.Printf("The person's name is %s", P.name) // 访问 P 的 name 属性 .
~~~
除了上面这种P的声明使用之外,还有其他两种声明使用方式
1、按照顺序提供初始化值
~~~
P := person{"Liuxinming", 28}
~~~
2、通过field:value的方式初始化,这样可以任意顺序
~~~
P := person{age:28, name:"Liuxinming"}
~~~
举例:
~~~
package main
import "fmt"
//声明一个新的类型
type person struct {
name string
avg int
}
//比较两个人的年龄,返回年龄大的那个人,并且返回年龄差
//struct 也是传值的
func older(p1, p2 person) (person, int) {
if p1.avg > p2.avg { //比较p1和p2年龄
return p1, p1.avg - p2.avg
}
return p2, p2.avg - p1.avg
}
func main() {
var tom person
//赋值初始化
tom.name, tom.avg = "Tom", 18
//两个字段都写清楚的初始化
bob := person{avg: 25, name: "Bob"}
//按照struct定义顺序初始化
paul := person{"Paul", 43}
tb_Older, tb_diff := older(tom, bob)
tp_Older, tp_diff := older(tom, paul)
bp_Older, bp_diff := older(bob, paul)
fmt.Printf("Of %s and %s, %s is older by %d years\n",
tom.name, bob.name, tb_Older.name, tb_diff)
fmt.Printf("Of %s and %s, %s is older by %d years\n",
tom.name, paul.name, tp_Older.name, tp_diff)
fmt.Printf("Of %s and %s, %s is older by %d years\n",
bob.name, paul.name, bp_Older.name, bp_diff)
}
~~~
输出结果如下:
~~~
Of Tom and Bob, Bob is older by 7 years
Of Tom and Paul, Paul is older by 25 years
Of Bob and Paul, Paul is older by 18 years
~~~
# struct的匿名字段
我们上面介绍了如何定义一个struct,定义的时候是字段名与其类型一一对应,实际上Go支持只提供类型,而不写字段名的方式,也就是匿名字段,也称为嵌入字段。
当匿名字段是一个struct的时候,那么这个struct所拥有的全部字段都被隐式地引入了当前定义的这个struct
~~~
// struct2.go
package main
import (
"fmt"
)
type Human struct {
name string
age int
weight int
}
type Student struct {
Human //匿名字段,那么默认struct就包含了Human所有字段
speciality string
}
func main() {
//初始化一个学生
mark := Student{Human{"Mark", 25, 100}, "Computer Science"}
//访问相应的字段
fmt.Println("His name is ", mark.name)
fmt.Println("His age is ", mark.age)
fmt.Println("His weight is ", mark.weight)
fmt.Println("His speciality is ", mark.speciality)
//修改对应的信息
mark.speciality = "AI"
fmt.Println("Mark changed his speciality")
fmt.Println("His speciality is ", mark.speciality)
// 修改他的年龄信息
fmt.Println("Mark become old")
mark.age = 46
fmt.Println("His age is", mark.age)
// 修改他的体重信息
fmt.Println("Mark is not an athlet anymore")
mark.weight += 60
fmt.Println("His weight is", mark.weight)
}
~~~
输出结果:
~~~
His name is Mark
His age is 25
His weight is 100
His speciality is Computer Science
Mark changed his speciality
His speciality is AI
Mark become old
His age is 46
Mark is not an athlet anymore
His weight is 160
~~~
Go语言学习三:Go基础(iota,array,slice,map,make,new)
最后更新于:2022-04-01 11:58:56
# 分组声明
~~~
import "fmt"
import "os"
const i = 100
const ii = 3.1415
const iii = "test"
var i int
var ii float64
var iii string
//分组代码如下
import(
"fmt"
"os"
)
const(
i = 1
ii = 3.1415
iii = "test"
)
var (
i int
ii float64
iii string
)
~~~
# iota枚举
这个关键字用来声明enum的时候用,它默认开始值是0,每调用一次+1.
~~~
package main
import "fmt"
const (
x = iota
y = iota
z = iota
w
)
const v = iota //每遇到const关键字,iota就会重置
func main() {
fmt.Printf("x=%d\ny=%d\nz=%d\nw=%d\nv=%d", x, y, z, w, v)
}
~~~
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-05-16_57396c27ad6df.jpg)
**大写字母开头的变量是可导出的,即其他包可以读取,是公用变量**
**小写字母开头的不可导出,是私有变量**
**大写字母开头的函数也一样,相当于class中带public关键字词的公有函数**
**小写字母开头的函数,就是有private关键词的私有函数**
# array数组
在[n]array中,
n表示数组的长度
type表示存储元素的类型
~~~
var arr [n]array
~~~
~~~
package main
import "fmt"
func main() {
var arr [5]int //声明了一个int类型的数值
arr[0] = 10
arr[4] = 20
fmt.Printf("This first is %d\n", arr[0])
fmt.Printf("This last is %d\n", arr[4])
}
~~~
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-05-16_57396c27bdfc4.jpg)
说明:长度也是数组类型的一部分,因此[5]int与[10]int是不同的类型,数组也就不能改变长度。
如果使用指针,就需要用到slice类型
~~~
package main
import "fmt"
func main() {
a := [5]int{1,2,3,4,5} //声明了一个长度为5的int数组
b := [...]int{7,8,9} //可以省略长度,`...`的方式,会自动根据元素个数来计算长度
c := [2][4]int{1,2,3,4},{5,6,7,9} //声明了一个二维数组,两个数组作为元素,每个数组中又有4个int类型的元素
}
~~~
# slice
在初始定义数组时,我们并不知道需要多大的数组,因此我们就需要“动态数组”,这种数据结构叫slice。
slice并不是真正意义上的动态数组,而是一个引用类型。
slice总是指向一个底层array
~~~
//声明和array一样,只是少了长度
var a []int
~~~
slice通过 array[i:j]来获取,其中i是数组的开始位置,j是结束为止,但不包含array[j],
~~~
//声明一个含有5个元素类型为byte的数组
test := [5]byte{'a', 'b', 'c', 'd', 'e'}
//声明两个含有byte的slice
var a, b []byte
a = test[1:3] //test[1],test[2]
b = test[3:5] //test[3],test[4]
~~~
1、slice的默认开始位置0,test[:n]等价于test[0:n]
2、slice的第二个序列默认是数组的长度,test[n:] 等价于 test[n:len[test]]
3、如果直接获取slice,可以使用test[:]
# map
map读取和设置也类似slice一样,通过key来操作,只是slice的index只能是int类型,而map多了很多类型,可以是int,string及所有完全定义了 == 与 != 操作的类型。
~~~
package main
import "fmt"
func main() {
//声明一个key是字符串,值为int的字典,这种声明需要在使用之前使用make初始化
//var numbers map[string]int
//另一种map的声明方式
numbers := make(map[string]int)
numbers["test"] = 1
numbers["test1"] = 2
numbers["test2"] = 3
fmt.Println("test2:", numbers["test2"])
}
~~~
### map的初始化
可以通过key:val的方式初始化值,同时map内置有判断是否存在key的方式
~~~
package main
import "fmt"
func main() {
//初始化一个字典
test := map[string]float32{"a": 1, "b": 2, "c": 3}
//map有两个返回值,第二个返回值,如果不存在key返回flase,如果存在key返回true
testArr, result := test["a"]
if result {
fmt.Println("value:", testArr)
} else {
fmt.Println("key不存在")
}
}
~~~
### 通过delete删除map元素
~~~
delete(testArr,"a") //删除key为a的元素
~~~
### map是一种引用类型
如果两个map同时指向一个底层,那么一个改变,另一个也相应改变
~~~
package main
import "fmt"
func main() {
m := make(map[string]string)
m["test"] = "test"
m1 := m
m1["test"] = "change test" //现在,m["test"]的值也已经是chage test了
fmt.Printf("改变后的test:%s", m["test"])
}
~~~
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-05-16_57396c27cf896.jpg)
# make,new操作
### make
make用于内建类型(map、slice、channel)的内存分配
make(T,args)与new(T)有着不同的功能,make只能创建slice,map,channel,并且返回一个有初始值(非零)的T类型,而不是*T
### new
new用于各种类型的内存分配【new返回指针】
new(T)分配了零值填充的T类型的内存空间,并且返回其地址,即一个*T类型的值(GO语言的术语:返回了一个指针,指向新分配的类型T的零值)
**总结:**
new 负责分配内存,new(T) 返回*T 指向一个零值 T 的指针
make 负责初始化值,make(T) 返回初始化后的 T ,而非指针
最重要的一点:make 仅适用于slice,map 和channel
关于“零值”,并非是空值,而是一种“变量未填充前”的默认值,通常为0,如下
~~~
int 0
int8 0
int32 0
int64 0
uint 0x0
rune 0 //rune的实际类型是 int32
byte 0x0 //byte的实际类型是uint8
float32 0 //长度为4 byte
float64 0 //长度为8 byte
bool false
string ""
~~~
Go语言学习二:Go基础(变量、常量、数值类型、字符串、错误类型)
最后更新于:2022-04-01 11:58:53
# 定义变量
使用var关键字是go语言最基本的定义变量方式,与C语言不通的是Go语言把变量放在变量名后面。
~~~
//定义一个"liuxinming"变量,类型为"type"的变量
var liuxinming type
~~~
~~~
//定义多个"type"类型变量
var name1,name2,name3 type
~~~
~~~
//初始化liuxinming的变量为"value"值,类型是"type"
var liuxinming type = value
~~~
~~~
//定义三个类型为"type"的变量,并且初始化
var name1,name2,name3 type = 1,2,3
~~~
~~~
//简化上面的语句
name1,name2,name3 := 1,2,3
~~~
**说明:**
":="这个符号直接取代了var 和 type,这种形式叫做简短声明,不过它有一个限制,它只能用在函数内部(在函数外部使用则会无法编译通过)。
所以一般用var方式来定义全局变量。
"_" 是一个特殊的变量名,任何赋予它的值都会被丢弃
~~~
_,b = 1,2
//b被赋予2,并同时丢弃1
~~~
Go预约对于已声明但未使用的变量会在编译阶段报错,如下
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-05-16_57396c2761cb6.jpg)
~~~
package main
func main(){
var i int//声明了i,但未使用
}
~~~
# 常量
在程序编译阶段就确定下来的值,而程序在运行时则无法改变该值,在GO语言中,常量可以定义为数值、布尔值或字符串等类型
~~~
const constantname = value
//如果需要,可以明确指定常量类型
const Pi float32 = 3.1415926
const i = 10000
const MaxThread = 10
const prefix = "astaxie_"
~~~
# Boolean类型
布尔值的类型bool,值为true或false,默认false.
~~~
//实例代码
var isActvie bool //全局变量声明
var enabled,disabled = true, false//忽略类型的声明
func test(){
var available bool //一般声明
valid := false //简短声明
available = true //赋值操作
}
~~~
# 数值类型
### 整数类型:
1、有符合
2、无符号
Go语言同时支持int 和 uint,这两种类型的长度相同,但具体长度取决于不同编译器的实现。
Go预约也有直接定义好位数的类型:
rune(int32的别称)
int8
int16
int32
int64
byte(uint8的别称)
uint8
unit16
uint32
uint64
注意:这些类型的变量之间不允许互相赋值或操作
~~~
//错误代码
var a int8
var b int32
c := a+ b
~~~
###浮点数的类型:
float32
float64(默认)
Go语言还支持复数,它默认类型是complex128(64位实数+64位虚数)complex64
复数的形式为RE+IMi,其中RE是实数部分,IM是虚数部分,而最后的i是虚数单位。
~~~
package main
import "fmt"
func main() {
var c complex64 = 5 + 5i
fmt.Printf("Value is : %v", c)
}
~~~
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-05-16_57396c27733f9.jpg)
# 字符串
Go语言字符串都是采用UTF-8字符集编码。字符串是用一对双引号或反引号(``)括起来定义,它的类型是string。
~~~
//示例代码
func test(){
userame,realname := "grassroots", "liuxinming" //简短声明
username2 := "test"
username2 = "test2" //常规赋值
}
~~~
**在Go语言中字符串是不可变的,下面代码编译时会报错**
~~~
var s string = "hello"
s[0] = 'c'
~~~
可以使用下面代码实现
~~~
/*
* 字符串示例代码
*@author lxm
*/
package main
import "fmt"
func main() {
s := "hello"
c := []byte(s) //将字符串 s 转换为 []byte 类型
s2 := string(c) //再转换回 string类型
fmt.Printf("%s\n", s2)
}
~~~
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-05-16_57396c278469e.jpg)
Go语言中可以使用“+” 操作符来链接两个字符串。
修改字符串参考如下代码:
~~~
/*
*修改字符串
*@author lxm
*/
package main
import "fmt"
func main() {
s := "hello"
s = "c" + s[1:] //字符串切片操作
fmt.Printf("%s\n", s)
}
~~~
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-05-16_57396c279a509.jpg)
# 错误类型
error类型,用来处理错误信息,Go语言的package里面还有专门一个包errors来处理错误。