1.1 反射reflect
1.1.1 调试工具 dlv
GitHub:
下载方式:
bash
go install github.com/go-delve/delve/cmd/dlv@latest
之后就是配置环境变量了,自行搜索下即可。
Windows:
learn.microsoft.com/zh-cn/power...
安装完成后,我们在终端命令行内任意一个路径下输入dlv
,则会出现这样的提示:
这样的话,就已经配置完成了。
接下来,我们来看看dlv
内都有哪些内容:
-
第一步
bashdlv debug main.go
这个意思是,在
main.go
文件中,开启debug模式 -
第二步,输入
help
,就会出现下方代码块中的内容
Type
(dlv) help
The following commands are available:
Running the program:
call ------------------------ Resumes process, injecting a function call (EXPERIMENTAL!!!)
continue (alias: c) --------- Run until breakpoint or program termination.
next (alias: n) ------------- Step over to next source line.
rebuild --------------------- Rebuild the target executable and restarts it. It does not work if the executable was not built by delve.
restart (alias: r) ---------- Restart process.
step (alias: s) ------------- Single step through program.
step-instruction (alias: si) Single step a single cpu instruction.
stepout (alias: so) --------- Step out of the current function.
Manipulating breakpoints:
break (alias: b) ------- Sets a breakpoint.
breakpoints (alias: bp) Print out info for active breakpoints.
clear ------------------ Deletes breakpoint.
clearall --------------- Deletes multiple breakpoints.
condition (alias: cond) Set breakpoint condition.
on --------------------- Executes a command when a breakpoint is hit.
toggle ----------------- Toggles on or off a breakpoint.
trace (alias: t) ------- Set tracepoint.
watch ------------------ Set watchpoint.
Viewing program variables and memory:
args ----------------- Print function arguments.
display -------------- Print value of an expression every time the program stops.
examinemem (alias: x) Examine raw memory at the given address.
locals --------------- Print local variables.
print (alias: p) ----- Evaluate an expression.
regs ----------------- Print contents of CPU registers.
set ------------------ Changes the value of a variable.
vars ----------------- Print package variables.
whatis --------------- Prints type of an expression.
Listing and switching between threads and goroutines:
goroutine (alias: gr) -- Shows or changes current goroutine
goroutines (alias: grs) List program goroutines.
thread (alias: tr) ----- Switch to the specified thread.
threads ---------------- Print out info for every traced thread.
Viewing the call stack and selecting frames:
deferred --------- Executes command in the context of a deferred call.
down ------------- Move the current frame down.
frame ------------ Set the current frame, or execute command on a different frame.
stack (alias: bt) Print stack trace.
up --------------- Move the current frame up.
Other commands:
config --------------------- Changes configuration parameters.
disassemble (alias: disass) Disassembler.
dump ----------------------- Creates a core dump from the current process state
edit (alias: ed) ----------- Open where you are in $DELVE_EDITOR or $EDITOR
exit (alias: quit | q) ----- Exit the debugger.
funcs ---------------------- Print list of functions.
help (alias: h) ------------ Prints the help message.
libraries ------------------ List loaded dynamic libraries
list (alias: ls | l) ------- Show source code.
packages ------------------- Print list of packages.
source --------------------- Executes a file containing a list of delve commands
sources -------------------- Print list of source files.
target --------------------- Manages child process debugging.
transcript ----------------- Appends command output to a file.
types ---------------------- Print list of types
Type help followed by a command for full documentation.
(dlv)
常用的几个命令:
break
,breakpoints
,clear
,clearall
,continue
,next
,print
分别是: 设置断点、打印设置的断点、清除断点、清除断点、运行程序遇到断点或终止、执行下一步操作、输出
1.1.2 接口的底层实现
在Go语言中,接口类型使用两种内部结构来表示,一个是iface
,用于非空接口(即包含方法的接口),另一个是eface
,用于空接口(即不包含任何方法的接口)。
-
iface 结构:
-
iface结构用于表示非空接口(包含方法的接口)。它包含两个字段:
type
:指向实现该接口的具体类型的指针。value
:指向实现了接口方法的具体值的指针。
这个结构用于在运行时实现接口的动态分派,确保调用的方法是正确的。
-
go
package main
type Rectangle struct {
Width int
Height int
}
func (r Rectangle) Area() int {
return r.Height * r.Width
}
type Shape interface {
Area()
}
func main() {
r := Rectangle{
Width: 10,
Height: 8,
}
var s Shape
var i interface{} = s
_ = r
_ = i
}
yaml
Type 'help' for list of commands.
(dlv)
(dlv) clear
Command failed: not enough arguments
(dlv) clearall
(dlv) b main.main
Breakpoint 1 set at 0x104a71910 for main.main() ./main.go:16
(dlv) c
> main.main() ./main.go:16 (hits goroutine(1):1 total:1) (PC: 0x104a71910)
11:
12: type Shape interface {
13: Area()
14: }
15:
=> 16: func main() {
17: r := Rectangle{
18: Width: 10,
19: Height: 8,
20: }
21: var s Shape
(dlv) n
> main.main() ./main.go:17 (PC: 0x104a7191c)
12: type Shape interface {
13: Area()
14: }
15:
16: func main() {
=> 17: r := Rectangle{
18: Width: 10,
19: Height: 8,
20: }
21: var s Shape
22: var i interface{} = s
(dlv) n
> main.main() ./main.go:18 (PC: 0x104a71920)
13: Area()
14: }
15:
16: func main() {
17: r := Rectangle{
=> 18: Width: 10,
19: Height: 8,
20: }
21: var s Shape
22: var i interface{} = s
23: _ = r
(dlv) n
> main.main() ./main.go:19 (PC: 0x104a71928)
14: }
15:
16: func main() {
17: r := Rectangle{
18: Width: 10,
=> 19: Height: 8,
20: }
21: var s Shape
22: var i interface{} = s
23: _ = r
24: _ = i
(dlv) n
> main.main() ./main.go:21 (PC: 0x104a71930)
16: func main() {
17: r := Rectangle{
18: Width: 10,
19: Height: 8,
20: }
=> 21: var s Shape
22: var i interface{} = s
23: _ = r
24: _ = i
25: }
(dlv) n
> main.main() ./main.go:22 (PC: 0x104a71934)
17: r := Rectangle{
18: Width: 10,
19: Height: 8,
20: }
21: var s Shape
=> 22: var i interface{} = s
23: _ = r
24: _ = i
25: }
(dlv) n
> main.main() ./main.go:25 (PC: 0x104a7196c)
20: }
21: var s Shape
22: var i interface{} = s
23: _ = r
24: _ = i
=> 25: }
(dlv) p i
interface {} nil
(dlv) p &i
(*interface {})(0x14000044728)
(dlv) p r
main.Rectangle {Width: 10, Height: 8}
(dlv) p &r
(*main.Rectangle)(0x14000044700)
(dlv) p *(*runtime.iface)((uintptr)(&r))
runtime.iface {
tab: *runtime.itab {
inter: (unreadable protocol error E08 during memory read for packet $ma,20),
_type: (unreadable protocol error E08 during memory read for packet $ma,20),
hash: (unreadable protocol error E08 during memory read for packet $ma,20),
_: [4]uint8 [(unreadable protocol error E08 during memory read for packet $ma,20),(unreadable protocol error E08 during memory read for packet $ma,20),(unreadable protocol error E08 during memory read for packet $ma,20),(unreadable protocol error E08 during memory read for packet $ma,20)],
fun: [1]uintptr [(unreadable protocol error E08 during memory read for packet $ma,20)],},
data: unsafe.Pointer(0x8),}
(dlv) p *(*runtime.eface)((uintptr)(&i))
runtime.eface {
_type: *internal/abi.Type nil,
data: unsafe.Pointer(0x0),}
(dlv)
-
eface 结构:
-
eface结构用于表示空接口(不包含任何方法的接口)。它包含两个字段:
type
:指向实际值的类型的指针。data
:指向实际值的指针。
这个结构允许空接口变量存储任意类型的值,因为它不包含任何方法,可以代表任何类型。
-
go
package main
func main() {
num := 123
var i interface{} = &num
_ = i
}
-
我们先写出这样的代码,然后通过
dlv
命令来调试一下代码,这样我们可以直接了当的看出其中的底层实现。其次,通过之前学习的内容,进行调试:
yamlType 'help' for list of commands. (dlv) b main.main Breakpoint 1 set at 0x10457d910 for main.main() ./main.go:3 (dlv) bp Breakpoint runtime-fatal-throw (enabled) at 0x104552ef0,0x104552fb0,0x1045672ec for (multiple functions)() <multiple locations>:0 (0) Breakpoint unrecovered-panic (enabled) at 0x104553270 for runtime.fatalpanic() /usr/local/go/src/runtime/panic.go:1175 (0) print runtime.curg._panic.arg Breakpoint 1 (enabled) at 0x10457d910 for main.main() ./main.go:3 (0) (dlv) c > main.main() ./main.go:3 (hits goroutine(1):1 total:1) (PC: 0x10457d910) 1: package main 2: => 3: func main() { 4: num := 123 5: var i interface{} = &num 6: _ = i 7: } 8: (dlv) n > main.main() ./main.go:4 (PC: 0x10457d91c) 1: package main 2: 3: func main() { => 4: num := 123 5: var i interface{} = &num 6: _ = i 7: } 8: 9: //type Rectangle struct { (dlv) n > main.main() ./main.go:5 (PC: 0x10457d924) 1: package main 2: 3: func main() { 4: num := 123 => 5: var i interface{} = &num 6: _ = i 7: } 8: 9: //type Rectangle struct { 10: // Width int (dlv) n > main.main() ./main.go:7 (PC: 0x10457d93c) 2: 3: func main() { 4: num := 123 5: var i interface{} = &num 6: _ = i => 7: } 8: 9: //type Rectangle struct { 10: // Width int 11: // Height int 12: //} (dlv) p num 123 (dlv) p &num (*int)(0x14000044728) (dlv) p i interface {}(*int) *123 (dlv) p (&i) (*interface {})(0x14000044738) (dlv) p (unitptr)(&i) Command failed: could not evaluate function or type (unitptr): could not find symbol value for unitptr (dlv) p (uintptr)(&i) 1374389815096 (dlv) p (*runtime.eface)((uintptr)(&i)) (*runtime.eface)(0x14000044738) (dlv) p *(*runtime.eface)((uintptr)(&i)) runtime.eface { _type: *internal/abi.Type {Size_: 8, PtrBytes: 8, Hash: 3126353255, TFlag: TFlagRegularMemory (8), Align_: 8, FieldAlign_: 8, Kind_: 54, Equal: runtime.memequal64, GCData: *1, Str: 362, PtrToThis: 0}, data: unsafe.Pointer(0x14000044728),} (dlv)
最后就会调测出:
_type
以及data
这两种结构是Go语言运行时系统用来实现接口和类型断言的内部机制。它们使得Go语言的接口实现非常灵活,并且可以适应不同类型的对象。用户在使用接口时,一般不需要直接与这些结构交互,而是使用Go语言提供的接口和类型断言语法来处理。因为这不是Go语言中合法的操作。所以,用到了反射reflect
。
1.1.3 反射
reflect
包是Go语言标准库中的一个强大工具,它允许你在运行时检查和操作变量、方法、结构等信息,而无需在编译时知道这些信息的确切类型。使用 reflect
包,你可以编写更加灵活和通用的代码,但同时也需要小心,因为它牵涉到运行时的类型信息。
在Go语言的reflect
包中,reflect.TypeOf
和 reflect.ValueOf
是两个非常重要的函数,它们分别用于获取一个变量的类型信息和获取一个变量的反射对象(reflect.Value)。
1.1.3.1 reflect.TypeOf
函数
reflect.TypeOf
函数用于获取一个变量的类型信息,返回一个 reflect.Type
类型的对象。这个函数的签名如下:
go
func TypeOf(i interface{}) Type
其中,i
是要获取类型信息的变量。下面是一个示例:
go
package main
import (
"fmt"
"reflect"
)
func main() {
var x float64 = 3.14
fmt.Println(reflect.TypeOf(x)) // 输出: float64
}
在这个例子中,reflect.TypeOf(x)
返回的是 float64
类型的反射对象。
1.1.3.1.1 TypeOf
函数常用方法--Name
Name:
这将返回类型的名称。切片或指针,没有名称,此方法返回一个空字符串。
go
package main
import (
"fmt"
"reflect"
)
type Rectangle struct {
Width int
Height int
}
func main() {
r := Rectangle{
Width: 10,
Height: 8,
}
fmt.Println(reflect.TypeOf(r).Name())
}
1.1.3.1.2 TypeOf
函数常用方法--Kind
go
package main
import (
"fmt"
"reflect"
)
type Rectangle struct {
Width int
Height int
}
func main() {
r := Rectangle{
Width: 10,
Height: 8,
}
fmt.Println(reflect.TypeOf(r).Kind() == reflect.Struct)
fmt.Println(reflect.TypeOf(&r).Kind() == reflect.Pointer)
fmt.Println(reflect.TypeOf(true).Kind() == reflect.Bool)
fmt.Println(reflect.TypeOf([]int{}).Kind() == reflect.Slice)
fmt.Println(reflect.TypeOf(map[string]int{}).Kind() == reflect.Map)
}
1.1.3.1.3 TypeOf
函数常用方法--Elem
Elem:
如果变量是指针、map、slice、channel或数组,使用varType.Elem() 找出元素的类型。
go
package main
import (
"fmt"
"reflect"
)
type Rectangle struct {
Width int
Height int
}
func main() {
r := Rectangle{
Width: 10,
Height: 8,
}
fmt.Println(reflect.TypeOf(r).Name())
fmt.Println(reflect.TypeOf(&r).Elem())
fmt.Println(reflect.TypeOf([]Rectangle{}).Elem())
fmt.Println(reflect.TypeOf([]*Rectangle{}).Elem())
fmt.Println(reflect.TypeOf(map[string]*Rectangle{}).Elem().Elem())
fmt.Println(reflect.TypeOf(make(chan Rectangle)).Elem())
}
举两个🌰
go
package main
import (
"fmt"
"reflect"
)
type Person struct {
Name string
Age int
}
func main() {
p := Person{Name: "Alice", Age: 30}
t := reflect.TypeOf(p)
// 使用 Name 方法获取类型的名称
fmt.Println("Type Name:", t.Name()) // 输出: Person
// 使用 Kind 方法获取类型的种类
fmt.Println("Type Kind:", t.Kind()) // 输出: struct
// 获取结构体字段的类型信息
nameField, _ := t.FieldByName("Name")
ageField, _ := t.FieldByName("Age")
// 使用 Elem 方法获取字段的元素类型
fmt.Println("Name Field Type:", nameField.Type) // 输出: string
fmt.Println("Age Field Type:", ageField.Type) // 输出: int
}
这个例子中:我们定义了一个 Person
结构体,包含了两个字段 Name
和 Age
。我们使用 reflect.TypeOf
函数获取了结构体 Person
的反射类型信息,并且通过 Name
方法获得了类型的名称("Person"),通过 Kind
方法获得了类型的种类("struct")。然后,我们使用 FieldByName
方法获取了结构体字段 Name
和 Age
的类型信息,最后使用 Elem
方法获得了字段的元素类型。
go
package main
import (
"fmt"
"reflect"
)
type Person struct {
Name string
Age int
}
func main() {
p := Person{Name: "Alice", Age: 30}
t := reflect.TypeOf(p)
// 判断类型的种类(Kind)
if t.Kind() == reflect.Struct {
// 获取结构体的元素类型
elemType := t.Elem()
// 打印元素类型的名称和种类
fmt.Println("元素类型的名称:", elemType.Name()) // 输出: ""
fmt.Println("元素类型的种类:", elemType.Kind()) // 输出: struct
}
}
在这个示例中,t
是 Person
结构体的反射类型。通过 Elem()
方法,我们获取了结构体的元素类型。需要注意的是,结构体的元素类型并不是结构体本身,而是结构体的成员字段的类型。
在这里,elemType.Name()
返回的是空字符串,因为结构体本身并没有名称。而 elemType.Kind()
返回的是 struct
,表示元素类型是一个结构体。
请注意,Elem()
方法只能用于接口、指针、数组、切片、通道类型。如果调用了不支持的类型,会引发 panic。在实际使用时,请确保你的代码遵循正确的类型规则。
1.1.3.1.4 获取结构体类型中的字段数量和具体字段的信息
在Go的reflect
包中,Type
类型具有 NumField()
方法和 Field(int)
方法,可以用于获取结构体类型中的字段数量和具体字段的信息。
1.NumField()
方法
NumField()
方法用于获取结构体中的字段数量。它返回一个 int
值,表示结构体中的字段数量。以下是一个示例:
go
package main
import (
"fmt"
"reflect"
)
type Person struct {
Name string
Age int
}
func main() {
p := Person{Name: "Alice", Age: 30}
t := reflect.TypeOf(p)
// 获取结构体中的字段数量
numFields := t.NumField()
fmt.Println("字段数量:", numFields) // 输出: 2
}
在这个示例中,t.NumField()
返回的是 Person
结构体中的字段数量。
2.Field(int)
方法
Field(int)
方法用于获取结构体中指定索引位置的字段信息。索引从0开始,一直到 NumField()-1
。该方法返回一个 reflect.StructField
类型的对象,其中包含了字段的详细信息,比如字段的名称、类型等。以下是一个示例:
v
package main
import (
"fmt"
"reflect"
)
type Person struct {
Name string
Age int
}
func main() {
p := Person{Name: "Alice", Age: 30}
t := reflect.TypeOf(p)
// 获取第一个字段的信息
firstField := t.Field(0)
fmt.Println("字段名称:", firstField.Name) // 输出: Name
fmt.Println("字段类型:", firstField.Type) // 输出: string
}
在这个示例中,t.Field(0)
获取的是 Person
结构体中的第一个字段(即 Name
字段)的信息。
需要注意的是,Field(int)
方法的参数是字段的索引,从0开始。这个方法返回的是一个 reflect.StructField
类型的对象,你可以通过它获取字段的各种信息。
3.举个🌰
go
package main
import (
"fmt"
"reflect"
)
type Rectangle struct {
Width int
Height int
Address string
Assistant string
}
func main() {
r := Rectangle{
Width: 10,
Height: 8,
}
rt := reflect.TypeOf(r)
for i := 0; i < rt.NumField(); i++ {
field := rt.Field(i)
fmt.Println(field.Name)
}
}
1.1.3.2 reflect.ValueOf
函数
reflect.ValueOf
函数用于获取一个变量的反射对象(reflect.Value
),返回一个 reflect.Value
类型的对象。这个函数的签名如下:
go
func ValueOf(i interface{}) Value
其中,i
是要获取反射对象的变量。下面是一个示例:
go
package main
import (
"fmt"
"reflect"
)
func main() {
var x float64 = 3.14
value := reflect.ValueOf(x)
fmt.Println(value.Float()) // 输出: 3.14
}
在这个例子中,reflect.ValueOf(x)
返回的是 float64
类型的反射对象,你可以使用 value.Float()
来获取这个变量的实际值。
1.1.3.2.1 ValueOf
函数示例
go
package main
import (
"fmt"
"reflect"
)
func main() {
var x float64 = 3.14
// 使用reflect.ValueOf获取变量x的reflect.Value
valueOfX := reflect.ValueOf(x)
// 获取reflect.Value的类型
fmt.Println("Type of valueOfX:", valueOfX.Type())
// 获取reflect.Value的kind
fmt.Println("Kind of valueOfX:", valueOfX.Kind())
// 获取reflect.Value的值
fmt.Println("Value of valueOfX:", valueOfX.Float())
// 尝试修改reflect.Value的值(会引发panic,因为reflect.Value是不可修改的)
// valueOfX.SetFloat(2.71) // 这行代码会引发panic
// 使用Elem方法获取指针指向的值的reflect.Value
ptr := &x
valueOfPtr := reflect.ValueOf(ptr).Elem()
fmt.Println("Value of valueOfPtr (before modification):", valueOfPtr.Float())
// 使用reflect.Value修改指针指向的值
valueOfPtr.SetFloat(2.71)
fmt.Println("Value of x after modification:", x)
}
在这个示例中,首先使用reflect.ValueOf
函数获取了一个float64
类型变量x
的reflect.Value
。然后,使用Value
类型的Type
方法获取了该reflect.Value
的类型,使用Kind
方法获取了该reflect.Value
的Kind(基础类型),使用Float
方法获取了该reflect.Value
的实际值。接着,演示了如何使用Elem
方法获取指针指向的值的reflect.Value
,并且修改了该指针指向的值。需要注意的是,reflect.Value
是不可修改的,如果尝试修改将会引发panic。所以,如果需要修改变量的值,必须使用指针。
1.1.3.2.2ValueOf以及TypeOf
函数示例
当你需要处理不同类型的结构体时,可以使用 reflect.TypeOf
获取结构体的类型信息,以及使用 reflect.ValueOf
获取结构体的值。以下是一个示例,演示了如何创建一个通用的结构体操作函数,该函数接受一个结构体类型的参数,使用 reflect
包中的函数进行操作:
go
package main
import (
"fmt"
"reflect"
)
type Person struct {
Name string
Age int
Email string
}
func processStruct(input interface{}) {
// 使用 reflect.TypeOf 获取结构体的类型信息
structType := reflect.TypeOf(input)
fmt.Println("Struct Type:", structType)
// 使用 reflect.ValueOf 获取结构体的值
structValue := reflect.ValueOf(input)
// 遍历结构体的字段并打印字段名和对应的值
for i := 0; i < structType.NumField(); i++ {
field := structType.Field(i)
fieldValue := structValue.Field(i)
// 打印字段名和对应的值
fmt.Printf("Field: %s, Value: %v\n", field.Name, fieldValue.Interface())
}
}
func main() {
// 创建一个Person结构体实例
person := Person{
Name: "Alice",
Age: 30,
Email: "alice@example.com",
}
// 调用处理结构体的函数
processStruct(person)
}
在这个示例中,我们定义了一个 Person
结构体,然后编写了一个名为 processStruct
的函数,该函数接受一个空接口类型的参数 input
。在函数内部,我们使用 reflect.TypeOf
获取结构体的类型信息,然后使用 reflect.ValueOf
获取结构体的值。接着,我们使用 NumField
方法获取结构体的字段数量,然后遍历字段,分别获取字段名和对应的值,并打印出来。
总之,reflect.TypeOf
用于获取类型信息,而 reflect.ValueOf
用于获取反射对象,它们是使用反射进行类型分析和操作的基础。
1.1.3.3 3个判断的方法IsNil IsValid IsZero
在Go语言的reflect
包中,reflect.Value
类型提供了三个非常有用的方法:IsNil
、IsValid
和IsZero
。
-
IsNil
方法:IsNil
方法用于检查reflect.Value
的底层值是否为nil
。这通常用于检查指针类型的reflect.Value
是否为nil
指针。如果reflect.Value
包装的值是nil
,IsNil
方法返回true
,否则返回false
。示例:
govar x *int v := reflect.ValueOf(x) fmt.Println("IsNil:", v.IsNil()) // 输出: IsNil: true
-
IsValid
方法:IsValid
方法用于检查reflect.Value
是否包含一个有效的值。如果reflect.Value
是无效的(例如,由于传入了无效的参数导致),IsValid
方法返回false
,否则返回true
。示例:
govar x int v := reflect.ValueOf(x) fmt.Println("IsValid:", v.IsValid()) // 输出: IsValid: true var y chan int w := reflect.ValueOf(y) fmt.Println("IsValid:", w.IsValid()) // 输出: IsValid: false
-
IsZero
方法:IsZero
方法用于检查reflect.Value
是否为其类型的零值。零值的定义取决于reflect
包中对于不同类型的零值定义。如果reflect.Value
的值是其类型的零值,IsZero
方法返回true
,否则返回false
。示例:
govar x int v := reflect.ValueOf(x) fmt.Println("IsZero:", v.IsZero()) // 输出: IsZero: true var y string w := reflect.ValueOf(y) fmt.Println("IsZero:", w.IsZero()) // 输出: IsZero: true var z float64 u := reflect.ValueOf(z) fmt.Println("IsZero:", u.IsZero()) // 输出: IsZero: true var a []int t := reflect.ValueOf(a) fmt.Println("IsZero:", t.IsZero()) // 输出: IsZero: true
这三个方法都会允许你在运行时动态的检查reflect.Value
的属性,以便在处理未知类型的数据时更加灵活。
1.2 常用标准库OS
1.2.1 fmt包实现格式化I/O.
1.2.1.1 Formatter
go
package main
import (
"fmt"
"strings"
)
type Person struct {
Name string
Age int
}
func (p Person) Format(f fmt.State, c rune) {
switch c {
case 'l':
fmt.Fprint(f, strings.ToLower(p.Name))
case 'u':
fmt.Fprint(f, strings.ToUpper(p.Name))
default:
fmt.Fprintf(f, "Name: %s, Age: %d", p.Name, p.Age)
}
}
func main() {
p := Person{Name: "Alice", Age: 30}
fmt.Printf("Person: %v\n", p)
fmt.Printf("Person (lowercase name): %l\n", p)
fmt.Printf("Person (uppercase name): %u\n", p)
}
1.2.1.2 GoStringer
GoString方法用于打印作为操作数传递到%#v格式的值。
go
package main
import "fmt"
type Person struct {
Name string
Age int
}
func (p Person) GoString() string {
return fmt.Sprintf("Person{Name: %q, Age: %d}", p.Name, p.Age)
}
func main() {
p := Person{Name: "Alice", Age: 30}
fmt.Printf("%#v\n", p)
}
1.2.1.3 Stringer
go
import (
"fmt"
)
type Animal struct {
Name string
Age uint
}
func (a Animal) String() string {
return fmt.Sprintf("%v (%d)", a.Name, a.Age)
}
func main() {
a := Animal{
Name: "Gopher",
Age: 2,
}
fmt.Println(a)
}
1.2.2 环境变量读写
go
package main
import (
"fmt"
"os"
)
func init() {
err := os.Setenv("LogLevel", "Debug")
if err != nil {
return
}
}
func main() {
fmt.Println(os.Getenv("GOROOT"))
fmt.Println(os.Getenv("LogLevel"))
}
这个代码其实很简单,就是在这个go文件初始化的时候,就设置env环境变量,随后在main函数中去调用。
当然,我们也能看到go给我们配置了一些环境变量,我们可以在终端中输入env
自己去看一下。
1.2.3 文件读取
go
package main
import (
"fmt"
"log"
"os"
)
func main() {
// Open the file
filePath := "example.txt"
file, err := os.Open(filePath)
if err != nil {
log.Fatal("Error opening file:", err)
}
defer file.Close()
// Read the file data
fileData := make([]byte, 1024)
n, err := file.Read(fileData)
if err != nil {
log.Fatal("Error reading file:", err)
}
// Print the file contents
fmt.Println("File Contents:")
fmt.Println(string(fileData[:n]))
}
循环读取所有数据
go
package main
import (
"fmt"
"io" "log" "os")
func main() {
// Open the file
filePath := "anki.txt"
file, err := os.Open(filePath)
if err != nil {
log.Fatal("Error opening file:", err)
}
defer file.Close()
// Read the file data
fileData := make([]byte, 1024)
for {
n, err := file.Read(fileData)
if err != nil {
if err == io.EOF {
break
}
panic(err)
}
fmt.Println("File Contents:")
fmt.Println(string(fileData[:n]))
}
}
解释一下:
file, err := os.Open("123.txt")
: 这行代码尝试打开名为 "123.txt" 的文件。如果文件成功打开,它将返回一个文件对象file
和nil
的错误值err
。如果文件打开失败,err
将包含一个描述错误的消息。if err != nil { panic(err) }
: 这行代码检查文件打开的错误。如果发生了错误(err
不为nil
),程序将会进入紧急状态,打印错误信息并终止程序执行。panic
函数用于引发一个运行时错误。defer file.Close()
: 这行代码确保在函数返回之前关闭文件。defer
关键字用于延迟函数的执行,这里是延迟文件的关闭操作。fileData := make([]byte, 1024)
: 这行代码创建一个长度为 1024 字节的字节数组,用于存储从文件中读取的数据。for { ... }
: 这是一个无限循环,它会一直执行直到遇到break
语句为止。在循环内部,程序尝试从文件中读取数据并将其存储到fileData
字节数组中。n, err := file.Read(fileData)
: 这行代码尝试从文件中读取数据,将读取的字节数存储在变量n
中,并将可能出现的错误存储在变量err
中。if err != nil { ... }
: 这个条件判断语句检查读取操作是否发生错误。如果出现了错误,程序会根据错误的类型采取不同的行动。如果错误是文件结束错误 (io.EOF
),表示已经读取到文件末尾,循环会被中断。否则,如果是其他类型的错误,程序会进入紧急状态并打印错误信息,然后终止执行。fmt.Println(n)
: 这行代码打印每次从文件中读取的字节数。这对于了解文件读取的进展非常有用。fmt.Println(string(fileData[:n]))
: 这行代码将读取的字节数据转换为字符串并打印出来。注意fileData[:n]
表示字节数组的前n
个元素,这是因为file.Read
可能读取少于 1024 字节,所以我们只打印实际读取的部分。
使用 ioutil.ReadAll 一次性读取
go
package main
import (
"fmt"
"io" "log" "os")
func main() {
// Open the file
filePath := "anki.txt"
file, err := os.Open(filePath)
if err != nil {
log.Fatal("Error opening file:", err)
}
defer file.Close()
data, err := io.ReadAll(file)
if err != nil {
log.Fatal("Error reading file:", err)
}
fmt.Println("File Contents:")
fmt.Println(string(data))
}
使用bufio一行一行读
go
package main
import (
"bufio"
"fmt" "io" "os")
func main() {
file, err := os.Open("example.csv")
if err != nil {
panic(err)
}
defer file.Close()
reader := bufio.NewReader(file)
for {
line, _, err := reader.ReadLine()
if err != nil {
if err == io.EOF {
break
}
panic(err)
}
fmt.Println(string(line))
}
}
bufio
包提供了一个方便的 Reader
类型,用于高效地从输入源(例如文件)中读取数据。reader.ReadLine()
函数用于从 Reader
中读取一行数据。该函数返回三个值:
line []byte
: 这个参数代表读取的一行数据。它以字节切片的形式返回,因为Go中的字符串是不可变的,而使用字节切片可以更加灵活地处理文本数据。prefix bool
: 这个参数是一个布尔值,用于指示读取的行是否为前缀行。如果prefix
为true
,说明line
是reader.ReadLine()
函数在遇到较长的行时切分的结果。如果prefix
为false
,则表示line
包含了完整的一行数据。err error
: 这个参数表示在读取过程中是否发生了错误。如果读取成功,err
将为nil
。如果读取到文件末尾,err
将等于io.EOF
。如果在读取过程中发生了其他错误,err
将包含相关的错误信息。
使用 reader.ReadLine()
函数,你可以逐行地从输入源中读取数据,并根据 prefix
参数判断是否需要继续读取下一行以获取完整的文本数据。
1.2.4 os.Open & os.OpenFile
在Go语言中,os.Open
和 os.OpenFile
函数都用于打开文件,但它们之间有一些重要的区别。
1.2.4.1 os.Open
函数
os.Open
函数用于只读方式打开一个文件。它的函数签名如下:
go
func Open(name string) (*File, error)
name
是文件的路径。- 返回值是一个指向
File
结构的指针和一个可能的错误。File
结构代表了一个打开的文件。
使用 os.Open
函数,你只能以只读方式打开文件,不能进行写操作。示例代码如下:
go
file, err := os.Open("example.txt")
if err != nil {
log.Fatal(err)
}
defer file.Close()
// 从文件中读取数据
1.2.4.2 os.OpenFile
函数
os.OpenFile
函数用于以指定的模式(读、写、追加等)和权限打开文件。它的函数签名如下:
go
func OpenFile(name string, flag int, perm FileMode) (*File, error)
name
是文件的路径。flag
参数用于指定文件的打开模式,可以是os.O_RDONLY
(只读)、os.O_WRONLY
(只写)、os.O_RDWR
(读写)等。perm
参数用于指定文件的权限,通常用0666
表示可读写权限。
os.OpenFile
函数允许你在打开文件时指定更多的选项,例如,你可以使用 os.O_APPEND
标志来在文件末尾追加数据,而 os.Open
不提供这样的选项。示例代码如下:
go
file, err := os.OpenFile("example.txt", os.O_WRONLY|os.O_APPEND, 0666)
if err != nil {
log.Fatal(err)
}
defer file.Close()
// 向文件中写入数据
总而言之,如果你只需要以只读方式打开文件,可以使用 os.Open
函数。如果你需要更多的控制,例如指定打开文件的模式、权限等,可以使用 os.OpenFile
函数。
1.2.5 文件写入
go
package utils
import (
"fmt"
"os"
)
func FileWrite() {
file, err := os.OpenFile("1231.txt", os.O_WRONLY|os.O_CREATE, 0644)
if err != nil {
panic(err)
}
defer file.Close()
for i := 0; i < 50; i++ {
_, err := file.WriteString(fmt.Sprintf("%d\n", i))
if err != nil {
panic(err)
}
}
}
这段Go语言代码定义了一个utils
包,其中包含一个名为FileWrite
的函数。
-
导入包:
goimport ( "fmt" "os" )
导入了两个标准库包:
fmt
用于格式化字符串,os
用于处理文件操作。 -
函数
FileWrite
:gofunc FileWrite() {
函数
FileWrite
没有任何参数,它的目的是打开或创建一个文件(文件名为"1231.txt"),然后向文件中写入从0到49的整数,每个整数占据一行。 -
文件操作:
gofile, err := os.OpenFile("1231.txt", os.O_WRONLY|os.O_CREATE, 0644) if err != nil { panic(err) } defer file.Close()
尝试以写入模式打开文件"1231.txt"。如果文件不存在,它将被创建;如果文件已存在,它将被截断为零长度。
os.O_CREATE
标志用于在文件不存在时创建文件,os.O_WRONLY
标志表示以只写方式打开文件。0644
表示文件权限,允许文件所有者读写,其他用户只能读取。如果打开文件时发生错误,
panic(err)
会触发一个恐慌(panic),程序将终止执行并输出错误信息。defer file.Close()
语句确保在函数返回之前关闭文件,即使在函数内部发生了恐慌。 -
写入数据:
gofor i := 0; i < 50; i++ { _, err := file.WriteString(fmt.Sprintf("%d\n", i)) if err != nil { panic(err) } }
一个循环从0到49,将每个数字转换为字符串并写入文件,每个数字占据一行。
file.WriteString
函数返回写入的字节数和可能的错误。如果写入时发生错误,panic(err)
会触发一个恐慌,程序将终止执行。
总结:创建(如果文件不存在)或截断(如果文件已存在)文件"1231.txt",然后写入从0到49的整数,每个整数占据文件中的一行。如果在文件操作或写入过程中发生错误,程序会进入恐慌状态并终止执行。
1.2.6 文件追加写入
go
func FileWriteAppend() {
file, err := os.OpenFile("1234.txt", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
if err != nil {
panic(err)
}
defer file.Close()
for i := 0; i < 50; i++ {
_, err := file.WriteString(fmt.Sprintf("%d\n", i))
if err != nil {
panic(err)
}
}
}
1.2.7 文件截断
go
func FileWriteTru() {
file, err := os.OpenFile("1234.txt", os.O_WRONLY|os.O_TRUNC, 0664)
if err != nil {
panic(err)
}
defer file.Close()
for i := 0; i < 10; i++ {
_, err := file.WriteString(fmt.Sprintf("-%d\n", i))
if err != nil {
panic(err)
}
}
}
1.2.8 文件截断 seek
go
func FileSeek() {
// 写
file, err := os.OpenFile("1234.txt", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
if err != nil {
panic(err)
}
defer file.Close()
for i := 0; i < 10; i++ {
_, err := file.WriteString(fmt.Sprintf("-%d\n", i))
if err != nil {
panic(err)
}
}
// seek
_, err = file.Seek(0, io.SeekStart)
if err != nil {
return
}
// 读
reader := bufio.NewReader(file)
for {
line, _, err := reader.ReadLine()
if err != nil {
if err == io.EOF {
break
}
panic(err)
}
fmt.Println(string(line))
}
}
-
文件的创建和写入:
gofile, err := os.OpenFile("1234.txt", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
打开了一个文件,如果文件不存在则创建,如果文件存在则截断文件内容。
os.O_RDWR
表示以可读可写的方式打开文件。gofor i := 0; i < 10; i++ { _, err := file.WriteString(fmt.Sprintf("-%d\n", i)) // 写入文件 }
在一个循环中,它向文件写入了10行数据,每行的内容是 "-数字\n",例如 "-0\n","-1\n" 等。
-
文件指针的移动 (Seek):
go_, err = file.Seek(0, io.SeekStart)
将文件指针(即读/写位置)移动到文件的起始位置(偏移量为0)。这是通过使用
file.Seek()
函数来实现的。在这个例子中,io.SeekStart
表示相对于文件的起始位置进行偏移。 -
文件的读取:
goreader := bufio.NewReader(file) for { line, _, err := reader.ReadLine() // 读取文件内容 }
使用
bufio.NewReader()
创建了一个带缓冲的读取器,并通过ReadLine()
函数逐行读取文件内容。读取的内容被存储在line
变量中。如果到达文件末尾,ReadLine()
会返回io.EOF
错误,表示文件结束。在这个例子中,如果读取到文件末尾就会退出循环。读取的每一行内容被打印到控制台上:
gofmt.Println(string(line))
总而言之,就是是打开一个文件,往文件中写入10行数据,然后将文件指针移动到文件开头,逐行读取文件内容并打印到控制台。
1.2.9 读取目录
go
func ReadFileContent() {
file, err := os.OpenFile("a", os.O_RDONLY, 0644)
if err != nil {
panic(err)
}
defer file.Close()
dirEntries, err := file.ReadDir(-1)
if err != nil {
panic(err)
}
for _, entry := range dirEntries {
info, err := entry.Info()
if err != nil {
panic(err)
}
fmt.Printf("%v, %v, %v\n", entry.Name(), info.Size(), entry.IsDir())
}
}
这段代码用于读取目录中的文件信息并将文件名、大小和是否为目录的信息打印到控制台上。以下是代码的详细解释:
-
打开目录:
gofile, err := os.OpenFile("a", os.O_RDONLY, 0644)
这行代码打开了一个名为 "a" 的目录。
os.O_RDONLY
表示以只读的方式打开目录。 -
读取目录项:
godirEntries, err := file.ReadDir(-1)
这行代码使用
ReadDir()
函数读取目录中的所有项。参数-1
表示读取所有目录项,不进行限制。ReadDir()
函数返回一个[]DirEntry
切片,其中每个DirEntry
对象包含一个目录项的信息。 -
遍历目录项并打印信息:
gofor _, entry := range dirEntries { info, err := entry.Info() // 获取目录项的信息 if err != nil { panic(err) } fmt.Printf("%v, %v, %v\n", entry.Name(), info.Size(), entry.IsDir()) // 打印文件名、大小和是否为目录的信息 }
在循环中,代码遍历
dirEntries
切片,对于每个目录项,通过entry.Info()
获取详细信息。然后,使用entry.Name()
获取文件名,info.Size()
获取文件大小,和entry.IsDir()
判断是否为目录。这些信息被格式化后打印到控制台上。
总的来说,这段代码的目的是打开指定的目录(在这里是名为 "a" 的目录),读取目录中的文件信息,并将文件名、大小和是否为目录的信息打印到控制台上。
1.2.10 递归读取一个目录下面的所有txt文件
go
// PrintAllTxtFiles 递归读取一个目录下所有的文件
func PrintAllTxtFiles(dir string) error {
entries, err := os.ReadDir(dir)
if err != nil {
return err
}
for _, entry := range entries {
filePath := filepath.Join(dir, entry.Name())
println(filePath)
if entry.IsDir() {
if err := PrintAllTxtFiles(filePath); err != nil {
return err
}
} else if filepath.Ext(filePath) == ".txt" {
fileBytes, err := os.ReadFile(filePath)
if err != nil {
return nil
}
fmt.Printf("fileName: %v\n%v\n", filePath, string(fileBytes))
}
}
return nil
}
-
函数签名:
gofunc PrintAllTxtFiles(dir string) error {
函数接受一个参数
dir
,表示要递归读取的目录的路径。函数的返回类型为error
,表示可能会返回一个错误。 -
读取目录项:
goentries, err := os.ReadDir(dir) if err != nil { return err }
使用
os.ReadDir()
函数读取指定目录下的所有目录项。如果读取目录项过程中发生错误,函数会立即返回该错误。 -
遍历目录项并处理文件:
gofor _, entry := range entries { filePath := filepath.Join(dir, entry.Name()) println(filePath) ``` 在循环中,对于每个目录项,构建完整的文件路径 `filePath`,然后将该路径打印到控制台上。 ```go if entry.IsDir() { // 如果是目录,则递归调用 PrintAllTxtFiles 函数 if err := PrintAllTxtFiles(filePath); err != nil { return err } } else if filepath.Ext(filePath) == ".txt" { // 如果是文本文件(扩展名为 .txt),则读取文件内容并打印 fileBytes, err := os.ReadFile(filePath) if err != nil { return err } fmt.Printf("fileName: %v\n%v\n", filePath, string(fileBytes)) }
- 如果当前目录项是一个子目录,就递归调用
PrintAllTxtFiles()
函数,以处理这个子目录。 - 如果当前目录项是一个文本文件(文件扩展名为
.txt
),则使用os.ReadFile()
函数读取文件内容,并将文件路径和内容打印到控制台上。
- 如果当前目录项是一个子目录,就递归调用
-
返回错误:
goreturn nil
如果所有操作都顺利完成,函数返回
nil
表示没有错误发生。
综上所述,这段代码实现了递归读取指定目录下所有的文件,找到所有扩展名为 .txt
的文本文件,并将文件路径和文件内容打印到控制台上。
1.2.11 使用filepath下的函数简化递归读取流程
WalkDir
是 filepath
包中提供的一个函数,用于递归地遍历指定目录及其子目录中的所有文件和子目录。WalkDir
函数的签名如下:
go
func WalkDir(root string, fn func(path string, d fs.DirEntry, err error) error) error
root
参数表示要开始遍历的根目录的路径。fn
参数是一个回调函数,用于处理每个访问到的文件和子目录。该回调函数接受三个参数:path
表示当前文件或目录的完整路径,d
是一个实现了fs.DirEntry
接口的对象,包含了关于当前文件或目录的信息,err
是在获取文件信息时可能发生的错误。
以下是一个使用 WalkDir
函数的例子:
go
package main
import (
"fmt"
"io/fs"
"os"
"path/filepath"
)
func visit(path string, d fs.DirEntry, err error) error {
if err != nil {
fmt.Printf("Error accessing path %s: %v\n", path, err)
return err
}
if d.IsDir() {
fmt.Printf("Directory: %s\n", path)
} else {
fmt.Printf("File: %s\n", path)
}
return nil
}
func main() {
root := "." // 当前目录
err := filepath.WalkDir(root, visit)
if err != nil {
fmt.Printf("Error walking the path %s: %v\n", root, err)
}
}
在上面的例子中,visit
函数被传递给 WalkDir
函数作为回调函数。visit
函数会打印出每个访问到的文件和子目录的路径,并指示它们是文件还是目录。filepath.WalkDir
函数会递归遍历当前目录及其子目录,并对每个文件和目录调用 visit
函数。
再举个🌰:
这段代码定义了一个名为 ReadWalkDir
的函数,该函数使用 filepath.WalkDir
函数递归遍历指定目录及其子目录中的所有文件和子目录,并打印所有扩展名为 .txt
的文本文件的路径和文件内容。以下是代码的详细解释:
go
func ReadWalkDir(dir string) error {
return filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
fmt.Println(d.IsDir(), "ceshi ~~~~~~~~~~~~~~~~~~~~~~~~")
if !d.IsDir() && filepath.Ext(d.Name()) == ".txt" {
fmt.Println(!d.IsDir(), "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")
fileBytes, err := os.ReadFile(path)
if err != nil {
return err
}
fmt.Printf("filePath: %v\n%v\n", path, string(fileBytes))
}
return nil
})
}
-
filepath.WalkDir
函数:gofilepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
这行代码使用
filepath.WalkDir
函数遍历指定目录dir
及其子目录中的所有文件和子目录。WalkDir
函数的第二个参数是一个回调函数,该函数会在每次访问到一个文件或目录时被调用。回调函数的参数包括当前文件或目录的完整路径path
,fs.DirEntry
对象d
包含了关于当前文件或目录的信息,以及可能发生的错误err
。 -
!d.IsDir() && filepath.Ext(d.Name()) == ".txt"
的条件判断:goif !d.IsDir() && filepath.Ext(d.Name()) == ".txt" {
这个条件判断语句的作用是判断当前访问到的是一个文件(而不是目录),且文件的扩展名是
.txt
。d.IsDir()
返回true
表示当前项是一个目录,false
表示是一个文件。filepath.Ext(d.Name())
返回文件的扩展名,如果扩展名是.txt
,则条件成立。 -
处理符合条件的文件:
gofileBytes, err := os.ReadFile(path) if err != nil { return err } fmt.Printf("filePath: %v\n%v\n", path, string(fileBytes))
如果当前项是一个符合条件的文本文件,代码使用
os.ReadFile
函数读取文件内容,并将文件路径和文件内容打印到控制台上。
总的来说,这段代码递归地遍历指定目录及其子目录中的所有文件和子目录,在遍历的过程中,判断每个访问到的项是否为文件且扩展名为 .txt
,如果是则读取文件内容并将文件路径和内容打印到控制台上。
1.3 单元测试
在Go语言中,单元测试是一种非常重要的软件开发实践,它可以帮助你确保你的代码在各种情况下都能按照预期工作。Go语言的测试框架内建在语言本身中,使得编写和运行单元测试变得非常容易。下面是一个简单的Go语言单元测试的示例:
假设你有一个名为 calculator.go
的文件,其中包含了一个简单的加法函数 Add
:
go
// calculator.go
package calculator
func Add(a, b int) int {
return a + b
}
然后,你可以为这个函数编写单元测试。在同一个目录下创建一个名为 calculator_test.go
的文件,用于编写测试代码:
go
// calculator_test.go
package calculator
import "testing"
func TestAdd(t *testing.T) {
result := Add(2, 3)
expected := 5
if result != expected {
t.Errorf("Add(2, 3) returned %d, expected %d", result, expected)
}
}
在这个测试文件中,我们使用了 testing
包来编写测试函数 TestAdd
。在这个函数中,我们调用了 Add
函数,并使用 t.Errorf
来比较实际的结果和预期的结果。如果两者不相等,测试将会失败。
要运行这个测试,你可以使用 go test
命令。在命令行中,进入包含 calculator.go
和 calculator_test.go
的目录,然后运行以下命令:
go
go test
如果一切正常,你应该会看到类似下面的输出:
go
ok command-line-arguments 0.001s
这表示你的测试通过了!
1.3.1 库
github.com/stretchr/testify/assert
是一个非常流行的Go语言测试断言库,它提供了丰富的断言函数,用于简化测试代码的编写。使用这个库,你可以更容易地编写清晰、易读的测试代码。下面是一个示例,演示了如何使用 assert
包进行单元测试。
首先,你需要确保你的Go语言环境已经安装了 github.com/stretchr/testify/assert
包。如果没有安装,你可以使用以下命令安装:
go
go get -u github.com/stretchr/testify/assert
接下来,让我们修改之前的示例代码,使用 assert
包进行单元测试。我们将继续使用之前的 calculator.go
文件和 calculator_test.go
文件,但是这次我们将使用 assert
包的断言函数来编写测试:
go
// calculator_test.go
package calculator
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestAdd(t *testing.T) {
result := Add(2, 3)
expected := 5
assert.Equal(t, expected, result, "Add(2, 3) should return 5")
}
在这个示例中,我们使用了 assert.Equal
函数来比较实际结果和预期结果。如果它们不相等,assert
包会自动输出详细的错误信息,包括实际值和期望值。这样,你就无需手动编写错误消息了,使得测试代码更加简洁和易读。
要运行这个测试,你仍然可以使用 go test
命令。确保你的当前目录中包含了 calculator.go
和 calculator_test.go
文件,然后运行以下命令:
go
go test
如果测试通过,你会看到类似下面的输出:
go
ok command-line-arguments 0.001s
这表示你的测试通过了,而且你可以信心满满地知道你的 Add
函数在给定的输入下按照预期工作。
1.4 json序列化和反序列化
在Go语言中,你可以使用encoding/json
包来进行JSON序列化(将Go数据结构转换为JSON字符串)和反序列化(将JSON字符串转换为Go数据结构)。以下是JSON序列化和反序列化的基本用法示例:
1.4.1 JSON序列化
go
package main
import (
"encoding/json"
"fmt"
)
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
City string `json:"city"`
}
func main() {
// 创建一个Person对象
person := Person{
Name: "Alice",
Age: 30,
City: "New York",
}
// 将Person对象序列化为JSON字符串
jsonData, err := json.Marshal(person)
// 缩进的
jsonData, err := json.MarshalIndent(person, "", " ")
if err != nil {
fmt.Println("JSON serialization error:", err)
return
}
// 输出JSON字符串
fmt.Println(string(jsonData))
}
在上面的示例中,我们定义了一个Person
结构体,然后将其序列化为JSON字符串。输出结果会是一个包含name
、age
和city
字段的JSON对象。
1.4.2 JSON反序列化
go
package main
import (
"encoding/json"
"fmt"
)
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
City string `json:"city"`
}
func main() {
// JSON字符串
jsonData := `{"name":"Bob","age":25,"city":"London"}`
// 将JSON字符串反序列化为Person对象
var person Person
err := json.Unmarshal([]byte(jsonData), &person)
if err != nil {
fmt.Println("JSON deserialization error:", err)
return
}
// 输出反序列化后的Person对象
fmt.Println(person)
}
在上面的示例中,我们有一个包含JSON数据的字符串。我们使用json.Unmarshal
函数将JSON字符串解析为Person
对象。注意,传递给json.Unmarshal
函数的第二个参数是一个指向目标结构体的指针,以便函数可以填充结构体的字段。
在Go语言中,JSON编码和解码可以使用encoding/json
包中的Encoder
和Decoder
类型来实现。这两个类型提供了更灵活的方式来处理JSON数据流,特别是当你需要处理大量JSON数据时。
1.4.3 使用Encoder
进行JSON编码
go
package main
import (
"encoding/json"
"fmt"
"os"
)
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
City string `json:"city"`
}
func main() {
// 创建一个Person对象
person := Person{
Name: "Alice",
Age: 30,
City: "New York",
}
// 创建一个文件用于写入JSON数据
file, err := os.Create("person.json")
if err != nil {
fmt.Println("Error creating file:", err)
return
}
defer file.Close()
// 创建JSON编码器
encoder := json.NewEncoder(file)
// 使用Encoder将Person对象编码为JSON并写入文件
err = encoder.Encode(person)
if err != nil {
fmt.Println("JSON encoding error:", err)
return
}
fmt.Println("JSON data has been encoded and written to person.json")
}
在这个示例中,我们创建了一个Person
对象,并将其使用json.NewEncoder
函数创建的Encoder
对象编码并写入一个文件中。
1.4.4 使用Decoder
进行JSON解码
go
package main
import (
"encoding/json"
"fmt"
"os"
)
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
City string `json:"city"`
}
func main() {
// 打开包含JSON数据的文件
file, err := os.Open("person.json")
if err != nil {
fmt.Println("Error opening file:", err)
return
}
defer file.Close()
// 创建JSON解码器
decoder := json.NewDecoder(file)
// 创建一个空的Person对象,用于存储解码后的JSON数据
var person Person
// 使用Decoder将JSON数据解码到Person对象中
err = decoder.Decode(&person)
if err != nil {
fmt.Println("JSON decoding error:", err)
return
}
// 输出解码后的Person对象
fmt.Println("Decoded Person:", person)
}
在这个示例中,我们打开包含JSON数据的文件,然后使用json.NewDecoder
函数创建Decoder
对象。我们创建了一个空的Person
对象,然后使用Decode
方法将JSON数据解码到Person
对象中。