终端读写
操作终端相关文件句柄常量:
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("文件拷贝完成")
}
逐行读取
这个是《Go程序设计语言》里的一个例子,代码在这里也有:
https://github.com/adonovan/gopl.io/tree/master/ch1/dup2
上面已经有逐行读取的例子,但是这个例子的使用的方法应该更好。首先创建 bufio.Scanner 类型,然后调用 Scan() 方法,每一次调用就是读取下一行,并且会将结尾的换行符去掉。最后通过 Text() 方法来获取读到的内容。并且 Scan() 方法能读到新的一行是返回 true ,如果读不到内容了就会返回 false 。
// 打印输入中多次出现的行的个数和文本
// 它从 stdin 或指定的文件列表读取
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
counts := make(map[string]int)
files := os.Args[1:]
if len(files) == 0 {
countLines(os.Stdin, counts)
} else {
for _, arg := range files {
f, err := os.Open(arg)
if err != nil {
fmt.Fprintf(os.Stderr, "dup2: %v\n", err)
continue
}
countLines(f, counts)
f.Close()
}
}
for line, n := range counts {
if n > 1 {
fmt.Printf("%d\t%s\n", n, line)
}
}
}
func countLines(f *os.File, counts map[string]int) {
input := bufio.NewScanner(f)
for input.Scan() {
counts[input.Text()]++
}
// 注意: 忽略了 input.Err() 中有可能出现的错误
}
另外,这段程序的功能是找出重复的行并统计重复了几次,这里实现的方法也值得参考。简单来说就是使用map,把内容作为key,默认的value就是重复的次数,初始值是0。每次都是往map里追加一行的内容,如果没有这个key就是生成一个key-value,如果有相同的key,就是该行重复了,则把value自增1。
这里的错误处理,使用了简单的错误处理方法。使用Fprintf 和 %v 。这样就把信息从标准错误流上输出了。
命令行参数
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,增加一下功能:
增加持久化存储的功能
增加日志记录的功能
©著作权归作者所有:来自51CTO博客作者骑士救兵的原创作品,如需转载,请注明出处,否则将追究法律责任