文章目錄
1 讀取文件
讀文件用 os
包,需要注意的是在打開一個文件進行操作後需要及時關閉文件。用幾個小例子來說明一下。
1.1 exa1
package main
import (
"fmt"
"os"
)
func main() {
// 打開一個文件
// 概念說明:file的叫法
// 1.file 叫 file對象
// 2.file 叫 file指針
// 3.file 叫 file文件句柄
file, err := os.Open("d:/test.txt")
if err != nil {
fmt.Println("Open file err=", err)
}
// 輸出以下內容可以看出file就是一個指針
fmt.Printf("file=%v", file) // 輸出file=&{0xc000084780}
// 關閉文件
err = file.Close()
if err != nil {
fmt.Println("close file err=", err)
}
}
1.2 exa2
讀文件的內容並顯示在終端(帶緩衝區的方式)使用os.Open()
, file.close()
, bufio.NewReader()
, reader.ReadString
函數和方法。
package main
import (
"fmt"
"os"
"bufio"
"io"
)
func main() {
// 打開文件
file, err := os.Open("d:/test.txt")
if err != nil {
fmt.Println("Open file err=", err)
}
// 當函數退出時候,要及時關閉文件句柄,否則會有內存泄漏
defer file.Close()
// 創建一個帶緩衝的 *Reader
// 默認緩衝區 4096
reader := bufio.NewReader(file)
for {
str, err := reader.ReadString('\n') // 每讀到一個換行就結束一次
if err == io.EOF { // io.EOF代表文件的末尾
break
}
// 輸出內容
fmt.Print(str)
}
fmt.Println("文件讀取結束")
}
// 需要注意的是如果使用 if err == io.EOF ,那麼在我們要讀取的文件中最後一行一定是一個換行
// 否則不能讀取到最後一行
1.3 exa3
讀取文件的內容並顯示在終端(使用ioutil
一次將整個文件讀入到內存中)這種方式適用於文件不大的情況,相關方法和函數(ioutil.ReadFile),此函數封裝了打開和關閉的操作
package main
import (
"fmt"
"io/ioutil"
)
func main() {
// 使用 ioutil.ReadFile 一次性將文件讀取到位
file := "d:/test.txt"
content, err := ioutil.ReadFile(file)
if err != nil {
fmt.Printf("read file err = %v", err)
}
// 把讀取到的內容顯示到終端
fmt.Printf("%v", content) // []byte 會以byte切片的樣式輸出
// 因爲沒有顯式Open,因此也不需要顯式的Close文件文件的Open和Close功能都被封裝到函數中
fmt.Printf("%s", content) // 可以完整將字符串輸出
//fmt.Printf("%v", string(content))
}
1.4 判斷文件是否存在
golang中判斷文件或文件夾是否存在的方法是使用 os.Stat()
函數返回的錯誤值進行判斷。
package main
import (
"fmt"
"os"
)
// 判斷文件是否存在
func PathExists(path string) (bool, error) {
_, err := os.Stat(path)
if err == nil { // 如果err爲空,則證明文件存在
return true, nil
}
if os.IsNotExist(err) {
return false, nil
}
return false, err
}
func main() {
filePath := "d:/test.txt"
b, err := PathExists(filePath)
if b == true && err == nil {
fmt.Printf("%v文件存在", filePath)
return
}
fmt.Printf("%v文件不存在", filePath)
}
2 寫入文件內容
需要用到 os.OpenFile
函數
func OpenFile(name string, flag int, perm FileMode) (file *File, err error)
說明: os.OpenFile
它會使用指定的選項(如os.o_RDONLY
等)、指定的模式(如0666等,此選項只在linux系統中生效),如果成功,則返回的對象可以用於 I/O
操作
2.1 exa1
創建一個新文件,寫入內容 5 句 “hello, Gardon”
package main
import (
"fmt"
"bufio"
"os"
)
func main() {
// 創建一個新文件,寫入內容 5 句 "hello, Gardon"
// 1.打開文件 d:/abc.txt
filePath := "d:/abc.txt"
file, err := os.OpenFile(filePath, os.O_CREATE | os.O_WRONLY, 0666)
if err != nil {
fmt.Printf("open file err = %v", err)
return
}
// 準備寫入5句話
str := "hello, Gardon\r\n" // 表示換行
writer := bufio.NewWriter(file)
for i := 0; i < 5; i++ {
writer.WriteString(str)
}
// 及時關閉file句柄
defer file.Close()
// 因爲writer是帶緩存,因此在調用WriteString方法時
// 其實內容是先寫入到緩存的,所以需要調用Flush方法,將緩衝的數據真正寫入到文件中,
// 否則文件中會沒有數據
writer.Flush()
}
2.2 exa2
打開一個已經存在的文件,將原來的內容覆蓋成新的內容10句 “你好,我是老大”
package main
import (
"fmt"
"bufio"
"os"
)
func main() {
// 打開一個已經存在的文件,將原來的內容覆蓋成新的內容10句 "你好,我是老大"
// 1.打開文件 d:/abc.txt
filePath := "d:/abc.txt"
file, err := os.OpenFile(filePath, os.O_WRONLY | os.O_TRUNC , 0666)
if err != nil {
fmt.Printf("open file err = %v", err)
return
}
// 及時關閉file句柄
defer file.Close()
// 寫入10句 "你好,我是老大"
str := "你好,我是老大\r\n" // 表示換行
writer := bufio.NewWriter(file)
for i := 0; i < 10; i++ {
writer.WriteString(str)
}
writer.Flush()
}
2.3 exa3
打開一個存在的文件,在原來的內容追加內容 'ABC!ENGHSK’
package main
import (
"fmt"
"bufio"
"os"
)
func main() {
// (3)打開一個存在的文件,在原來的內容追加內容 'ABC!ENGHSK'
filePath := "d:/abc.txt"
file, err := os.OpenFile(filePath, os.O_WRONLY | os.O_APPEND , 0666)
if err != nil {
fmt.Printf("open file err = %v", err)
return
}
// 及時關閉file句柄
defer file.Close()
// 寫入10句 'ABC!ENGHSK'
str := "'ABC!ENGHSK'\r\n" // 表示換行
writer := bufio.NewWriter(file)
for i := 0; i < 10; i++ {
writer.WriteString(str)
}
writer.Flush()
}
2.4 exa4
打開一個存在的文件,將原來的內容讀出顯示在終端,並且追加5句"hello, 北京"
package main
import (
"fmt"
"bufio"
"os"
"io"
)
func main() {
// (4)打開一個存在的文件,將原來的內容讀出顯示在終端,並且追加5句"hello, 北京"
filePath := "d:/abc.txt"
file, err := os.OpenFile(filePath, os.O_RDONLY | os.O_APPEND , 0666)
if err != nil {
fmt.Printf("open file err = %v", err)
return
}
// 及時關閉file句柄
defer file.Close()
// 先讀取原來的內容
reader := bufio.NewReader(file)
for {
str, err := reader.ReadString('\n')
if err == io.EOF {
break
}
// 顯示到終端
fmt.Print(str)
}
// 寫入10句 "hello, 北京"
str := "hello, 北京\r\n" // 表示換行
writer := bufio.NewWriter(file)
for i := 0; i < 5; i++ {
writer.WriteString(str)
}
writer.Flush()
}
2.5 exa5
編寫一個程序,將一個文件的內容寫入到另一個文件。這兩個文件已經存在
package main
// 編寫一個程序,將一個文件的內容寫入到另一個文件。這兩個文件已經存在
// 使用ioutil.ReadFile / ioutil.WriteFile
import (
"fmt"
"io/ioutil"
)
func main() {
// 將d:/abc.txt文件內容導入到 f:/kkk.txt
// 聲明兩個文件名
file1Path := "d:/abc.txt"
file2Path := "f:/kkk.txt"
// 1.將d:/abc.txt內容讀取到內存
content, err := ioutil.ReadFile(file1Path)
if err != nil {
// 說明文件讀取有錯誤
fmt.Printf("read file err = %v\n", err)
return
}
// 2.將讀取到的內容寫入到 f:/kkk.txt
err = ioutil.WriteFile(file2Path, content, 0666)
if err != nil {
fmt.Printf("write file error = %v\n", err)
}
}
3 文件的拷貝
3.1 拷貝非文本文件的文件
package main
import (
"fmt"
"os"
"io"
_ "io/ioutil"
"bufio"
)
// 將一張圖片/電影/mp3文件拷貝到另一個文件中 需要用到io包(注意不是文本文件)
// io.Copy
// 編寫一個函數,接收兩個文件路徑 srcFileName dstFileName
func CopyFile(dstFileName string, srcFileName string) (written int64, err error) {
srcfile, err := os.Open(srcFileName)
if err != nil {
fmt.Printf("Open file err = %v", err)
}
defer srcfile.Close()
//通過srcfile句柄獲取到 Reader
reader := bufio.NewReader(srcfile)
// 打開dstFileName(由於這個文件可能不存在,所以不能用os.Open來打開)
dstFile, err := os.OpenFile(dstFileName, os.O_CREATE | os.O_WRONLY, 0666)
if err != nil {
fmt.Printf("open file err = %v", err)
return
}
// 通過dstFile,獲取到writer
writer := bufio.NewWriter(dstFile)
defer dstFile.Close()
return io.Copy(writer, reader)
}
func main() {
// 將 d:/flower.jpg 拷貝到 f:/abc.jpg
// 調用CopyFile完成文件的拷貝
srcFile := "d:/flower.jpg"
dstFile := "f:/abc.jpg"
_, err := CopyFile(dstFile, srcFile)
if err == nil {
fmt.Println("Copy Successd!")
} else {
fmt.Println("Copy error info = %v", err)
}
}
3.2 拷貝文本文件
package main
import (
"fmt"
"os"
"io"
_ "io/ioutil"
"bufio"
)
// 將文本文件拷貝到另一個文件中 需要用到io包,
// io.Copy
// 編寫一個函數,接收兩個文件路徑 srcFileName dstFileName
func CopyFile(dstFileName string, srcFileName string) (written int64, err error) {
srcfile, err := os.Open(srcFileName)
if err != nil {
fmt.Printf("Open file err = %v", err)
}
defer srcfile.Close()
//通過srcfile句柄獲取到 Reader
reader := bufio.NewReader(srcfile)
// 打開dstFileName(由於這個文件可能不存在,所以不能用os.Open來打開)
dstFile, err := os.OpenFile(dstFileName, os.O_CREATE | os.O_WRONLY, 0666)
if err != nil {
fmt.Printf("open file err = %v", err)
return
}
// 通過dstFile,獲取到writer
writer := bufio.NewWriter(dstFile)
// 循環把src讀取的每行數據寫入到writer
for {
str, err := reader.ReadString('\n')
if err == io.EOF { // io.EOF代表文件的末尾
break
}
// 顯示到終端
writer.WriteString(str)
}
// flush把緩衝的數據寫入到真實文件中
writer.Flush()
defer dstFile.Close()
return io.Copy(writer, reader)
}
func main() {
// 將 d:/flower.txt 拷貝到 f:/abc.txt
srcFile := "d:/flower.txt"
dstFile := "f:/abc.txt"
_, err := CopyFile(dstFile, srcFile)
if err == nil {
fmt.Println("Copy Successd!")
} else {
fmt.Println("Copy error info = %v", err)
}
}
4 命令行參數
如果希望獲取到命令行輸入的各種參數,可以使用os.Args
,它是一個string切片
,用來存儲所有的命令行參數
4.1 exa1
package main
import (
"fmt"
"os"
)
func main() {
fmt.Println("命令行的參數有", len(os.Args))
for i, v := range os.Args {
fmt.Printf("args[%v]=%v\n", i, v)
}
/*
D:\goproject\src\go_code\file\commandLineArgs>main.exe D:\goproject\src\go_code 99
命令行的參數有 3
args[0]=main.exe
args[1]=D:\goproject\src\go_code
args[2]=99
*/
}
4.2 exa2 (flag)
golang中可以用flag
包來解析命令行參數,exa1中使用的 os.Args
來獲取命令行參數比較原生,解析不是特別方便,尤其是帶有指定參數形式的命令行
package main
import (
"fmt"
"flag"
)
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.Printf("user=%v pwd=%v host=%v port=%v",
user, pwd, host, port)
/*
可以不用指定順序
D:\goproject\src\go_code\file\commandLineArgs\flag>go build -o flag.exe main.go
D:\goproject\src\go_code\file\commandLineArgs\flag>flag.exe -u root -pwd 12335465 -h 192.168.0.1 -port 8080
user=root pwd=12335465 host=192.168.0.1 port=8080
默認值
D:\goproject\src\go_code\file\commandLineArgs\flag>flag.exe -u root -pwd 12335465
user=root pwd=12335465 host=localhost port=3306
*/
}
5 json序列化及反序列化
json是一種輕量級的數據交換格式,易於人爲閱讀和編寫,同時也易於機器的解析和生成,是一種key-value
的格式。
由於json語言中,一切都是對象。因此,任何的數據類型都可以通過json來表示,例如,字符串、數字、對象、數組、map、結構體等等。
5.1 json的序列化
json的序列化是指,將現有的key-value結構的數據類型(比如結構體、map、切片)序列化成json字符串的操作。
package main
import (
"fmt"
"encoding/json"
)
// 定義一個結構體
type Monster struct {
// 如果希望json過後字段的名稱,需要打一個tag
Name string `json:"name"` // 通過反射機制實現的
Age int `json:"age"`
Birthday string
Sal float64
Skill string
}
func testStruct() {
// 聲明結構體實例
monster := Monster{
Name: "牛魔王",
Age: 500,
Birthday: "2011-11-11",
Sal: 10000.0,
Skill: "牛頭拳",
}
// 將monster序列化
data, err := json.Marshal(&monster)
if err != nil {
fmt.Printf("序列化錯誤 = %v\n", err)
}
// 輸出序列化後的結果
fmt.Printf("monster序列化後 = %v\n", string(data)) // 由於data是bate切片,所以需要轉換成字符串
}
func testMap() {
// 定義一個map
var a map[string]interface{}
// 使用map,需要make
a = make(map[string]interface{})
a["name"] = "紅孩兒"
a["age"] = 200
a["address"] = "洪崖洞"
// 將map序列化
data, err := json.Marshal(a) // 由於a map本身就是一個指針,所以不需要加引用地址的符號 &
if err != nil {
fmt.Printf("序列化錯誤 = %v\n", err)
}
// 輸出序列化後的結果
fmt.Printf("a map序列化後 = %v\n", string(data))
}
// 對切片進行序列化
func testSlice() {
var slice []map[string]interface{}
var m1 map[string]interface{}
m1 = make(map[string]interface{})
m1["name"] = "jack"
m1["age"] = 7
m1["address"] = "天津"
slice = append(slice, m1)
var m2 map[string]interface{}
m2 = make(map[string]interface{})
m2["name"] = "merry"
m2["age"] = 30
m2["address"] = [2]string{"北京", "墨西哥"}
slice = append(slice, m2)
// 將切片序列化
data, err := json.Marshal(slice) // 由於a map本身就是一個指針,所以不需要加引用地址的符號 &
if err != nil {
fmt.Printf("序列化錯誤 = %v\n", err)
}
// 輸出序列化後的結果
fmt.Printf("slice序列化後 = %v\n", string(data))
}
// 對基本數據類型序列化
func testFloat64() {
var num1 float64 = 1233.45
data, err := json.Marshal(num1) // 由於a map本身就是一個指針,所以不需要加引用地址的符號 &
if err != nil {
fmt.Printf("序列化錯誤 = %v\n", err)
}
// 輸出序列化後的結果
fmt.Printf("num1序列化後 = %v\n", string(data))
}
func main() {
// (1)將結構體進行序列化
testStruct() // monster序列化後 = {"name":"牛魔王","age":500,"Birthday":"2011-11-11","Sal":10000,"Skill":"牛頭拳"}
// (2)將map進行序列化
testMap() // a map序列化後 = {"address":"洪崖洞","age":200,"name":"紅孩兒"}
// (3)將切片進行序列化
testSlice() // slice序列化後 = [{"address":"天津","age":7,"name":"jack"},{"address":["北京","墨西哥"],"age":30,"name":"merry"}]
// (4)對基本數據類型序列化
testFloat64() // num1序列化後 = 1233.45
// 可以去 json.cn 網站上對json序列進行解析,看是否正確
}
5.2 json的反序列化
json的反序列化是指將json字符串反序列化成對應的數據類型(比如結構體、map、切片)的操作。
package main
// 反序列化
import (
"fmt"
"encoding/json"
)
// 定義一個結構體
type Monster struct {
Name string
Age int
Birthday string
Sal float64
Skill string
}
// 將json字符串,反序列化成struct
func unmarshalStruct() {
// str在實際開發項目中是通過網絡傳輸獲取到,或者是讀取文件獲取到
str := "{\"name\":\"牛魔王\",\"age\":500,\"Birthday\":\"2011-11-11\",\"Sal\":10000,\"Skill\":\"牛頭拳\"}"
// 定義一個Monster實例
var monster Monster
err := json.Unmarshal([]byte(str), &monster) // 這裏需要注意,反序列化時候需要將str轉成byte數組
if err != nil {
fmt.Printf("unmarshal err = %v\n", err)
}
fmt.Printf("反序列化後 monster = %v monster.Name=%v\n", monster, monster.Name)
}
// 反序列化map
func unmarshalMap() {
str := "{\"address\":\"洪崖洞\",\"age\":200,\"name\":\"紅孩兒\"}"
// 定義一個map
var a map[string]interface{}
// 反序列化不需要make,底層會自動make
err := json.Unmarshal([]byte(str), &a)
if err != nil {
fmt.Printf("unmarshal err = %v\n", err)
}
fmt.Printf("反序列化後 a = %v\n", a)
}
// 將json字符串,反序列化成切片
func unmarshalSlice() {
str := "[{\"address\":\"天津\",\"age\":7,\"name\":\"jack\"}," +
"{\"address\":[\"北京\",\"墨西哥\"],\"age\":30,\"name\":\"merry\"}]"
// 定義一個Slice
var slice []map[string]interface{}
// 不需要make
err := json.Unmarshal([]byte(str), &slice)
if err != nil {
fmt.Printf("unmarshal err = %v\n", err)
}
fmt.Printf("反序列化後 a = %v\n", slice)
}
func main() {
// (1)反序列化mstruct結構體
unmarshalStruct() // 反序列化後 monster = {牛魔王 500 2011-11-11 10000 牛頭拳} monster.Name=牛魔王
// (2)反序列化map
unmarshalMap() // 反序列化後 a = map[address:洪崖洞 age:200 name:紅孩兒]
// (3)反序列化Slice
unmarshalSlice()
// 注意:
// 1、反序列化一個json字符串時,要確保反序列化後的數據類型和原來序列化前的數據類型一致。
// 2、如果json字符串是通過程序獲取的,那麼就不需要對雙引號進行 \ 轉移處理
}
6 單元測試
正常情況下在寫好代碼後需要對函數的功能進行一個測試,
6.1 傳統函數功能測試
傳統的函數功能測試就是單一的對函數的返回值或者功能進行判斷,看是否符合我們的預期要求,比如我們寫一個計算數值和的函數,在測試的時候我們就對結果進行判斷,看結果是否是我們預期的值,進而實現函數的測試。
6.2 testing測試框架
Go語言中自帶有一個輕量級的測試框架testing和自帶的 go test 命令來實現單元測試和性能測試。
6.2.1 exa1
cal.go
package main
func addUpper(n int) int {
res := 0
for i := 0; i <= n; i++ {
res += i
}
return res
}
cal_test.go
package main
import (
"fmt"
"testing" // 引用go的testing框架包
)
// 編寫一個測試用例,去測試剛纔寫的 addUpper 是否正確
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) 執行正確...")
}
func TestHello(t *testing.T) {
fmt.Println("TestHello()被調用...")
}
測試
/* 輸出信息
D:\goproject\src\go_code\testingDemo\testCase>go test -v
=== RUN TestAddUpper
--- PASS: TestAddUpper (0.00s)
cal_test.go:19: AddUpper(10) 執行正確...
PASS
ok go_code/testingDemo/testCase 0.237s
*/
6.2.2 exa2
編寫一個結構體Monster,定義幾個字段,給Monster綁定Store序列化方法,序列化後保存到一個文件中,再給Monster綁定ReStore反序列化方法,可以將一個序列化的Monster從文件中讀取內容,並反序列化爲Monster對象,檢查內容是否正確,編寫測試文件對結果進行測試。
monster.go
package main
import (
"fmt"
"io/ioutil"
"encoding/json"
)
type Monster struct {
Name string
Age int
Skill string
}
func (m *Monster) Store() bool {
data, err := json.Marshal(&m)
if err != nil {
fmt.Printf("序列化錯誤 = %v\n", err)
}
// 保存到文件
filePath := "d:/monster.ser"
err = ioutil.WriteFile(filePath, data, 066)
if err != nil {
fmt.Println("Writer file err = ", err)
}
return true
}
func (m *Monster) ReStore() bool {
// 先從文件中讀取序列化的字符串
filePath := "d:/monster.ser"
data, err := ioutil.ReadFile(filePath)
if err != nil {
fmt.Println("Read file err = ", err)
return false
}
err = json.Unmarshal(data, &m)
if err != nil {
fmt.Printf("unmarshal err = %v\n", err)
return false
}
return true
}
monster_test.go
package main
import (
_ "fmt"
"testing"
)
func TestMonster_Store(t *testing.T) {
monster := Monster{
Name: "紅孩兒",
Age: 20,
Skill: "吐火",
}
res := monster.Store()
if !res {
t.Fatalf("monster.Store() test err,希望是 = %v 實際是 = %v", true, res)
}
t.Logf("monster.Store() test successed.")
}
func TestMonster_ReStore(t *testing.T) {
// 先創建一個monster實例
var monster Monster
// 通過反序列化
res := monster.ReStore()
if !res {
t.Fatalf("monster.Store() test err,希望是 = %v 實際是 = %v", true, res)
}
if monster.Name != "紅孩兒" {
t.Fatalf("monster.Store() test err,希望是 = %v 實際是 = %v", "紅孩兒", res)
}
t.Logf("monster.ReStore() test successed.")
}
6.3 注意事項
- (1)測試用例文件必須以
*_test.go
結尾。比如cal_test.go
, cal是不固定的,可變的。 - (2)測試用例函數必須以Test開頭,一般來說就是 Test+被測試的函數名,比如 TestAddUpper。
- (3)TestAddUpper(t *testing.T)的形參類型必須是 *testing.T
- (4)一個測試用例文件中,可以有多個測試用力函數,比如TestAddUpper、TestSub
- (5)運行測試用例指令
1) cmd> go test [如果運行正確,無日誌;錯誤時,會輸出日誌]
2) cmd> go test -v [運行正確或是錯誤,都輸出日誌]
- (6)當出現錯誤時,可以使用 t.Fatalf 來格式化輸出錯誤信息,並退出程序
- (7)t.Logf方法可以輸出相應的日誌
- (8)測試用例函數,並沒有放在main函數中,也是執行了,這就是測試用例的方便之處
- (9)PASS表示測試用例運行成功,FAIL表示測試用例運行失敗
- (10)測試單個文件,一定要帶上被測試的源文件
go test -v cal_test.go cal.go
go test -v cal.go cal_test.go // 測試文件和被測試文件的順序可以交換
- (11)測試單個方法
go test -v -test.run TestAddUpper