简介
本文为Go学习过程中记录的笔记,参考文档如下:《Go入门指南》
接口与反射
1. 接口定义:
type Namer interface{
Method1(param_list) return_type
Method2(param_list) return_type
...
}
接口定义了一组方法,但是这些方法不包含代码,它们没有被实现,且接口里也不能包含变量。如果有某个类型(或结构体)实现了接口方法集中的所有方法,我们则称该类型(或结构体)实现了该接口。此时,我们可以将该类型(或接口)的实例赋值给接口,再由接口来调用相应的方法,通过这种方式可以实现多态。
- 类型不需要显式声明它实现了某个接口,接口被隐式地实现,且多个类型可以实现同一个接口;
- 实现某个接口的类型,除了接口定义的方法外,还可以有其他的方法;
- 一个类型可以实现多个接口;
- 接口类型可以指向一个实例的引用,该实例的类型实现了此接口(接口是动态类型);
- 即使接口在类型之后才定义,二者处于不同的包中,被单独编译;只要类型实现了接口中的方法,它就实现了此接口。
package main
import "fmt"
type Shaper interface {
Area() float32
}
type Square struct {
side float32
}
func (sq *Square) Area() float32 {
return sq.side * sq.side
}
func main() {
sq1 := new(Square)
sq1.side = 5
var areaIntf Shaper
areaIntf = sq1
// shorter,without separate declaration:
// areaIntf := Shaper(sq1)
// or even:
// areaIntf := sq1
fmt.Printf("The square has area: %f\n", areaIntf.Area())
}
在上面的代码中,如果Shaper有另外一个方法Perimeter(),但是Square并没有实现它,即使没有人在Square实例上调用这个方法,编译器也会给出错误,提示目标实例没有实现该接口。
2. 接口嵌套接口 :
一个接口可以包含一个或多个其他的接口,这相当于直接将这些内嵌接口的方法列举在外层接口中一样,即提级别。
3. 类型断言:
一个接口类型的变量varI可以包含任何实现了该接口的类型的值,需要一种方式来检测它真正的类型。通常我们可以使用类型断言来测试某个时刻varI是否包含类型T的值:
v := varI.(*T)
//这里varI必须是一个接口变量,否则编译器会报错
类型断言可能是无效的,转换时有一定的可能程序运行失败发生错误,因此尽量使用以下结构做类型判断:
if v,ok := varI.(*T);ok{
...
}
如果转换合法,即T为实际类型的话,那么v时varI转换到类型T的值,ok会是true;否则v是目标类型T的零值,ok是false,但不会有运行时错误发生。
类型前面的 “*” 是必备的,否则会导致编译错误。
4. 类型判断type-switch:
除了上述方法之外,也可以调用type来做类型检测:
switch t := areaIntf.(type) {
case *Square:
fmt.Printf("Type Square %T with value %v\n", t, t)
case *Circle:
fmt.Printf("Type Circle %T with value %v\n", t, t)
case nil:
fmt.Printf("nil value: nothing to check?\n")
default:
fmt.Printf("Unexpected type %T\n", t)
}
//Type Square *main.Square with value &{5}
在上面的代码中,t得到了areaIntf的值和类型,同时case中列举的所有类型都必须实现接口对应的方法,如果检测类型没有在case语句列举的类型中,就会执行default语句,在该结构中不允许有fallthrough。
这里如果是基本数据类型的话,则不用加上 “*” 做判断。
5. 测试一个值是否实现了某个接口:
type Stringer interface {
String() string
}
if sv, ok := v.(Stringer); ok {
fmt.Printf("v implements String(): %s\n", sv.String()) // note: sv, not v
}
如果变量v的类型确实实现了Stringer接口,那么sv此时是接口类型。
6. 方法集和接口:
接口变量中存储的具体值是不可寻址的,在前面关于方法的部分可知,作用域变量上的方法实际上是不区分变量到底是指针还是值的,因为Go都会做进一步处理,把指针变为值或把值变为指针。但是结合接口使用的时候,情况就不一样了,因为接口变量中存储的具体值是不可寻址的。这就导致了,指针变量可以调用实现接口的指针方法和值方法,因为可以直接解引用,而值变量只能调用实现接口的值方法,无法调用指针方法,因为接口变量中存储的具体值是不可寻址的。
package main
import (
"fmt"
)
type List []int
func (l List) Len() int {
return len(l)
}
func (l *List) Append(val int) {
*l = append(*l, val)
}
type Appender interface {
Append(int)
}
func CountInto(a Appender, start, end int) {
for i := start; i <= end; i++ {
a.Append(i)
}
}
type Lener interface {
Len() int
}
func LongEnough(l Lener) bool {
return l.Len()*10 > 42
}
func main() {
// A bare value
var lst List
// compiler error:
// cannot use lst (type List) as type Appender in argument to CountInto:
// List does not implement Appender (Append method has pointer receiver)
// CountInto(lst, 1, 10)
if LongEnough(lst) { // VALID:Identical receiver type
fmt.Printf("- lst is long enough\n")
}
// A pointer value
plst := new(List)
CountInto(plst, 1, 10) //VALID:Identical receiver type
if LongEnough(plst) {
// VALID: a *List can be dereferenced for the receiver
fmt.Printf("- plst is long enough\n")
}
}
7. 使用Sorter接口排序:
Len()
//判断元素个数
Less(i,j)
//比较第i和第j个元素
Swap(i,j)
//交换第i和第j个元素
对于任何实现了以上接口方法(假设接口名为Sorter)的类型,都可以使用func Sort(data Sorter)做排序。
8. 空接口:
空接口不包含任何方法,它对实现不做任何要求。
type Any interface{}
任何其他类型都实现了空接口,类似于Java中的Object类,可以给一个空接口类型的变量赋任何类型的值。
package main
import "fmt"
var i = 5
var str = "ABC"
type Person struct {
name string
age int
}
type Any interface{}
func main() {
var val Any
val = 5
fmt.Printf("val has the value: %v\n", val)
val = str
fmt.Printf("val has the value: %v\n", val)
pers1 := new(Person)
pers1.name = "Rob Pike"
pers1.age = 55
val = pers1
fmt.Printf("val has the value: %v\n", val)
switch t := val.(type) {
case int:
fmt.Printf("Type int %T\n", t)
case string:
fmt.Printf("Type string %T\n", t)
case bool:
fmt.Printf("Type boolean %T\n", t)
case *Person:
fmt.Printf("Type pointer to Person %T\n", t)
default:
fmt.Printf("Unexpected type %T", t)
}
}
在上面的例子中,接口变量 val 被依次赋予一个 int,string 和 Person 实例的值,然后使用 type-switch 来测试它的实际类型。每个 interface {} 变量在内存中占据两个字长:一个用来存储它包含的类型,另一个用来存储它包含的数据或者指向数据的指针。
9. 构建通用类型的数组:
我们可以使用空接口,用来存储不同的类型,使用一个自定义的结构体来存储空接口数组,从而实现通用类型的数组。
type Element interface{}
type Vector struct{
a []Element
}
在该结构体上定义一些相关方法,即可实现Java中ArrayList的功能。
10. 复制数据切片至空接口切片:
假设有一个myType类型的数据切片,如果想要将切片中的数据复制到一个空接口切片中
var dataSlice []myType = FuncReturnSlice()
var interfaceSlice []interface{} = dataSlice
使用以上代码编译时报错,因为它们在内存中的布局是不一样的,必须使用for-range语句来一个一个显式复制:
var dataSlice []myType = FuncReturnSlice()
var interfaceSlice []interface{} = make([]interface{},len(dataSlice))
for i,d := range dataSlice{
interfaceSlice[i]=d
}
在前面结构体的部分,关于链表和树的数据结构,可以在data部分定义为空接口,这样就可以存放任何类型的数据。
11. 接口赋值接口:
一个接口的值可以赋给另一个接口变量,只要底层实现了必要的方法。这个转换是在运行时进行检查的,转换失败了则会导致一个运行时错误,这是Go语言动态的一面。
举个例子:我们有三个接口,分别是空接口empty,绝对值接口AbsInterface(定义了Abs()),平方根接口SqrtInterface(定义了Sqr()),还有一个结构体类型Point,且实现了Abs()方法和Sqr()方法。
我们可以先将结构体类型Point的实例,赋值给空接口,然后再将空接口赋值给其他的两个接口,这种转换是合法的。
empty = pp
ai = empty.(AbsInterface)
//这里的pp是结构体类型Point的实例
si = ai.(SqrInterface)
empty = si
12. 反射包:
反射可以在运行时检查类型和变量,例如它的大小,方法和动态的调用这些方法,这对于没有源代码的包尤其有用。
reflect.TypeOf(var1)
//获得var1的类型
reflect.ValueOf(var1)
//获得var1的值
实际上,反射是通过检查一个接口的值(包含一个type和value),变量会首先被转换成空接口,然后再做相应的处理。
func TypeOf(i interface{}) Type
func ValueOf(i interface{}) Value
返回的类型Type和Value中,Value有一个Type方法返回Value的Type,也有Int和Float作为方法名的方法可以获取存储在内部的值,Value有一个Interface方法获得接口值;Type和Value都有Kind方法返回一个常量来表示类型,Kind总是返回底层类型。
// blog: Laws of Reflection
package main
import (
"fmt"
"reflect"
)
func main() {
var x float64 = 3.4
fmt.Println("type:", reflect.TypeOf(x))
v := reflect.ValueOf(x)
fmt.Println("value:", v)
fmt.Println("type:", v.Type())
fmt.Println("kind:", v.Kind())
fmt.Println("value:", v.Float())
fmt.Println(v.Interface())
fmt.Printf("value is %5.2e\n", v.Interface())
y := v.Interface().(float64)
fmt.Println(y)
}
13. 通过反射修改值:
正常情况下我们是无法通过反射修改相关变量的值的,因为默认是不允许的,我们可以通过Value的CanSet()方法判断目标是否可修改,true可修改false则不可以。要想使得可设置我们可以参考以下代码:
v := reflect.ValueOf(&x)
v = v.Elem()
//此时再次调用v.CanSet()返回值则为true
//通过v.Set...()方法既可以通过反射修改值
v.SetFloat(val1)
//这里只有被导出字段才是可以通过反射设置的
14. 反射结构体(动作):
反射包中也提供了一些方法用来反射结构体类型(如果反射对象是一个结构体类型),如下:
value.NumField()
//获得结构体中字段的数目
value.Field(int)
//获得结构体中的第i个字段
value.Method(0).call(nil)
//调用结构体的String()方法,默认的第0个方法,nil为参数
15. 如果参数类型为接口的方法,使用实现了相应接口的结构体类型作为实参调用该方法会导致编译错误。
16. 动态方法调用:
17. 显式地指明类型实现了某个接口:
接口的实现在Go中并不需要声明,但是我们也可以通过声明来解决歧义的问题,这种用法在大部分时候都不建议使用,限制了接口的实用性。
type Fooer interface {
Foo()
ImplementsFooer()
}
type Bar struct{}
func (b Bar) ImplementsFooer() {}
func (b Bar) Foo() {}
18. 空接口和函数重载:
函数重载在Go中是不被允许的,但是如果我们把参数换成空接口类型的可变参数,这样方法的调用就可以接受不同类型的实参,从某个角度实现了重载。
19. 接口的继承:
当一个类型包含(内嵌)另一个类型(实现了一个或多个接口)的指针时,这个类型就可以使用(另一个类型)所有的接口方法。
这里强调是指针的原因与接口的存储方式有关,因为接口是不可寻址的,存储指针就保证了不管方法的参数是指针还是值都可以被调用。
type Task struct {
Command string
*log.Logger
}
当log.Logger实现了Log()方法后,Task的实例就可以调用该方法。
类型可以通过继承多个接口来实现多重继承。
20. Go中的面向对象:
读写数据
1. 读取用户的输入:
最简单的方法是调用fmt包提供的Scan和Sscan开头的函数:
var (
firstName, lastName, s string
i int
f float32
input = "56.12 / 5212 / Go"
format = "%f / %d / %s"
)
func main() {
fmt.Println("Please enter your full name: ")
fmt.Scanln(&firstName, &lastName)
// fmt.Scanf("%s %s", &firstName, &lastName)
// 将读取到的两个值分别赋值给firstname,lastname
fmt.Sscanf(input, format, &f, &i, &s)
// Sscanf扫描指定的字符串,如上所示,我们可以看到input的内容为"56.12 / 5212 / Go"
// 而format则是我们需要提取的模板信息,如第一个"/"前面的信息我们要赋值给f
// 因此,这样子完成该操作之后并输出,结果便如下所示
fmt.Println("From the string we read: ", f, i, s)
// 输出结果: From the string we read: 56.12 5212 Go
}
也可以使用bufio包提供的缓冲读取来读取数据。
var inputReader *bufio.Reader
var input string
var err error
func main() {
inputReader = bufio.NewReader(os.Stdin)
fmt.Println("Please enter some input: ")
input, err = inputReader.ReadString('\n')
//这里的'\n'是指读取到'\n',本次读取便结束,同时会把标识符一起放到input中
if err == nil {
fmt.Printf("The input was: %s\n", input)
}
}
2. 文件读写:
在Go语言中,文件使用指向os.File类型的指针来表示,也叫做文件句柄。我们在前面章节使用到过标准输入os.Stdin和标准输出os.Stdout,他们的类型都是*os.File。
func main() {
inputFile, inputError := os.Open("input.dat")
// 获得文件句柄,inputFile的类型为*os.File,使用os.Open()函数
if inputError != nil {
fmt.Printf("An error occurred on opening the inputfile\n" +
"Does the file exist?\n" +
"Have you got acces to it?\n")
return // exit the function on error
}
defer inputFile.Close()
// 关闭资源
inputReader := bufio.NewReader(inputFile)
// 获得文件的阅读器
for {
inputString, readerError := inputReader.ReadString('\n')
// 逐行阅读文件
fmt.Printf("The input was: %s", inputString)
if readerError == io.EOF {
// 遇到文件终止符停止
return
}
}
}
一些读取相关的函数:
-
直接读取/写入整个文件:
可以使用io/ioutil包里的ioutil.ReadFile()方法,该方法第一个返回值类型为[]byte,存放读取到的内容,第二个返回值是错误,如果没有发生错误则为nil;使用WriteFile()方法可以将[]byte中的内容写回文件。
func main() { inputFile, inputError := os.Open("input.dat") if inputError != nil { fmt.Printf("An error occurred on opening the inputfile\n" + "Does the file exist?\n" + "Have you got acces to it?\n") return // exit the function on error } defer inputFile.Close() inputReader := bufio.NewReader(inputFile) for { inputString, readerError := inputReader.ReadString('\n') fmt.Printf("The input was: %s", inputString) if readerError == io.EOF { return } } }
-
带缓冲的读取:
有的时候,如果目标读取文件是不按照行划分的或者是一个二进制文件,此时bufio.Reader的ReadString()方法是不起作用的,在这种情况下我们可以使用bufio.Reader.Read()函数,是现代缓冲读取。
buf := make([]byte, 1024) ... n, err := inputReader.Read(buf) if (n == 0) { break}
-
按列读取文件中的数据:
如果数据是按列排列并用空格分隔的,你可以使用 fmt 包提供的以 FScan 开头的一系列函数来读取他们。
func main() { file, err := os.Open("products2.txt") if err != nil { panic(err) } defer file.Close() var col1, col2, col3 []string for { var v1, v2, v3 string _, err := fmt.Fscanln(file, &v1, &v2, &v3) // scans until newline if err != nil { break } col1 = append(col1, v1) col2 = append(col2, v2) col3 = append(col3, v3) } fmt.Println(col1) fmt.Println(col2) fmt.Println(col3) }
3. compress包:读取压缩文件
compress包提供了读取压缩文件的功能,支持的压缩文件格式为:bzip2、flate、gzip、lzw和zlib。
import (
"fmt"
"bufio"
"os"
"compress/gzip"
)
func main() {
fName := "MyFile.gz"
var r *bufio.Reader
fi, err := os.Open(fName)
// 获得文件句柄
if err != nil {
fmt.Fprintf(os.Stderr, "%v, Can't open %s: error: %s\n", os.Args[0], fName,err)
os.Exit(1)
}
fz, err := gzip.NewReader(fi)
// 获得阅读器,这里的gzip是一个包
if err != nil {
r = bufio.NewReader(fi)
} else {
r = bufio.NewReader(fz)
}
for {
line, err := r.ReadString('\n')
if err != nil {
fmt.Println("Done reading file")
os.Exit(0)
}
fmt.Println(line)
}
}
4. 写文件:代码如下
package main
import (
"os"
"bufio"
"fmt"
)
func main () {
// var outputWriter *bufio.Writer
// var outputFile *os.File
// var outputError os.Error
// var outputString string
outputFile, outputError := os.OpenFile("output.dat",os.O_WRONLY|os.O_CREATE, 0666)
// 以只写的形式打开该文件,如果文件不存在则新建
// os.O_RDONLY:只读
// os.O_WRONLY:只写
// os.O_CREATE:创建,如果指定文件不存在就创建
// os.O_TRUNC: 截断,如果指定文件已存在,就将该文件的长度截为0
// 固定使用参数 "0666"
if outputError != nil {
fmt.Printf("An error occurred with file opening or creation\n")
return
}
defer outputFile.Close()
outputWriter := bufio.NewWriter(outputFile)
// 获得文件的写入器
outputString := "hello world!\n"
for i:=0; i<10; i++ {
outputWriter.WriteString(outputString)
// 写入相应字符串到文件中,这里只是写入缓冲区
}
outputWriter.Flush()
// 调用该函数将缓冲区的内容写入到目标文件中
}
如果写入的东西很简单,我们可以使用
fmt.Fprintf(outputFile, “Some test data.\n”)
直接将内容写入文件。fmt 包里的 F 开头的 Print 函数可以直接写入任何 io.Writer,包括文件。
5. 文件拷贝:
使用io包的Copy方法,需要将待拷贝文件和目标地址文件都通过os包的相关函数获得文件句柄,再使用os.Copy()方法进行拷贝。
io.Copy(dst,src)
// dst和src都是文件句柄
6. 读取命令行的参数:
-
os包读取:
os包中有一个string类型的切片变量os.Args,用来存放调用该程序时的相关参数。
func main() { who := "Alice " if len(os.Args) > 1 { who += strings.Join(os.Args[1:], " ") } fmt.Println("Good Morning", who) }
这里如果是正常通过IDE运行则无法显示想要的结果,需要在命令行调用该程序并添加相应的参数,Args的第一个参数是程序名,之后的参数通过空格进行分割。
-
flag包读取:
flag包有一个NArg()方法可以获取到调用该程序时附加的参数数目,额外调用Arg(index)方法可以获得参数的内容,此外,与os包不同的是,Arg(index)函数传入0时返回的是实际的第一个参数而不是程序名。
flag包也可以用来实现自定义的命令:
package main import ( "flag" // command line option parser "os" ) var NewLine = flag.Bool("n", false, "print newline") // echo -n flag, of type *bool // 这里是将一个 -n 指令的调用结果解引用给NewLine,使用*NewLine获得,默认值是false // 只有命令行中调用了 -n 指令,该解引用的结果才会认定为true const ( Space = " " Newline = "\n" ) func main() { flag.PrintDefaults() //打印flag的使用帮助信息 flag.Parse() //扫描参数列表并设置flag var s string = "" for i := 0; i < flag.NArg(); i++ { if i > 0 { s += " " if *NewLine { // -n is parsed, flag becomes true s += Newline } } s += flag.Arg(i) } os.Stdout.WriteString(s) }
flag.VisitAll(fn func(*Flag))
是另一个有用的功能:按照字典顺序遍历 flag,并且对每个标签调用 fn。
7. 使用切片读写文件:
//f :文件句柄
//nr:读取长度,如果为0读取结束,如果小于0说明出现异常
nr,err := f.Read(buf[:])
os.Stdout.Write(buf[0:nr])
//将读取到的内容写出到标准输出
8. 使用defer关闭文件:
每次成功获取了文件句柄后需要将文件及时关闭,这里的及时指的是程序运行结束前,因此使用defer可以很好地解决这个问题。
9. Fprintf函数:
其函数签名如下:
func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error)
第一个参数必须是一个实现了io.Writer接口的变量,即实现了Write方法的类型变量。第二个参数为format,即将后面的 a 字符串按照指定的格式 format 将其写入到 w 中。
func main() {
// unbuffered
fmt.Fprintf(os.Stdout, "%s\n", "hello world! - unbuffered")
// buffered: os.Stdout implements io.Writer
buf := bufio.NewWriter(os.Stdout)
// and now so does buf.
fmt.Fprintf(buf, "%s\n", "hello world! - buffered")
buf.Flush()
}
10. JSON数据格式:
-
序列化:
// 调用json包 js,_ := json.Marshal(object) // Marshal方法可以将object转码为json数据格式,使用js接收 // 出于安全考虑,在 web 应用中最好使用 json.MarshalforHTML() 函数 // 其对数据执行 HTML 转码,所以文本可以被安全地嵌在 HTML <script> 标签中
并不是所有的数据都可以编码为JSON类型,只有验证通过的数据结构才能被编码。
- JSON对象只支持字符串类型的key,如果要编码一个map类型的对象,map必须是mao[string]T(T是json包中支持的任何类型)
- Channel,复杂类型和函数类型不能被编码
- 不支持循环数据结构,它将引起序列化进入一个无限循环
- 指针可以被编码,实际上是对指针指向的值进行编码
-
反序列化:Unmarshal()
-
解码任意的数据:如果事先我们不知道json数据的数据结构,我们可以使用
map[string]interface{}
和[]interface{}
储存任意的 JSON 对象和数组,这样就避免了匹配失败的情况出现。b := []byte(`{"Name": "Wednesday", "Age": 6, "Parents": ["Gomez", "Morticia"]}`) var f interface{} err := json.Unmarshal(b, &f) // 通过这种方式,默认f是map类型 // 打印结果为:map[Age:6 Name:Wednesday Parents:[Gomez Morticia]] // 属性按照ASCII顺序排列,数组间通过空格分开
要访问这个数据,我们可以使用类型断言然后对其进行遍历
m := f.(map[string]interface{})
-
解码数据到结构:如果已知JSON数据的结构,我们可以定义一个适当的结构并对 JSON 数据反序列化。
type FamilyMember struct { Name string Age int Parents []string } var m FamilyMember err := json.Unmarshal(b, &m)
-
-
编码和解码流:
json包提供Decoder和Encoder类型来支持常用JSON数据流读写,其分别封装了io.Reader和io.Writer接口。
func NewDecoder(r io.Reader) *Decoder func NewEncoder(w io.Writer) *Encoder
要想把 JSON 直接写入文件,可以使用 json.NewEncoder 初始化文件(或者任何实现 io.Writer 的类型),并调用 Encode ();反过来与其对应的是使用 json.Decoder 和 Decode () 函数:
func NewDecoder(r io.Reader) *Decoder func (dec *Decoder) Decode(v interface{}) error
11. XML数据格式:
xml包中,也有像json包中一样的Marshal()
和UnMarshal()
函数对数据进行编码解码,也有处理流数据的newDecoder()
和newEncoder()
等函数。
此外,xml包下的Decoder类型还有一个Token()
函数,可以自动读取流数据的下一个标签,直到读取到末尾结束,包中定义了若干 XML 标签类型:StartElement,Chardata(这是从开始标签到结束标签之间的实际文本),EndElement,Comment,Directive 或 ProcInst。通过Token()
我们可以使用switch做不同的处理:
package main
import (
"encoding/xml"
"fmt"
"strings"
)
var t, token xml.Token
var err error
func main() {
input := "<Person><FirstName>Laura</FirstName><LastName>Lynn</LastName></Person>"
inputReader := strings.NewReader(input)
p := xml.NewDecoder(inputReader)
for t, err = p.Token(); err == nil; t, err = p.Token() {
switch token := t.(type) {
case xml.StartElement:
name := token.Name.Local
fmt.Printf("Token name: %s\n", name)
for _, attr := range token.Attr {
attrName := attr.Name.Local
attrValue := attr.Value
fmt.Printf("An attribute is: %s %s\n", attrName, attrValue)
// ...
}
case xml.EndElement:
fmt.Println("End of token")
case xml.CharData:
content := string([]byte(token))
fmt.Printf("This is the content: %v\n", content)
// ...
default:
// ...
}
}
}
Token name: Person
Token name: FirstName
This is the content: Laura
End of token
Token name: LastName
This is the content: Lynn
End of token
End of token
12. Gob传输数据:
Gob 是 Go 自己的以二进制形式序列化和反序列化程序数据的格式;可以在 encoding
包中找到。这种格式的数据简称为 Gob (即 Go binary 的缩写),类似于Java的 “Serialization” 。
Gob 特定地用于纯 Go 的环境中,例如,两个用 Go 写的服务之间的通信。
只有可导出的字段会被编码,零值会被忽略。在解码结构体的时候,只有同时匹配名称和可兼容类型的字段才会被解码。当源数据类型增加新字段后,Gob 解码客户端仍然可以以这种方式正常工作:解码客户端会继续识别以前存在的字段。
和 JSON 的使用方式一样,Gob 使用通用的 io.Writer 接口,通过 NewEncoder() 函数创建 Encoder 对象并调用 Encode();相反的过程使用通用的 io.Reader 接口,通过 NewDecoder() 函数创建 Decoder 对象并调用 Decode。
13. Go中的密码学:
- hash 包:实现了 adler32、crc32、crc64 和 fnv 校验;
- crypto 包:实现了其它的 hash 算法,比如 md4、md5、sha1 等。以及完整地实现了 aes、blowfish、rc4、rsa、xtea 等加密算法。
这里只简单介绍一下sha1包的一些用法:
func main() {
hasher := sha1.New()
// 创建一个新的hash.Hash对象,采用sha1加密
io.WriteString(hasher, "test")
// 将文本 "test" 写到hasher中
// Hash对象实际上是一个接口,它实现了 io.Writer 接口,所以可以写入
b := []byte{}
fmt.Printf("Result: %x\n", hasher.Sum(b))
fmt.Printf("Result: %d\n", hasher.Sum(b))
// 将文本 "test" 的检验和结果写到数组b中并按照格式输出
hasher.Reset()
// 清空文本,重新设置
data := []byte("We shall overcome!")
n, err := hasher.Write(data)
// 也可以通过 Hash.Write() 方法写入数据
if n!=len(data) || err!=nil {
log.Printf("Hash write error: %v / %v", n, err)
}
checksum := hasher.Sum(b)
// 同理计算检验和
fmt.Printf("Result: %x\n", checksum)
}
Result: a94a8fe5ccb19ba61c4c0873d391e987982fbbd3
Result: [169 74 143 229 204 177 155 166 28 76 8 115 211 145 233 135 152 47 187 211]
Result: e2222bfc59850bbb00a722e764a555603bb59b2a