Go语言不太快速的入门(4),详解向文章| 青训营笔记

115 阅读2分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第3天。

文件操作

文件与流概念

文件为数据源,其主要作用为保存数据

文件在程序是以输入(文件到程序)输出(程序到文件)流的形式来操作的。

流:数据在数据源(文件)和程序(内存)之间经历的路径,输入为读,输出为写

image.png

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: 失败。

以上内容若有不正之处,恳请您不吝指正!