这是我参与「第五届青训营 」伴学笔记创作活动的第3天。
文件操作
文件与流概念
文件为数据源,其主要作用为保存数据。
文件在程序是以输入(文件到程序)输出(程序到文件)流的形式来操作的。
流:数据在数据源(文件)和程序(内存)之间经历的路径,输入为读,输出为写。
os.File封装所有相关的标准的文件操作,是一个结构体,也是在文件操作学习中要重点掌握的模块
常用操作
1.打开一个文件,返回的对象可用于读取文件,是一个指针,称为文件对象、文件句柄等
os.Open(name string)(*File, error)
2.关闭文件
File.Close() // File是变量名哦
如对一个文件打开,什么都不干,关闭:
file, err := os.Open("c:/a.txt")
if err != nil {
fmt.Println(err)
}
fmt.Println(file) // 注意file是一个指针
err = file.Close()
if err != nil {
fmt.Println(err)
}
3.一次性读取文件全部内容到内存中(使用于文件不太大的情况)
通过这个方法 ----> ioutil.ReadFile
file := "c:/a.txt"
content, err := ioutil.ReadFile(file) // content为一个byte切片
// 注意这里没有显示的open,也不需要显式的close,这都封装到了ReadFile中
if err != nil {
fmt.Println(err)
}
fmt.Println(string(content))
4.写文件操作
格式是:
func OpenFilename string (flag int, perm FileMode) (file *File, err error)
注意:FileMode在linux或Unix中才需指,一般在win或mac上,指定为0666即可,详情可查官方文档
文件操作的重点来了,要记的操作指令集~
O_RDONLY 只读模式
O_WRONLY 只写模式
O_RDWR 读写模式
O_APPEND 写操作时将数据附加到文件末尾
O_CREATE 如果文件不存在就创建一个文件
O_EXCL 和O_CREATE配合使用,文件必须不存在
O_SYNC 打开文件用于同步I/O
O_TRUNC 如果可能,打开时清空文件,即清空文件中的内容
一个写入操作:
filePath := "c:/a.txt"
file, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE, 0666)
if err != nil {
fmt.Println(err)
return
}
// 及时关闭file句柄
defer file.Close()
str := "hello~\n"
// 写入时使用带缓存的writer
writer := bufio.NewWriter(file)
for i := 0; i < 5; i++ {
writer.WriteString(str)
}
// 注意:因为writer是带缓存的,因此在调用writerString方法时,内容是先写入了缓存,所以需要调用Flush方法,将缓冲的数据真正写入到文件中
// 否则会没有数据
writer.Flush() // 很关键!
5.判断文件是否存在
常用方法为os.Stat()函数返回的错误值进行判断:
如果返回的错误为nil,说明文件或文件夹存在
如返回的错误类型使用os.isNotExist()判断为true,即文件或文件夹不存在
如果返回的错误类型为其他,则不确定文件是否存在
func PathExist(path string) (bool, error) {
_, err := os.Stat(path)
if err == nil {
return true, nil
}
if os.IsNotExist(err) {
return false, nil
}
return false, err
}
小应用实例:带缓冲区的读取文件
写的不严谨哈,重点在应用文件操作中的方法
file, err := os.Open("c:/a.txt")
if err != nil {
fmt.Println(err)
}
// 函数退出时,要及时关闭file,否则会有内存泄露
defer file.Close()
reader := bufio.NewReader(file) // 返回一个指针类型,默认缓冲区大小为4096 bytes
for {
str, err := reader.ReadString('\n')
if err == io.EOF { // 表示读到文件的末尾
break
}
fmt.Print(str)
}
fmt.Println("over")
命令行参数
var Args []string
args这个切片保管了命令行参数,第一个是程序名,后续的会存在数组中 比如:
func main() {
fmt.Println("命令行参数有:", len(os.Args))
// 遍历args切片,就可以得到所有的命令行输入参数值
for i, v := range os.Args {
fmt.Printf("argsp[%v]=%v", i, v)
}
}
不过相对于原生的args,我们可以使用flag来解析命令行参数,它的优点是使得用户输入的参数顺序可以是随意的。
比如获取用户登录Mysql时,其账号、密码等:
import (
"flag"
"fmt"
)
func main() {
var user string
var pwd string
var host string
var port int
// &user 是用于接收用户命令行中输入的-u后面的参数值
// "u"是-u指定的参数
// "" 默认值
flag.StringVar(&user, "u", "", "用户名默认为空")
flag.StringVar(&pwd, "pwd", "", "密码默认为空")
flag.StringVar(&host, "h", "localhost", "主机名默认为localhost")
flag.IntVar(&port, "port", 3306, "端口号默认为3306")
// 必要操作,转换必须调用该方法
flag.Parse()
fmt.Println(user, pwd, port, host)
}
JSON(反)序列化
在go中,序列化结构体、map、slice、基本数据类型等使用json.Marshal。
序列化实例
将结构体序列化:
func testStruct() {
monster := Monster{
Name: "哥斯拉",
Age: 18,
Birthday: "2004-11-11",
Salary: 48853.2,
Skill: "正义冲拳",
}
// 序列化
data, err := json.Marshal(&monster) // data是byte切片
if err != nil {
fmt.Println(err)
}
fmt.Println(string(data))
}
将map序列化:
func testMap() {
var a map[string]interface{}
a = make(map[string]interface{})
a["name"] = "哥斯拉"
a["age"] = 18
a["address"] = "福田县"
data, err := json.Marshal(&a) // data是byte切片
if err != nil {
fmt.Println(err)
}
fmt.Println(string(data))
}
tag修改json字段
type Monster struct {
Name string `json:"name"`
Age int `json:"age"`
}
通过tag,我们可以实现将随意设置序列化后的字段名,它存在的意义是,由于字段名是必须要首字母大写的,否则为私有会使得无法Json序列化,tag使我们拥有了更多的可操作性与个性化。
其实现的原理是反射机制,有兴趣的同学可以自行去了解。
反序列化
err := json.Unmarshal([]byte(a), &b)
a:被序列化的内容
b:接收序列化结果的变量,是引用传递
注意:在反序列时,map、slice时不需要make啦,因为Unmarshal中封装了make~可以直接使用map和slice。
单元测试
go中自带一个轻量级的测试框架testing和自带的go test命令来实现单元测试和性能测试,可以基于这个框架写针对相应函数的测试用例,也可以基于该框架写相应的压力测试用例。
如我们来简单的给一个加法器函数进行测试:
func addUpper(n int) int {
res := 0
for i := 1; i <= n; i++ {
res += 1
}
return res
}
编写测试用例:
import (
"testing"
)
// 编写测试用例
func TestAddUpper(t *testing.T) {
// 调用
res := addUpper(10)
if res != 10 {
t.Fatalf("执行错误 %v", res)
}
// 正确则输出日志
t.Logf("执行正确")
}
func main() {
}
注意事项
1.测试用例文件名必须以_test.go结尾;
2.测试用例函数必须以Test开头,一般来说是Test+被测试的函数名,比如TestAddUpper;
3.测试用例函数的形参类型必须是*testing.T;
4.一个测试用例文件中,可以有多个测试用例函数;
5.运行测试用例指令
(1) cmd -> go test 如果运行正确,无日志,错误时会输出日志
(2) cmd -> go test-v 运行正确或错误都会输出日志
6.当出现错误,可以使用t.Fatalf来格式化输出错误信息,并退出程序;
7.t.Logf方法可以输出相应的日志;
8.测试用例函数及时并没有放在main函数中,也会被执行,这是由testing框架封装好的;
9.PASS: 成功 FAIL: 失败。
以上内容若有不正之处,恳请您不吝指正!