Go語言7
終端讀寫
操作終端相關檔案控制代碼常量:
os.Stdin
: 標準輸入os.Stdout
: 標準輸出os.Stderr
: 標準錯誤
這個是fmt包裡的一個方法,列印到檔案。比平時用的fmt列印多一個引數,這個引數接收的就是檔案控制代碼,一個實現了 io.Winter
的介面:
func Fprint(w io.Writer, a ...interface{}) (n int, err error)
把終端的標準輸出的檔案控制代碼傳入,就是列印到標準輸出,即螢幕:
package main import ( "os" "fmt" ) func main(){ fmt.Fprintln(os.Stdout, "TEST") }
終端輸入
先列印提示資訊,然後獲取使用者輸入的值,最後打印出來:
package main import "fmt" var firstName, lastName string func main(){ fmt.Print("Please enter your full name:") fmt.Scanln(&firstName, &lastName) // fmt.Scanf("%s %s", &firstName, &lastName) // 和上面那句效果一樣 fmt.Printf("Hi %s %s.\n", firstName, lastName) }
把字串作為格式的化輸入
使用 fmt 包裡的 Sscanf()方法:
func Sscanf(str string, format string, a ...interface{}) (n int, err error)
Scanf 掃描實參 string,並將連續由空格分隔的值儲存為連續的實參, 其格式由 format 決定。它返回成功解析的條目數。
不是很好理解的話,參考下下面的例子:
package main import "fmt" func main(){ var ( input = "12.34 567 Golang" // 要掃描的字串 format = "%f %d %s" // 每段字串的格式 i float32 // 對應格式的變數,把字串裡的每一段賦值到這些變數裡 j int k string ) fmt.Sscanf(input, format, &i, &j, &k) fmt.Println(i) fmt.Println(j) fmt.Println(k) }
帶緩衝區的讀寫
不直接操作 io,在緩衝區裡進行讀寫,io的操作交由作業系統處理,主要是解決效能的問題。
這裡要使用 bufio 包,下面是緩衝區進行讀操作的示例:
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
var inputReader *bufio.Reader // bufio包裡的一個結構體型別
// 給上面的結構體賦值,包裡提供了建構函式
inputReader = bufio.NewReader(os.Stdin) // 生成例項,之後要呼叫裡面的方法
fmt.Print("請隨意輸入內容: ")
// 呼叫例項的方法進行讀操作,就是帶緩衝區的操作了
input, err := inputReader.ReadString('\n') // 這裡是字元型別
if err == nil {
fmt.Println(input)
}
}
上面是從終端讀取,檔案讀寫下面會講,先來個從檔案讀取的示例:
package main
import (
"bufio"
"fmt"
"os"
"strings"
"io"
)
func main() {
file, err := os.Open("test.txt")
if err != nil {
fmt.Println("ERROR:", err)
return
}
defer file.Close() // 函式返回時關閉檔案
bufReader := bufio.NewReader(file)
for {
line, err := bufReader.ReadString('\n')
// 最後一行會同時返回 line 和 err,所以先列印
fmt.Println(strings.TrimSpace(line))
if err != nil {
if err == io.EOF {
fmt.Println("讀取完畢")
break
} else {
fmt.Println("讀取檔案錯誤:", err)
return
}
}
}
}
檔案讀寫
os.File 是個結構體,封裝了所有檔案相關的操作。之前講的 os.Stdin、os.Stdout、os.Stderr 都是檔案控制代碼,都是 *os.File
讀取整個檔案
"io/ioutil" 可以直接把整個檔案讀取出來,適合檔案不是很大的情況:
package main
import (
"fmt"
"io/ioutil"
)
func main() {
buf, err := ioutil.ReadFile("test.txt")
if err != nil {
fmt.Println("ERROR", err)
return
}
fmt.Println(string(buf)) // buf是[]byte型別,要轉字串
// 寫操作
err = ioutil.WriteFile("wtest.txt", buf, 0x64)
if err != nil {
panic(err.Error())
}
}
上面還有整個檔案寫入的操作。
讀取壓縮檔案
下面的程式碼是解壓讀取一個 .gz 檔案,注意不是 .tar.gz 。打了tar包應該是不行的:
package main
import (
"compress/gzip"
"os"
"fmt"
"bufio"
"io"
"strings"
)
func main() {
fileName := "test.gz"
var reader *bufio.Reader
file, err := os.Open(fileName)
if err != nil {
fmt.Println("Open ERROE:", err)
os.Exit(1)
}
defer file.Close() // 記得關檔案
gzFile, err := gzip.NewReader(file)
if err != nil {
fmt.Println("gz ERROR:", err)
return
}
reader = bufio.NewReader(gzFile)
for {
line, err := reader.ReadString('\n')
fmt.Println(strings.TrimSpace(line))
if err != nil {
if err == io.EOF {
fmt.Println("讀取完畢")
break
} else {
fmt.Println("Read ERROR:", err)
os.Exit(0)
}
}
}
}
檔案寫入
寫入檔案的命令:
os.OpenFile("output.dat", os.O_WRONLY|os.O_CREATE, 0666)
第二個引數是檔案開啟模式:
- os.O_WRONLY : 只寫
- os.O_CREATE : 建立檔案
- os.O_RDONLY : 只讀
- os.O_RDWR : 讀寫
- os.O_TRUNC : 清空
第三個引數是許可權控制,同Linux的ugo許可權。
檔案寫入的示例:
package main
import (
"bufio"
"fmt"
"os"
"strconv"
)
func main() {
outputFile, err := os.OpenFile("test.txt", os.O_WRONLY|os.O_CREATE, 0666)
if err != nil {
fmt.Println("ERROR", err)
return
}
defer outputFile.Close()
outputWriter := bufio.NewWriter(outputFile)
outputString := "Hello World! "
for i := 0; i < 10; i++ {
outputWriter.WriteString(outputString + strconv.Itoa(i) + "\n")
}
outputWriter.Flush() // 強制重新整理,儲存到磁碟
}
拷貝檔案
首先分別開啟2個檔案,然後拷貝檔案只要一次呼叫傳入2個檔案控制代碼就完成了:
package main
import (
"io"
"fmt"
"os"
)
func CopyFile(dstName, srcName string) (written int64, err error) {
src, err := os.Open(srcName)
if err != nil {
fmt.Println("Open ERROR", err)
return
}
defer src.Close()
dst, err := os.OpenFile(dstName, os.O_WRONLY|os.O_CREATE, 0644)
if err != nil {
fmt.Println("OpenFile ERROR", err)
return
}
defer dst.Close()
// 先依次把2個檔案都開啟,然後拷貝只要下面這一句
return io.Copy(dst, src)
}
func main() {
CopyFile("test_copy.txt", "test.txt")
fmt.Println("檔案拷貝完成")
}
命令列引數
os.Args 是一個 string 的切片,用來儲存所有的命令列引數。
package main
import (
"os"
"fmt"
)
func main() {
fmt.Println(len(os.Args))
for i, v := range os.Args {
fmt.Println(i, v)
}
}
/* 執行結果
PS H:\Go\src\go_dev\day7\args\beginning> go run main.go arg1 arg2 arg3
4
0 [省略敏感資訊]\main.exe
1 arg1
2 arg2
3 arg3
PS H:\Go\src\go_dev\day7\args\beginning>
*/
os.Args 至少有一個元素,如果一個引數也不打,第一個元素就是命令本身。之後的命令列引數從下標1開始儲存。
解析命令列引數
flag 包實現命令列標籤解析。
func BoolVar(p *bool, name string, value bool, usage string)
func StringVar(p *string, name string, value string, usage string)
func IntVar(p *int, name string, value int, usage string)
第一個引數是個指標,指向要接收的引數的值
第二個引數是指定的名字
第三個引數是預設值
第四個引數是用法說明
用法示例:
package main
import (
"fmt"
"flag"
)
func main() {
var (
enable bool
conf string
num int
)
flag.BoolVar(&enable, "b", false, "是否啟用")
flag.StringVar(&conf, "s", "test.conf", "配置檔案")
flag.IntVar(&num, "i", 0, "數量")
flag.Parse() // 讀取命令列引數進行解析
fmt.Println(enable, conf, num)
}
/* 執行結果
PS H:\Go\src\go_dev\day7\args\flag_var> go run main.go
false test.conf 0
PS H:\Go\src\go_dev\day7\args\flag_var> go run main.go -b -s default.conf -i 10
true default.conf 10
PS H:\Go\src\go_dev\day7\args\flag_var>
*/
Json資料協議
匯入包
import "encoding/json"
序列化
json.Marshal(data interface{})
示例:
package main
import (
"encoding/json"
"fmt"
)
type User struct {
UserName string `json:"username"`
NickName string `json:"nickname"`
Age int `json:"age"`
Vip bool `json:"vip"`
}
func main() {
u1 := &User{
UserName: "Sara",
NickName: "White Canary",
Age: 29,
Vip: true,
}
if data, err := json.Marshal(u1); err == nil{
fmt.Println(string(data))
}
}
反序列化
json.Unmarshal(data []byte, v interface{})
示例:
package main
import (
"encoding/json"
"fmt"
)
type User struct {
UserName string `json:"username"`
NickName string `json:"nickname"`
Age int `json:"age"`
Vip bool `json:"vip"`
}
func main() {
var jsonStr = `{
"username": "Kara",
"nickname": "Supergirl",
"age": 20,
"vip": false
}`
var jsonByte = []byte(jsonStr)
var u2 User
if err := json.Unmarshal(jsonByte, &u2); err == nil {
fmt.Println(u2)
} else {
fmt.Println("ERROR:", err)
}
}
錯誤處理
error 型別是在 builtin 包裡定義的。error 是個介面,裡面實現了一個 Error() 的方法,返回一個字串:
type error interface {
Error() string
}
所以其實 error 也就是個字串資訊。
定義錯誤
error 包實現了用於錯誤處理的函式。
New 返回一個按給定文字格式化的錯誤:
package main
import (
"errors"
"fmt"
)
var errNotFound error = errors.New("Not found error")
func main() {
fmt.Println("ERROR:", errNotFound)
}
平時簡單這樣用用就可以了,也很方便。不過學習嘛,下面稍微再深入點。
自定義錯誤
主要是學習,上面的 New() 函式用起來更加方便。
使用自定義錯誤返回:
package main
import (
"fmt"
"os"
)
type PathError struct {
Op string
Path string
err string // 把這個資訊隱藏起來,所以是小寫
}
// 實現error的介面
func (e *PathError) Error() string {
return e.Op + " " + e.Path + " 路徑不存在\n原始錯誤資訊: " + e.err
}
func Open(filename string) error {
file, err := os.Open(filename)
if err != nil {
return &PathError{
Op: "read",
Path: filename,
err: err.Error(),
}
}
defer file.Close()
return nil
}
func main() {
err := Open("test.txt")
if err != nil {
fmt.Println(err)
}
}
/* 執行結果
PS H:\Go\src\go_dev\day7\error\diy_error> go run main.go
read test.txt 路徑不存在
原始錯誤資訊: open test.txt: The system cannot find the file specified.
PS H:\Go\src\go_dev\day7\error\diy_error>
*/
判斷自定義錯誤
這裡用 switch 來判斷:
switch err := err.(type) {
case ParseError:
PrintParseError(err)
case.PathError:
PrintPathError(err)
default:
fmt.Println(err)
}
異常和捕捉
首先呼叫 panic 來丟擲異常:
package main
func badCall() {
panic("bad end")
}
func main() {
badCall()
}
/* 執行結果
PS H:\Go\src\go_dev\day7\error\panic> go run main.go
panic: bad end
goroutine 1 [running]:
main.badCall()
H:/Go/src/go_dev/day7/error/panic/main.go:4 +0x40
main.main()
H:/Go/src/go_dev/day7/error/panic/main.go:8 +0x27
exit status 2
PS H:\Go\src\go_dev\day7\error\panic>
*/
執行後就丟擲異常了,但是這樣程式也崩潰了。
下面來捕獲異常,go裡沒有try之類來捕獲異常,所以panic了就是真的異常了,但是還不會馬上就崩潰。panic的函式並不會立刻返回,而是先defer,再返回。如果有辦法將panic捕獲到,並阻止panic傳遞,就正常處理,如果沒有沒有捕獲,程式直接異常終止。這裡並不是像別的語言裡那樣捕獲異常,因為即使捕獲到了,也只是執行defer,之後還是要異常終止的,而不是繼續在錯誤的點往下執行。
注意:就像上面說的,在go裡panic了就是真的異常了。recover之後,邏輯並不會恢復到panic那個點去,函式還是會在defer之後返回。
下面是使用 defer 處理異常的示例:
package main
import "fmt"
func badCall() {
panic("bad end")
}
func test() {
// 用defer在最後捕獲異常
defer func() {
if e := recover(); e != nil {
fmt.Println("Panic", e)
}
}()
badCall()
}
func main() {
test()
}
所以像 python 裡的 try except 那樣捕獲異常,在go裡,大概就是返回個 err(error型別) ,然後判斷一下 err 是不是 nil。
課後作業
實現一個圖書管理系統v3,增加一下功能:
- 增加持久化儲存的功能
- 增加日誌記錄的功能