Go语言7

骑士救兵 · · 1458 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

终端读写

操作终端相关文件句柄常量:

  • 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,增加一下功能:

  • 增加持久化存储的功能
  • 增加日志记录的功能

有疑问加站长微信联系(非本文作者)

本文来自:51CTO博客

感谢作者:骑士救兵

查看原文:Go语言7

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

1458 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传