为了账号安全,请及时绑定邮箱和手机立即绑定

Go语言7

标签:
Go

终端读写

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

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博客作者骑士救兵的原创作品,如需转载,请注明出处,否则将追究法律责任


点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消