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,增加一下功能:
- 增加持久化儲存的功能
- 增加日誌記錄的功能