IO流
流是数据在数据源和程序之间所采用的路径。数据源可以是文件数据库或者键盘输入等,程序是运行在内存中的应用程序。
数据从数据源输入到程序的路径是输入流,数据从内存输出到数据源的路径是输出流。
流式传输基于内存。内存的输入是输入流,数据在内存中的持久化是输出流。
计算机中的流实际上是信息的转换。它是一个有序的流,所以相对于某个对象,我们通常称该对象从外界输入信息(Input),相应地,该对象向外界输出的信息(Output)称为输出流,统称为输出流。称为输入/输出流(I/O Streams)。对象之间交换信息或数据时,总是将对象或数据转换为某种形式的流,然后通过流的传输,流到达目的对象后转换为对象数据。因此,流可以看作是一种数据载体,通过流可以实现数据的交换和传输。
对于计算机来说,流的基本单位是字节流,但是对于程序来说,程序也可以识别字符流。字节流是面向计算机的,字符流是面向应用程序的。
该程序还通过流的方式实现与计算机的数据交换。通常,编程语言提供内置的流(I/O)接口。
Java I/O
Go I/O
在计算机软件的工作过程中,需要大量的I/O。例如,软件的本地数据库存储用户的缓存信息等。流是数据持久化的桥梁。将程序中的数据,即内存中的数据写入到显示器或文件系统中。
键盘输入输出
//输出
fmt.Println() //打印换行
fmt.Print() //打印
fmt.Printf() //格式化打印
//输入
fmt.Scanf()
fmt.Scanln()
fmt.Scan()
格式化输入和输出(通常用f)
%v 值的默认格式表示
%T 值的类型的Go语法表示
%t 单词true或false
%d 表示为十进制
%f 有小数部分但无指数部分,如123.456
%s 直接输出字符串或者[]byte
fmt.Printf(format string, a ...interface{}) (n int, err error)
var a = "test"
var b = Student{
}
var c = "10"
fmt.Printf("默认输出%v,类型输出%T,十进制输出%d,字符输出%s", a, b, c, a)
fmt.Scanf(format string, a ...any) (n int, err error)
在格式化输入时,需要注意以下几点:
- 输入的格式必须与书面格式一致,与格式化程序相对应即可。
- 输入时必须使用接收地址以节省内存。
- 书写时,格式化字符不使用标点符号分隔,输入时默认使用空格分隔。
//3特性
var a1 int
var b1 string
fmt.Scanf("%d,%s\n", &a1, &b1)
fmt.Scanf("%d%s\n", &a1, &b1)
输入时,第一个用“,”分隔,第二个用空格分隔。
//特性1
fmt.Scanf("a=%d,b=%s\n", &a1, &b1)
fmt.Scanf("输入一个十进制数%d,输入一个字符串%s\n", &a1, &b1)
标准输入和输出
func Println(a ...interface{}) (n int, err error)
a1 := 10
b1 := "qwe"
fmt.Println(a1, b1)
Println方法标准化输出时相邻参数的输出之间添加空格并在输出结束后添加换行符,Print不会自动换行。
func Scan(a ...interface{}) (n int, err error)
Scan 扫描标准输入中的文本,并将成功读入的以空格分隔的值保存到成功传递给该函数的参数中。 (注意只能用空格分隔输入,并且书写时只能用“,”分隔变量地址接收)
var a1 int
var b1 string
fmt.Scan(&a1,&b1)
fmt.Print(a1, b1)
Scanln
方法能自动换行。
fmt
包其他格式化I/O,详见中文开发手册
文件操作
- 读
file, err := os.Open("D:\\Go\\Go Files\\unit5\\src\\test\\hello.txt")
//file, err := os.Open("D:/Go/Go Files/unit5/src/test/hello.txt")
//file, err := os.Open("../test/hello.txt")
if err != nil {
fmt.Println("file nnot found")
return
}
defer file.Close()
reader := bufio.NewReader(file)
for {
str, err := reader.ReadString('\n')
if err == io.EOF {
break
}
fmt.Print(str)
}
对于Go来说没有严格的/
和\\
区分,均可以读取。
filebyte, err := ioutil.ReadFile("D:\\Go\\Go Files\\unit5\\src\\test\\hello.txt")
if err != nil {
fmt.Println("read file err=", err)
}
fmt.Printf("%v", string(filebyte))
- 创建文件并写入文件
filepath := "./abc.txt"
file, err := os.OpenFile(filepath, os.O_RDONLY|os.O_CREATE, 0666)
if err != nil {
fmt.Printf("open file err=%v\n", err)
return
}
defer file.Close()
str := "hello,gardon\n"
writer := bufio.NewWriter(file)
//循环写入几行
for j := 0; j < len(str); j++ {
writer.WriteString(str)
}
writer.Flush()
工厂模式
在Go语言中,一条件下某些结构体并不需要多次创建,只需要单一的实例即可即工厂模式或者单例模式。例如在数据的datasource
创建时的工厂模式单例即可。
Go语言中没有像Java那样的static关键字。如果需要单例模式,只需要通过指针进行操作,即返回变量的地址。
package main
import "fmt"
func main() {
//
var student = StudentFactory(1, "xuwenhui")
fmt.Println((*student).id, (*student).name)
}
type student struct {
id int
name string
}
func StudentFactory(id int, name string) *student {
return &student{
id: id,
name: name,
}
}
在上面代码中student
结构体要设为单例模式,需要设置访问权限,通过公开访问方法StudentFactory
返回结构体的地址,并用指针操作结构体,实现工厂模式。
类型断言
在Go的多态特性中,不同的特性需要使用断言来调用不同的方法。
type Person struct {
name string
sex string
}
type Student struct {
person Person
sid string
}
// 需要一个父类是两个结构体的父接口
// 空接口是任何类型的父类
type Per interface {
}
在上述代码中需要一个参数能同时接收Person和Student类,同时调用其方法,Go语言中,通过断言
来决策。
断言能够将子类用父类接收实现多态性,具体用法是变量.(类型)
。
func main() {
var a Per
var b = a.(Person)
fmt.Println(b.name, b.sex)
var c = a.(Student)
fmt.Println(c.person.name, c.person.sex, c.sid)
}
type Person struct {
name string
sex string
}
type Student struct {
person Person
sid string
}
// 需要一个父类是两个结构体的父接口
// 空接口是任何类型的父类
type Per interface {
}
上面的代码中,a接口通过断言转换为任意类型(Person和Student)。使用断言转换任何已知类型并调用转换后的成员和方法。
package main
import "fmt"
func main() {
// var a Per
// var b = a.(Person)
// fmt.Println(b.name, b.sex)
// b.showPn()
// var c = a.(Student)
// fmt.Println(c.person.name, c.person.sex, c.sid)
// c.showStu()
stu1 := Student{
person: Person{
"xu",
"nan",
},
sid: "s001",
}
identufy(stu1)
}
type Person struct {
name string
sex string
}
func (pn Person) showPn() {
fmt.Println(pn.name, pn.sex)
}
type Student struct {
person Person
sid string
}
func (stu Student) showStu() {
fmt.Println(stu.person.name, stu.person.sex, stu.sid)
}
// 需要一个父类是两个结构体的父接口
// 空接口是任何类型的父类
type Per interface {
}
func identufy(per Per) {
//通过断言做类的变换
var x = per.(Student)
fmt.Println(x.person.name, x.person.sex, x.sid)
x.person.showPn()
x.showStu()
}
在上面代码中identufy
方法接收Per
的参数,使用断言将参数转化为已指对象Student
,就可以使用其成员与方法。同时也可以接收Person参数,或者接收任意已知类型。
命令参数
在多环境开发的情况下,需要更改环境以方便测试,因为不同的环境配置是不同的。这可以直接通过读取代码内部的不同配置文件来实现,但是代码打包后,就无法通过更改代码来实现了。 ,需要传递外部参数。
Go的os命令提供了os.Args
切片来存储所有命令行参数。
import (
"fmt"
"os"
)
func main() {
//输出命令行参数长度
fmt.Println("命令行参数长度", len(os.Args))
//循环输出参数
for index, value := range os.Args {
fmt.Printf("args[%v]=%v\n", index, value)
}
}
请注意,参数之间用空格分隔。
命令参数解析
flag包实现了命令行参数的解析。
os.Args
可以满足一些基本的参数要求,但是复杂的在使用改参数就不太方便了,Go也提供了flag
包实现了命令行参数的解析。
var usr, pwd, host string
var port int
flag.StringVar(&usr, "user", "", "用户名")
flag.StringVar(&pwd, "password", "", "密码")
flag.StringVar(&host, "h", "localhost", "主机名")
flag.IntVar(&port, "port", 3306, "端口")
flag.Parse()
fmt.Printf("usr=%s,pwd=%s,host=%s,port=%d", usr, pwd, host, port)
flag
的var系列的函数将参数绑定到指定的变量上,通过变量输入没有顺序要求。var系列函数有一般有四个参数第一参数为程序的变量,第二个参数为绑定输入变量参数,第三个为默认值,第四个参数为绑定参数描述。
命令行输入时通过-绑定参数 数值
输入。
序列化与反序列化
序列化
Go语言对JSON操作的包均在encoding/json
包下,包下提供了Marshal
方法来序列化Go内置对象。Marshal函数返回v的json编码。
func Marshal(v interface{}) ([]byte, error)
//创建结构体
type Person struct {
name string
address string
}
//序列化
per := Person{
name: "钢铁侠",
address: "漫威",
}
perjson, err := json.Marshal(&per)
if err != nil {
panic(err)
}
fmt.Printf("per序列化的json数据为为:%v", perjson)
fmt.Printf("per序列化的json字符串为:%v", string(perjson))
上面代码中序列化了一个Person对象,但打印时却打印失败,这是由于结构体成员都是小写的,只能在本包内访问,序列化没有意义,也不支持私有类的序列化。
type Person struct {
Name string
Address string
}
将结构体改为公共类时就可以序列化了。
与 Java 万物皆对象的本质不同,Go 除了结构体之外,还有映射、数组、切片等。
//定义一个map类型
func initMap() map[string]string {
var tmp map[string]string = map[string]string{
}
tmp["1"] = "aaa"
tmp["2"] = "bbb"
tmp["3"] = "ccc"
return tmp
}
//对map序列化
a := initMap()
ajson, err := json.Marshal(&a)
if err != nil {
panic("ajson序列化失败")
}
fmt.Printf("%v\n", ajson)
fmt.Println(string(ajson))
//输出序列化时的数据类型
fmt.Printf("%T\n", ajson)
fmt.Printf("%T\n", string(ajson))
可以看出序列化后时一个字节数组,通过string()函数将其转化为字符串,即为json字符串。
Go序列化时tag标签的使用
序列化时,成员变量的名称全部大写。但在实际使用中,需要统一的书写标准和具体的名称,这就需要在序列化时使用标签。
{
"Name":"钢铁侠","Address":"漫威"}
Go语言结构体序列化tag
使用规则是在结构体字段后添加json:"name"
并用反引号包裹。
type Person struct {
Name string `json:"person_name"`
Address string `json:"person_address"`
Age int
}
如果在“名称”和“地址”中使用标签,它们的序列化名称将成为自定义的 person_names。 Age成员不使用标签,因此不会改变。
per := Person{
Name: "钢铁侠",
Address: "漫威",
Age: 18,
}
perjson, err := json.Marshal(&per)
if err != nil {
panic(err)
}
fmt.Printf("per序列化的json数据为为:%v", perjson)
fmt.Printf("per序列化的json字符串为:%v", string(perjson))
struct的tag只会在序列化时起作用不会影响结构体的使用。
反序列化
反序列化就是将序列化后的json字符串或者字节数组转换成编程语言内置的数据结构。这种反序列化可以在相同语言或不同语言中发生。例如,Java类被序列化,然后通过网络传输到前端进行json反序列化,或者任何其他后端语言序列化内置数据,然后传输。到前端。
反序列化序列化数据的语言相同,Java实现和Go实现。
/** go语言实现 */
//定义结构体
type Person struct {
Name string `json:"person_name"`
Address string `json:"person_address"`
Age int `json:"person_age"`
}
//序列化
per := Person{
Name: "钢铁侠",
Address: "漫威",
Age: 18,
}
perjson, err := json.Marshal(&per)
if err != nil {
panic(err)
}
fmt.Printf("per序列化的json数据为为:%v", perjson)
fmt.Printf("per序列化的json字符串为:%v", string(perjson))
序列化后为一个字节数组,可以通过string()
方法转化为字符串。
func json.Unmarshal(data []byte, v any) error
该方法时反序列化方法,第一个参数为需要反序列化的字节数组,第二个参数为反序列化后数据的赋值变量。
//反序列化赋值变量
var per1 Person
err1 := json.Unmarshal(perjson, &per1)
if err1 != nil {
panic(err1)
}
fmt.Printf("反序列化的数据per1%s,%s,%d", per1.Name, per1.Address, per1.Age)
str := `{"person_name":"钢铁侠","person_address":"漫威","person_age":18}`
var per2 Person
err2 := json.Unmarshal([]byte(str), &per2)
if err2 != nil {
panic(err2)
}
fmt.Println(per2)
对字符串也可以反序列化,需要将字符串转化为字节数组,通过[]byte()
方法。
单元测试
在Go语言包中,需要测试某些方法是否正确,某些业务逻辑是否合理。这些测试需要通过。一般需要在主包中引入模块,然后测试模块的方法或逻辑。这样做的缺点是测试代码需要写在主类中。但在实际项目开发中,主类有自己的逻辑。在主类中编写测试代码非常繁琐。另外,项目运行时,不能写在主类中。修改类中的测试代码。而且,这些测试代码在生产环境中需要删除。
为了解决这些问题,Go语言提供了单元测试,可以对包和模块的某些方法进行编程代码测试。而且这些测试模块都是独立的,不需要写在主类中,不会影响主要业务和代码。部署生产环境时,可以直接删除测试模块的代码,非常方便。
Go语言中提供了testing
单元测试框架,能够很好的解决这些问题。testing单元测试框架提供go test
命令来实现单元测试和性能测试,基于这个框架针对相应的函数写测试用例。
testing 提供对 Go 包的自动化测试的支持。通过 go test
命令,能够自动执行如下形式的任何函数:
func TestXxx(*testing.T)
其中 Xxx 可以是任何字母数字字符串,用于识别测试用例。要编写一个新的测试套件,需要创建一个名称以 _test.go 结尾的文件,该文件包含 TestXxx
函数,如上所述。 将该文件放在与被测试的包相同的包中。该文件将被排除在正常的程序包之外,但在运行 “go test” 命令时将被包含。
单元测试的机制是编译器创建一个主类,该类将引入xxx_test.go包并调用TestXXX测试方法。这些过程都是由测试框架完成的。
因此,在使用测试框架时,必须按照其规范编写测试用例。
测试用例规范
- 测试的包必须使用
_test.go
后缀结尾;- 测试包需要引入
testing
包- 测试方法必须以
Test
开头即TestXXX,且参数为测试包的的指针参数,一般为*testing.T
//mathutil.go
package mathutil
func add(x int, y int) int {
return x + y
}
func sub(x int, y int) int {
return (x - y)
}
//mathutil_test.go
package mathutil
import (
"fmt"
"testing"
)
func TestAdd(t *testing.T) {
res := add(10, 13)
if res != (10 + 13) {
fmt.Println("add Fail,expect 23 but found", res)
}
}
总结
- 测试用例文件名必须以_test.go结尾;
- 测试用例函数必须以Test开头;
- 测试函数的参数类型必须是*testing.T
- 运行测试用例的指令为
go test
无日志信息;go test -v
打印详细信息- 使用
*testing.T
的方法t.Fatalf
打印错误退出程序和t.Logf
方法可以打印日志。- 测试用例独立于主程序,仅在go test命令中生效。 PASS表示运行成功,FAIL表示运行失败。