Go文件操作,Json和测试
- 1 文件
- 1.1 基本介绍
- 1.2 读取的基本操作
- 1.3 写入的基本操作
- 1.4 使用案例(三个)
- 2 Go语言的Json使用
- 2.1 序列化案例
- 2.2 反序列化案例
- 3 单元测试
- 3.1 先看个需求
- 3.2 快速入门
- 3.3 入门总结
1 文件
1.1 基本介绍
文件在程序中是以流的形式来操作的。
**流:**数据在数据源(文件)和程序(内存)之间经历的路径。
**输入流(读文件):**数据从数据源(文件)到程序(内存)的路径。
**输出流(写文件):**数据从程序(内存)到数据源(文件)的路径。
在Golang里,os.File封装所以文件相关操作,File是一个结构体。
// File represents an open file descriptor.
type File struct {
*file // os specific
}
1.2 读取的基本操作
方法一:使用带缓存的方式读取,适用于大文件读取
读取文件需要先了解下面的几个方法函数,需要这四步才算是一个完整的读取操作。
-
使用
os.Open()
函数打开文件- 函数原型:
func Open(name string) (*os.File, error)
- 示例代码:
file, err := os.Open("test.txt") if err != nil { log.Fatal(err) }
完整代码:
func main() { // 1.file 被叫做 file对象,file指针 ,file文件句柄 file, err := os.Open("D:\\Desktop\\test.txt") if err != nil { log.Fatal(err) } // 输出文件 fmt.Printf("file=%v", file) // 关闭文件 err = file.Close() if err != nil { fmt.Println("close file err=", err) } }
输出结果:
file=&{0xc00009aa00}
- 函数原型:
-
使用
bufio.NewReader()
函数创建读取缓冲区- 函数原型:
func NewReader(rd io.Reader) *bufio.Reader
- 示例代码:
reader := bufio.NewReader(file) // 默认缓冲区为4096
- 函数原型:
-
使用
ReadString()
函数读取文件内容- 函数原型:
func (b *bufio.Reader) ReadString(delim byte) (string, error)
- 示例代码:
content, err := reader.ReadString('\n') if err != nil && err != io.EOF { log.Fatal(err) }
- 函数原型:
-
关闭文件
- 示例代码:
file.Close()
将上面的步骤合起来,就是读取文件的全过程:
func main() {
// 1.file 被叫做 file对象,file指针 ,file文件句柄
file, err := os.Open("D:\\Desktop\\test.txt")
if err != nil {
log.Fatal(err)
}
// 输出文件
fmt.Printf("file=%v \n", file)
// 关闭文件(defer 最后结束再执行)
defer func(file *os.File) {
err := file.Close()
if err != nil {
fmt.Println("close file err=", err)
}
}(file)
// 创建读取缓冲区
reader := bufio.NewReader(file)
// 读取缓冲区内容,也就是文件内容
content, err := reader.ReadString('\n')
if err != nil && err != io.EOF {
log.Fatal(err)
}
// 打印
fmt.Printf("content=%v", content)
}
输出结果:
file=&{0xc000078a00}
content=你好,Hello Go File!!
方法二:一次性将文件读取到内存中,适用于文件不大的情况
- 使用
ReadFile()
函数读取文件内容,读取整个文件的操作已经封装在函数内,不用手动打开或关闭文件。。
- 函数原型:
func ReadFile(name string) ([]byte, error)
- 示例代码:
data, err := io.ReadFile("test.txt")
if err != nil {
log.Fatal(err)
}
代码示例:
func main() {
// 一次性读取文件
data, err := os.ReadFile("D:\\Desktop\\test.txt")
if err != nil {
log.Fatal(err)
}
fmt.Println(string(data))
}
输出结果:
你好,Hello Go File!!
- 使用
io.ReadAll()
时,需要先手动打开文件,并在读取完成后手动关闭。(不推荐,已经被舍弃)
- 函数原型:
func ReadFile(name string) ([]byte, error)
- 示例代码:
func main() {
file, err := os.Open("D:\\Desktop\\test.txt")
if err != nil {
log.Fatal(err)
}
defer file.Close()
data, err := ioutil.ReadAll(file)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(data))
}
输出结果:
你好,Hello Go File!
1.3 写入的基本操作
-
使用
os.Create()
函数创建文件- 函数原型:
func Create(name string) (*os.File, error)
- 示例代码:
file, err := os.Create("output.txt") if err != nil { log.Fatal(err) }
- 函数原型:
-
使用
file.Write()
函数将字符串写入文件
- 函数原型:
func (f *File) Write(b []byte) (n int, err error)
- 示例代码:
_, err := file.Write([]byte("Hello, world!\n"))
if err != nil {
log.Fatal(err)
}
- 关闭文件
- 示例代码:
file.Close()
将上面的步骤合起来,就是创建并写入文件的全过程:
func main() {
// 创建文件
file, err := os.Create("D:\\Desktop\\output.txt")
if err != nil {
log.Fatal("创建文件错误",err)
}
// 将字符串写入文件
_, err = file.Write([]byte("Hello, world!\n"))
if err != nil {
log.Fatal("文件写入错误:", err)
}
err = file.Close()
if err != nil {
fmt.Println("关闭文件错误:", err)
}
}
输出结果:无。打开文件,就能发现写入成功啦~~~~
1.4 使用案例(三个)
**案例一:**将一个文件的内容,写到另一个文件,注意,两个文件都已经存在。
方案一:通过缓存的方式
func main() {
// 打开原文件
inputFile, err := os.Open("D:\\Desktop\\input.txt")
if err != nil {
log.Fatal(err)
}
defer inputFile.Close()
// 创建目标文件
outputFile, err := os.Create("D:\\Desktop\\output.txt")
if err != nil {
log.Fatal(err)
}
defer outputFile.Close()
// 创建缓冲区
buffer := make([]byte, 1024)
// 读取原文件并写入目标文件
for {
// 从原文件读取数据到缓冲区
n, err := inputFile.Read(buffer)
if err != nil && err != io.EOF {
log.Fatal(err)
}
if n == 0 {
break
}
// 将数据从缓冲区写入目标文件
_, err = outputFile.Write(buffer[:n])
if err != nil {
log.Fatal(err)
}
}
log.Println("文件内容写入成功!")
}
方法二:使用io.Copy()复制io流
func main() {
// 打开原文件
inputFile, err := os.Open("D:\\Desktop\\input.txt")
if err != nil {
log.Fatal(err)
}
defer inputFile.Close()
// 创建目标文件
outputFile, err := os.Create("D:\\Desktop\\output.txt")
if err != nil {
log.Fatal(err)
}
defer outputFile.Close()
// 将原文件内容写入目标文件
_, err = io.Copy(outputFile, inputFile)
if err != nil {
log.Fatal(err)
}
log.Println("文件内容写入成功!")
}
输出结果:2023/10/26 16:29:29 文件内容写入成功!
**案例二:**将一个图片拷贝到另一个文件夹下
func main() {
srcPath := "D:\\Desktop\\img.jpg"
destPath := "D:\\Desktop\\img\\image.jpg"
err := copyFile(srcPath, destPath)
if err != nil {
fmt.Println("Failed to copy file:", err)
return
}
fmt.Println("File copied successfully!")
}
func copyFile(srcPath, destPath string) error {
srcFile, err := os.Open(srcPath)
if err != nil {
return err
}
defer srcFile.Close()
destFile, err := os.Create(destPath)
if err != nil {
return err
}
defer destFile.Close()
_, err = io.Copy(destFile, srcFile)
if err != nil {
return err
}
return nil
}
输出结果:File copied successfully!
上述代码中,srcPath和destPath分别表示源图片文件的路径和目标文件夹的路径。
在copyFile函数中,首先使用os.Open打开源图片文件,并使用os.Create创建目标文件。然后使用io.Copy将源图片文件的内容拷贝到目标文件中。最后返回nil表示拷贝成功,或者返回拷贝过程中遇到的错误。
**案例三:**统计一个文件内容里的英文、数字、空格和其他字符数量
func main() {
filePath := "D:\\Desktop\\input.txt" // 文件路径
data, err := os.ReadFile(filePath)
if err != nil {
fmt.Printf("读取文件失败:%s\n", err)
return
}
charsCount := make(map[string]int)
for _, ch := range string(data) {
switch {
case unicode.IsLetter(ch):
charsCount["英文"]++
case unicode.IsDigit(ch):
charsCount["数字"]++
case unicode.IsSpace(ch):
charsCount["空格"]++
default:
charsCount["其他字符"]++
}
}
fmt.Printf("英文字符数量:%d\n", charsCount["英文"])
fmt.Printf("数字字符数量:%d\n", charsCount["数字"])
fmt.Printf("空格数量:%d\n", charsCount["空格"])
fmt.Printf("其他字符数量:%d\n", charsCount["其他字符"])
}
输出结果:
英文字符数量:652
数字字符数量:67
空格数量:133
其他字符数量:107
2 Go语言的Json使用
JSON易于机器解析和生成,并有效地提升网络传输效率通常程序在网络传输时会先将数据(结构体、map等)序列化成son字符串到接收方得到ison字符串时,在反序列化恢复成原来的数据类型(结构体、map等)。这种方式已然成为各个语言的标准。
2.1 序列化案例
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
}
func main() {
people := []map[string]interface{}{
{
"name": "Alice",
"age": 25,
},
{
"name": "Bob",
"age": 30,
},
}
data, err := json.Marshal(people)
if err != nil {
fmt.Printf("序列化失败: %s", err)
return
}
err = ioutil.WriteFile("people.json", data, 0644)
if err != nil {
fmt.Printf("写入文件失败: %s", err)
return
}
fmt.Println("JSON数据已写入文件")
}
在代码中,我们定义了一个Person
结构体,表示每个人的姓名和年龄,这里主要添加tag
,不然序列化后的是大写,不符合公共规范。然后,我们创建了一个包含多个map
的切片people
,每个map
对应一个人的信息。
使用json.Marshal()
函数将切片people
序列化为JSON数据。json.Marshal()
函数会返回一个[]byte
类型的字节切片,表示JSON数据。
然后,我们使用ioutil.WriteFile()
函数将JSON数据写入一个名为people.json
的文件。
最后,我们输出一个提醒信息,表示JSON数据已成功写入文件。
运行以上代码,会在当前目录下生成一个名为people.json
的文件,其中包含以下JSON数据:
[
{"name":"Alice","age":25},
{"name":"Bob","age":30}
]
这个JSON数组包含了两个map
,每个map
对应一个人的姓名和年龄。
2.2 反序列化案例
将上面的代码反过来,json格式转换成对应的数据
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
}
func main() {
filePath := "D:\\Desktop\\people.json" // JSON文件路径
// 读取JSON文件内容
data, err := os.ReadFile(filePath)
if err != nil {
fmt.Printf("读取文件失败:%s\n", err)
return
}
var people []Person
// 反序列化JSON数据
err = json.Unmarshal(data, &people)
if err != nil {
fmt.Printf("反序列化失败: %s\n", err)
return
}
fmt.Printf("解析到%d个人的信息:\n", len(people))
for _, p := range people {
fmt.Printf("姓名:%s\t年龄:%d\n", p.Name, p.Age)
}
// 将JSON数据反序列化为map类型
var peopleMap []map[string]interface{}
err = json.Unmarshal(data, &peopleMap)
if err != nil {
fmt.Printf("反序列化为map失败: %s\n", err)
return
}
fmt.Printf("解析到%d个人的信息:\n", len(peopleMap))
for _, p := range peopleMap {
fmt.Printf("姓名:%s\t年龄:%v\n", p["name"], p["age"])
}
}
输出结果:
解析到2个人的信息:
姓名:Alice 年龄:25
姓名:Bob 年龄:30
解析到2个人的信息:
姓名:Alice 年龄:25
姓名:Bob 年龄:30
3 单元测试
3.1 先看个需求
在我们工作中,我们会遇到这样的情况,就是去确认一个函数,或者一个模块的结果是否正确,如下:
func addUpper(n int) int {
res := 0
for i := 1; i <= n; i++ {
res += i
}
return res
}
在 main 函数中,调用 addUpper 函数,看看实际输出的结果是否和预期的结果一致,如果一致,则说明函数正确,否则函数有错误,然后修改错误.
传统方式的缺点
- 不方便,我们需要在 main 函数中去调用,这样就需要去修改 main 函数,如果现在项目正在运行,就可能去停止项目。
- 不利于管理,因为当我们测试多个函数或者多个模块时,都需要写在 main 函数,不利于我们管理和清晰我们思路
- 引出单元测试。-> testing 测试框架 可以很好解决问题。
3.2 快速入门
Go 语言中自带有一个轻量级的测试框架 testing 和自带的 go test 命今来实现单元测试和性能测试,testing 框架和其他语言中的测试框架类似,可以基于这个框架写针对相应函数的测试用例,也可以基
于该框架写相应的压力测试用例。通过单元测试,可以解决如下问题:
- 确保每个函数是可运行,并且运行结果是正确的
- 确保写出来的代码性能是好的,
- 单元测试能及时的发现程序设计或实现的逻辑错误,使问题及早暴露,便于问题的定位解决,
1. 创建一个cal.go文件,把需要测试的代码放在里面
package test01
func AddUpper(n int) int {
res := 0
for i := 1; i <= n; i++ {
res += i
}
return res
}
2. 创建cal_test.go文件,在里面写测试案例
import (
"testing"
)
func TestAddUpper(t *testing.T) {
res := AddUpper(10)
if res != 55 {
// fmt.Printf("AddUpper(10) 执行错误,期望值=%v 实际值=%v\n", 55, res)
t.Fatalf("AddUpper(10) 执行错误,期望值=%v 实际值=%v\n", 55, res)
}
// 如果正确,输出日志
t.Logf("AddUpper(10) 执行正确。。。")
}
执行后的结果如图所示,我使用的GoLand,就比较方便:
3.3 入门总结
-
测试用例文件名必须以 test.go 结尾。 比如 cal test.go ,cal 不是固定的。
-
测试用例函数必须以 Test 开头,一般来说就是 Test+被测试的函数名,比如 TestAddUpper
-
TestAddUpper(t *tesing.T) 的形参类型必须是 *testing.T
-
一个测试用例文件中,可以有多个测试用例函数,比如 TestAddUpper、TestSub
-
运行测试用例指令
(1) cmd>go test [ 如果运行正确,无日志,错误时,会输出日志 ]
(2) cmd>go test-v [ 运行正确或是错误,都输出日志 ]
-
当出现错误时,可以使用 t.Fatalf 来格式化输出错误信息,并退出程序
-
t.Logf 方法可以输出相应的日志
-
测试用例函数,并没有放在 main 函数中,也执行了,这就是测试用例的方便之处
-
PASS 表示测试用例运行成功,FAIL 表示测试用例运行失败
-
测试单个文件,一定要带上被测试的原文件
go test -v cal test.go cal.go -
测试单个方法
go test -v -test.runTestAddUpper