读取用户的输入

Scan 和 Sscan

从键盘和标准输入 os.Stdin 读取输入,最简单的办法是使用 fmt 包提供的 Scan 和 Sscan 开头的函数。

  1. // 从控制台读取输入:
  2. package main
  3. import "fmt"
  4. var (
  5. firstName, lastName, s string
  6. i int
  7. f float32
  8. input = "56.12 / 5212 / Go"
  9. format = "%f / %d / %s"
  10. )
  11. func main() {
  12. fmt.Println("Please enter your full name: ")
  13. fmt.Scanln(&firstName, &lastName)
  14. // fmt.Scanf("%s %s", &firstName, &lastName)
  15. fmt.Printf("Hi %s %s!\n", firstName, lastName) // Hi Chris Naegels
  16. fmt.Sscanf(input, format, &f, &i, &s)
  17. fmt.Println("From the string we read: ", f, i, s)
  18. // 输出结果: From the string we read: 56.12 5212 Go
  19. }
  • Scanln 扫描来自标准输入的文本,将空格分隔的值依次存放到后续的参数内,直到碰到换行。
  • Scanf 与其类似,除了 Scanf 的第一个参数用作格式字符串,用来决定如何读取。
  • Sscan 和以 Sscan 开头的函数则是从字符串读取,除此之外,与 Scanf 相同。

也可以使用 bufio 包提供的缓冲读取(buffered reader)来读取数据

  1. package main
  2. import (
  3. "fmt"
  4. "bufio"
  5. "os"
  6. )
  7. var inputReader *bufio.Reader
  8. var input string
  9. var err error
  10. func main() {
  11. // inputReader := bufio.NewReader(os.Stdin)
  12. // input, err := inputReader.ReadString('\n')
  13. inputReader = bufio.NewReader(os.Stdin)
  14. fmt.Println("Please enter some input: ")
  15. input, err = inputReader.ReadString('\n')
  16. if err == nil {
  17. fmt.Printf("The input was: %s\n", input)
  18. }
  19. }
  • inputReader 是一个指向 bufio.Reader 的指针。inputReader := bufio.NewReader(os.Stdin) 这行代码,将会创建一个读取器,并将其与标准输入绑定。
  • bufio.NewReader() 构造函数的签名为:func NewReader(rd io.Reader) *Reader

使用 switch 语句

  1. package main
  2. import (
  3. "fmt"
  4. "os"
  5. "bufio"
  6. )
  7. func main() {
  8. inputReader := bufio.NewReader(os.Stdin)
  9. fmt.Println("Please enter your name:")
  10. input, err := inputReader.ReadString('\n')
  11. if err != nil {
  12. fmt.Println("There were errors reading, exiting program.")
  13. return
  14. }
  15. fmt.Printf("Your name is %s", input)
  16. // For Unix: test with delimiter "\n", for Windows: test with "\r\n"
  17. switch input {
  18. case "Philip\r\n": fmt.Println("Welcome Philip!")
  19. case "Chris\r\n": fmt.Println("Welcome Chris!")
  20. case "Ivo\r\n": fmt.Println("Welcome Ivo!")
  21. default: fmt.Printf("You are not welcome here! Goodbye!")
  22. }
  23. // version 2:
  24. switch input {
  25. case "Philip\r\n": fallthrough
  26. case "Ivo\r\n": fallthrough
  27. case "Chris\r\n": fmt.Printf("Welcome %s\n", input)
  28. default: fmt.Printf("You are not welcome here! Goodbye!\n")
  29. }
  30. // version 3:
  31. switch input {
  32. case "Philip\r\n", "Ivo\r\n": fmt.Printf("Welcome %s\n", input)
  33. default: fmt.Printf("You are not welcome here! Goodbye!\n")
  34. }
  35. }

文件读写

读文件

  • 文件使用指向 os.File 类型的指针来表示的,也叫做文件句柄
    • 在前面章节使用到过标准输入 os.Stdin 和标准输出 os.Stdout,他们的类型都是 *os.File

示例:

  1. package main
  2. import (
  3. "bufio"
  4. "fmt"
  5. "io"
  6. "os"
  7. )
  8. func main() {
  9. inputFile, inputError := os.Open("input.dat")
  10. if inputError != nil {
  11. fmt.Printf("An error occurred on opening the inputfile\n" +
  12. "Does the file exist?\n" +
  13. "Have you got access to it?\n")
  14. return // exit the function on error
  15. }
  16. defer inputFile.Close()
  17. inputReader := bufio.NewReader(inputFile)
  18. for {
  19. inputString, readerError := inputReader.ReadString('\n')
  20. fmt.Printf("The input was: %s", inputString)
  21. if readerError == io.EOF {
  22. return
  23. }
  24. }
  25. }
  • 变量 inputFile*os.File 类型的
  • 使用 os 包里的 Open 函数来打开一个文件
  • 使用 defer inputFile.Close() 语句确保在程序退出前关闭该文件
  • 使用 bufio.NewReader 来获得一个读取器变量
  • 使用 ReadString('\n')ReadBytes('\n') 将文件的内容逐行(行结束符 ‘\n’)读取出来
    • 注意:Unix 和 Linux 的行结束符是 \n,而 Windows 的行结束符是 \r\n
    • 在使用 ReadStringReadBytes 方法的时候,我们不需要关心操作系统的类型,直接使用 \n 就可以了。
    • 也可以使用 ReadLine() 方法来实现相同的功能
  • 一旦读取到文件末尾,变量 readerError 的值将变成非空(事实上,其值为常量 io.EOF

其他读文件函数

  1. 将整个文件的内容读到一个字符串里

可以使用 io/ioutil 包里的 ioutil.ReadFile() 方法,该方法第一个返回值的类型是 []byte,第二个返回值是错误

  1. func main() {
  2. inputFile := "products.txt"
  3. outputFile := "products_copy.txt"
  4. buf, err := ioutil.ReadFile(inputFile)
  5. if err != nil {
  6. fmt.Fprintf(os.Stderr, "File Error: %s\n", err)
  7. // panic(err.Error())
  8. }
  9. fmt.Printf("%s\n", string(buf))
  10. err = ioutil.WriteFile(outputFile, buf, 0644) // oct, not hex
  11. if err != nil {
  12. panic(err.Error())
  13. }
  14. }
  1. 带缓冲的读取

在很多情况下,文件的内容是不按行划分的,或者干脆就是一个二进制文件。使用 bufio.ReaderRead(),它只接收一个参数:

  1. buf := make([]byte, 1024)
  2. ...
  3. n, err := inputReader.Read(buf)
  4. if (n == 0) { break}

变量 n 的值表示读取到的字节数.

  1. 按列读取文件中的数据

如果数据是按列排列并用空格分隔的,你可以使用 fmt 包提供的以 FScan 开头的一系列函数来读取他们

  1. package main
  2. import (
  3. "fmt"
  4. "os"
  5. )
  6. func main() {
  7. file, err := os.Open("products2.txt")
  8. if err != nil {
  9. panic(err)
  10. }
  11. defer file.Close()
  12. var col1, col2, col3 []string
  13. for {
  14. var v1, v2, v3 string
  15. _, err := fmt.Fscanln(file, &v1, &v2, &v3)
  16. // scans until newline
  17. if err != nil {
  18. break
  19. }
  20. col1 = append(col1, v1)
  21. col2 = append(col2, v2)
  22. col3 = append(col3, v3)
  23. }
  24. fmt.Println(col1)
  25. fmt.Println(col2)
  26. fmt.Println(col3)
  27. }

输出结果:

  1. [ABC FUNC GO]
  2. [40 56 45]
  3. [150 280 356]
  1. 关于文件路径

path 包里包含一个子包叫 filepath,这个子包提供了跨平台的函数,用于处理文件名和路径。例如 Base() 函数用于获得路径中的最后一个元素(不包含后面的分隔符):

  1. import "path/filepath"
  2. filename := filepath.Base(path)

compress包:读取压缩文件

compress包提供了读取压缩文件的功能,支持的压缩文件格式为:bzip2、flate、gzip、lzw 和 zlib。

示例:读取一个 gzip 文件

  1. package main
  2. import (
  3. "fmt"
  4. "bufio"
  5. "os"
  6. "compress/gzip"
  7. )
  8. func main() {
  9. fName := "MyFile.gz"
  10. var r *bufio.Reader
  11. fi, err := os.Open(fName)
  12. if err != nil {
  13. fmt.Fprintf(os.Stderr, "%v, Can't open %s: error: %s\n", os.Args[0], fName,
  14. err)
  15. os.Exit(1)
  16. }
  17. defer fi.Close()
  18. fz, err := gzip.NewReader(fi)
  19. if err != nil {
  20. r = bufio.NewReader(fi)
  21. } else {
  22. r = bufio.NewReader(fz)
  23. }
  24. for {
  25. line, err := r.ReadString('\n')
  26. if err != nil {
  27. fmt.Println("Done reading file")
  28. os.Exit(0)
  29. }
  30. fmt.Println(line)
  31. }
  32. }

写文件

示例:

  1. package main
  2. import (
  3. "os"
  4. "bufio"
  5. "fmt"
  6. )
  7. func main () {
  8. // var outputWriter *bufio.Writer
  9. // var outputFile *os.File
  10. // var outputError os.Error
  11. // var outputString string
  12. outputFile, outputError := os.OpenFile("output.dat", os.O_WRONLY|os.O_CREATE, 0666)
  13. if outputError != nil {
  14. fmt.Printf("An error occurred with file opening or creation\n")
  15. return
  16. }
  17. defer outputFile.Close()
  18. outputWriter := bufio.NewWriter(outputFile)
  19. outputString := "hello world!\n"
  20. for i:=0; i<10; i++ {
  21. outputWriter.WriteString(outputString)
  22. }
  23. outputWriter.Flush()
  24. }
  • OpenFile 函数有三个参数:文件名、一个或多个标志(使用逻辑运算符“|”连接),使用的文件权限
    • os.O_RDONLY:只读
    • os.O_WRONLY:只写
    • os.O_CREATE:创建:如果指定文件不存在,就创建该文件。
    • os.O_TRUNC:截断:如果指定文件已存在,就将该文件的长度截为 0 。
    • 在读文件的时候,文件的权限是被忽略的,所以在使用 OpenFile 时传入的第三个参数可以用 0 。而在写文件时,不管是 Unix 还是 Windows,都需要使用 0666。
  • 创建一个写入器outputWriter := bufio.NewWriter(outputFile),将字符串写入缓冲区outputWriter.WriteString(outputString)
  • 缓冲区的内容紧接着被完全写入文件:outputWriter.Flush()

如果写入的东西很简单,我们可以使用 fmt.Fprintf(outputFile, "Some test data.\n") 直接将内容写入文件。fmt 包里的 F 开头的 Print 函数可以直接写入任何 io.Writer,包括文件

使用 os.Stdout.WriteString("hello, world\n"),我们可以输出到屏幕。

文件拷贝

  1. // filecopy.go
  2. package main
  3. import (
  4. "fmt"
  5. "io"
  6. "os"
  7. )
  8. func main() {
  9. CopyFile("target.txt", "source.txt")
  10. fmt.Println("Copy done!")
  11. }
  12. func CopyFile(dstName, srcName string) (written int64, err error) {
  13. src, err := os.Open(srcName)
  14. if err != nil {
  15. return
  16. }
  17. defer src.Close()
  18. dst, err := os.Create(dstName)
  19. if err != nil {
  20. return
  21. }
  22. defer dst.Close()
  23. return io.Copy(dst, src)
  24. }

注意 **defer** 的使用:当打开 dst 文件时发生了错误,那么 **defer** 仍然能够确保 **src.Close()** 执行。如果不这么做,src 文件会一直保持打开状态并占用资源。

从命令行读取参数

通过os包

os 包中有一个 string 类型的切片变量 os.Args,用来处理一些基本的命令行参数,它在程序启动后读取命令行输入的参数。

  • 类似于python的sys.args

示例:

  1. package main
  2. import (
  3. "fmt"
  4. "os"
  5. "strings"
  6. )
  7. func main() {
  8. who := "Alice "
  9. if len(os.Args) > 1 {
  10. who += strings.Join(os.Args[1:], " ")
  11. }
  12. fmt.Println("Good Morning", who)
  13. }

可以在命令行加入参数,像这样:os_args John Bill Marc Luke,将得到这样的输出:Good Morning Alice John Bill Marc Luke

通过flag包

在 flag 包中有一个 Flag 被定义成一个含有如下字段的结构体:

  1. type Flag struct {
  2. Name string // name as it appears on command line
  3. Usage string // help message
  4. Value Value // value as set
  5. DefValue string // default value (as text); for usage message
  6. }
  1. package main
  2. import (
  3. "flag" // command line option parser
  4. "os"
  5. )
  6. var NewLine = flag.Bool("n", false, "print newline") // echo -n flag, of type *bool
  7. const (
  8. Space = " "
  9. Newline = "\n"
  10. )
  11. func main() {
  12. flag.PrintDefaults()
  13. flag.Parse() // Scans the arg list and sets up flags
  14. var s string = ""
  15. for i := 0; i < flag.NArg(); i++ {
  16. if i > 0 {
  17. s += " "
  18. if *NewLine { // -n is parsed, flag becomes true
  19. s += Newline
  20. }
  21. }
  22. s += flag.Arg(i)
  23. }
  24. os.Stdout.WriteString(s)
  25. }
  • flag.Parse() 扫描参数列表(或者常量列表)并设置 flag, flag.Arg(i) 表示第 i 个参数。Parse() 之后 flag.Arg(i) 全部可用,flag.Arg(0) 就是第一个真实的 flag,而不是像 os.Args(0) 放置程序的名字
  • flag.Narg() 返回参数的数量。
  • flag.VisitAll(fn func(*Flag)) 是另一个有用的功能:按照字典顺序遍历 flag,并且对每个标签调用 fn

用 buffer 读取文件

  1. package main
  2. import (
  3. "bufio"
  4. "flag"
  5. "fmt"
  6. "io"
  7. "os"
  8. )
  9. func cat(r *bufio.Reader) {
  10. for {
  11. buf, err := r.ReadBytes('\n')
  12. fmt.Fprintf(os.Stdout, "%s", buf)
  13. if err == io.EOF {
  14. break
  15. }
  16. }
  17. return
  18. }
  19. func main() {
  20. flag.Parse()
  21. if flag.NArg() == 0 {
  22. cat(bufio.NewReader(os.Stdin))
  23. }
  24. for i := 0; i < flag.NArg(); i++ {
  25. f, err := os.Open(flag.Arg(i))
  26. if err != nil {
  27. fmt.Fprintf(os.Stderr, "%s:error reading from %s: %s\n", os.Args[0], flag.Arg(i), err.Error())
  28. continue
  29. }
  30. cat(bufio.NewReader(f))
  31. f.Close()
  32. }
  33. }

用切片读写文件

  1. package main
  2. import (
  3. "flag"
  4. "fmt"
  5. "os"
  6. )
  7. func cat(f *os.File) {
  8. const NBUF = 512
  9. var buf [NBUF]byte
  10. for {
  11. switch nr, err := f.Read(buf[:]); true {
  12. case nr < 0:
  13. fmt.Fprintf(os.Stderr, "cat: error reading: %s\n", err.Error())
  14. os.Exit(1)
  15. case nr == 0: // EOF
  16. return
  17. case nr > 0:
  18. if nw, ew := os.Stdout.Write(buf[0:nr]); nw != nr {
  19. fmt.Fprintf(os.Stderr, "cat: error writing: %s\n", ew.Error())
  20. }
  21. }
  22. }
  23. }
  24. func main() {
  25. flag.Parse() // Scans the arg list and sets up flags
  26. if flag.NArg() == 0 {
  27. cat(os.Stdin)
  28. }
  29. for i := 0; i < flag.NArg(); i++ {
  30. f, err := os.Open(flag.Arg(i))
  31. if f == nil {
  32. fmt.Fprintf(os.Stderr, "cat: can't open %s: error %s\n", flag.Arg(i), err)
  33. os.Exit(1)
  34. }
  35. cat(f)
  36. f.Close()
  37. }
  38. }

用 defer 关闭文件

defer 关键字对于在函数结束时关闭打开的文件非常有用,例如下面的代码片段:

  1. func data(name string) string {
  2. f, _ := os.OpenFile(name, os.O_RDONLY, 0)
  3. defer f.Close() // idiomatic Go code!
  4. contents, _ := ioutil.ReadAll(f)
  5. return string(contents)
  6. }

在函数 return 后执行了 f.Close()

golang中对不同数据格式的处理

  • 数据结构 —> 指定格式 = 序列化编码(传输之前)
  • 指定格式 —> 数据结构 = 反序列化解码(传输之后)

序列化是在内存中把数据转换成指定格式(data -> string),反之亦然(string -> data)。

编码也是一样的,只是输出一个数据流(实现了 io.Writer 接口);解码是从一个数据流(实现了 io.Reader)输出到一个数据结构。

JSON

Go 语言的 json 包可以让你在程序中方便的读取和写入 JSON 数据。)json.Marshal

  1. // json.go
  2. package main
  3. import (
  4. "encoding/json"
  5. "fmt"
  6. "log"
  7. "os"
  8. )
  9. type Address struct {
  10. Type string
  11. City string
  12. Country string
  13. }
  14. type VCard struct {
  15. FirstName string
  16. LastName string
  17. Addresses []*Address
  18. Remark string
  19. }
  20. func main() {
  21. pa := &Address{"private", "Aartselaar", "Belgium"}
  22. wa := &Address{"work", "Boom", "Belgium"}
  23. vc := VCard{"Jan", "Kersschot", []*Address{pa, wa}, "none"}
  24. // fmt.Printf("%v: \n", vc) // {Jan Kersschot [0x126d2b80 0x126d2be0] none}:
  25. // JSON format:
  26. js, _ := json.Marshal(vc)
  27. fmt.Printf("JSON format: %s", js)
  28. // using an encoder:
  29. file, _ := os.OpenFile("vcard.json", os.O_CREATE|os.O_WRONLY, 0666)
  30. defer file.Close()
  31. enc := json.NewEncoder(file)
  32. err := enc.Encode(vc)
  33. if err != nil {
  34. log.Println("Error in encoding json")
  35. }
  36. }
  • 出于安全考虑,在 web 应用中最好使用 json.MarshalforHTML() 函数,其对数据执行 HTML 转码,所以文本可以被安全地嵌在 HTML <script> 标签中。
  • JSON 与 Go 类型对应如下:
    • bool 对应 JSON 的 boolean
    • float64 对应 JSON 的 number
    • string 对应 JSON 的 string
    • nil 对应 JSON 的 null=
  • 反序列化json.Unmarshal()
  • json 包使用 map[string]interface{}[]interface{} 储存任意的 JSON 对象和数组;其可以被反序列化为任何的 JSON blob 存储到接口值中

解码任意的数据:

json 包使用 map[string]interface{}[]interface{} 储存任意的 JSON 对象和数组;其可以被反序列化为任何的 JSON blob 存储到接口值中。

来看这个 JSON 数据,被存储在变量 b 中:

  1. b := []byte(`{"Name": "Wednesday", "Age": 6, "Parents": ["Gomez", "Morticia"]}`)

不用理解这个数据的结构,我们可以直接使用 Unmarshal 把这个数据编码并保存在接口值中:

  1. var f interface{}
  2. err := json.Unmarshal(b, &f)

f 指向的值是一个 map,key 是一个字符串,value 是自身存储作为空接口类型的值:

  1. map[string]interface{} {
  2. "Name": "Wednesday",
  3. "Age": 6,
  4. "Parents": []interface{} {
  5. "Gomez",
  6. "Morticia",
  7. },
  8. }

要访问这个数据,我们可以使用类型断言

  1. m := f.(map[string]interface{})

我们可以通过 for range 语法和 type switch 来访问其实际类型:

  1. for k, v := range m {
  2. switch vv := v.(type) {
  3. case string:
  4. fmt.Println(k, "is string", vv)
  5. case int:
  6. fmt.Println(k, "is int", vv)
  7. case []interface{}:
  8. fmt.Println(k, "is an array:")
  9. for i, u := range vv {
  10. fmt.Println(i, u)
  11. }
  12. default:
  13. fmt.Println(k, "is of a type I don’t know how to handle")
  14. }
  15. }

通过这种方式,你可以处理未知的 JSON 数据,同时可以确保类型安全。

编码和解码流

json 包提供 Decoder 和 Encoder 类型来支持常用 JSON 数据流读写。NewDecoder 和 NewEncoder 函数分别封装了 io.Reader 和 io.Writer 接口。

  1. func NewDecoder(r io.Reader) *Decoder
  2. func NewEncoder(w io.Writer) *Encoder

要想把 JSON 直接写入文件,可以使用 json.NewEncoder 初始化文件(或者任何实现 io.Writer 的类型),并调用 Encode();反过来与其对应的是使用 json.NewDecoder 和 Decode() 函数:

  1. func NewDecoder(r io.Reader) *Decoder
  2. func (dec *Decoder) Decode(v interface{}) error

XML

  • 如同 json 包一样,也有 xml.Marshal()xml.Unmarshal() 从 XML 中编码和解码数据
  • encoding/xml 包实现了一个简单的 XML 解析器(SAX),用来解析 XML 数据内容

示例:

  1. package main
  2. import (
  3. "encoding/xml"
  4. "fmt"
  5. "io/ioutil"
  6. )
  7. func main() {
  8. //读取xml文件到缓存中,返回[]byte
  9. XmlParam, err := ioutil.ReadFile("test.xml")
  10. if err != nil {
  11. fmt.Println("xml文件读取失败!")
  12. return
  13. }
  14. fmt.Println(string(XmlParam))
  15. //预期:<message><name>chen</name><age>26</age><sex>男</sex></message>
  16. /*
  17. 如果不想使用xml文件获取参数则模拟http发送的xml参数,如下:
  18. var XmlParam=`<message><name>chen</name><age>26</age><sex>男</sex></message>`
  19. */
  20. //将xmL映射到结构体中
  21. var Per Person
  22. err1:=xml.Unmarshal(XmlParam,&Per)
  23. if err1!=nil{
  24. fmt.Println("Unmarshal error")
  25. return
  26. }
  27. //获取其中一个标签的值
  28. fmt.Println(Per.Name) //预期:chen
  29. }
  30. //反序列化结构体定义
  31. type Person struct {
  32. Name string `xml:"name"` //注意这里有个反引号
  33. Age string `xml:"age"`
  34. Sex string `xml:"sex"`
  35. }
  36. /*
  37. 结构体中的反引号说明:
  38. `xml:"name"` 表示:将xml数据反序列化时,xml标签的数据name对应到结构体的Name成员中,需要
  39. 使用xml中的name标签值时,从Person中的Name成员中获取即可.
  40. */

GOB

  • Gob 是 Go 自己的以二进制形式序列化和反序列化程序数据的格式
    • 类似于 Python 的 “pickle” 和 Java 的 “Serialization”
  • 两个用 Go 写的服务之间的通信。这样的话服务可以被实现得更加高效和优化。