读写数据
读取用户的输入
从键盘和标准输入 os.Stdin
读取输入,最简单的办法是使用fmt
包提供的Scan
和Sscan
开头的函数。请看以下程序:
1 | // 从控制台读取输入: |
Scanln
扫描来自标准输入的文本,将空格分隔的值依次存放到后续的参数内,直到碰到换行。Scanf
与其类似,除了 Scanf
的第一个参数用作格式字符串,用来决定如何读取。Sscan
和以 Sscan
开头的函数则是从字符串读取,除此之外,与 Scanf
相同。如果这些函数读取到的结果与您预想的不同,您可以检查成功读入数据的个数和返回的错误。
文件读写
1 | package main |
变量 inputFile
是 *os.File
类型的。该类型是一个结构,表示一个打开文件的描述符(文件句柄)。然后,使用 os
包里的Open
函数来打开一个文件。该函数的参数是文件名,类型为 string
。在上面的程序中,我们以只读模式打开input.dat
文件。
如果文件不存在或者程序没有足够的权限打开这个文件,Open
函数会返回一个错误:inputFile, inputError = os.Open("input.dat")
。如果文件打开正常,我们就使用defer inputFile.Close()
语句确保在程序退出前关闭该文件。然后,我们使用 bufio.NewReader
来获得一个读取器变量。
通过使用 bufio
包提供的读取器(写入器也类似),如上面程序所示,我们可以很方便的操作相对高层的 string 对象,而避免了去操作比较底层的字节。
接着,我们在一个无限循环中使用ReadString('\n')
或 ReadBytes('\n')
将文件的内容逐行(行结束符 ‘\n’)读取出来。
一旦读取到文件末尾,变量readerError
的值将变成非空(事实上,常量io.EOF
的值是 true),我们就会执行 return
语句从而退出循环。
其他类似函数:
1) 将整个文件的内容读到一个字符串里:
如果您想这么做,可以使用 io/ioutil
包里的 ioutil.ReadFile()
方法,该方法第一个返回值的类型是 []byte
,里面存放读取到的内容,第二个返回值是错误,如果没有错误发生,第二个返回值为 nil
。类似的,函数 WriteFile()
可以将 []byte
的值写入文件。
1 | package main |
2) 带缓冲的读取
在很多情况下,文件的内容是不按行划分的,或者干脆就是一个二进制文件。在这种情况下,ReadString()
就无法使用了,我们可以使用 bufio.Reader
的Read()
,它只接收一个参数:1
2
3
4buf := make([]byte, 1024)
...
n, err := inputReader.Read(buf)
if (n == 0) { break}
变量 n
的值表示读取到的字节数.
3) 按列读取文件中的数据
如果数据是按列排列并用空格分隔的,你可以使用 fmt
包提供的以 FScan
开头的一系列函数来读取他们。请看以下程序,我们将 3 列的数据分别读入变量 v1
、v2
和 v3
内,然后分别把他们添加到切片的尾部。
1 | package main |
输出结果:1
2
3[ABC FUNC GO]
[40 56 45]
[150 280 356]
注意: path
包里包含一个子包叫 filepath
,这个子包提供了跨平台的函数,用于处理文件名和路径。例如 Base()
函数用于获得路径中的最后一个元素(不包含后面的分隔符):1
2import "path/filepath"
filename := filepath.Base(path)
1 | package main |
除了文件句柄,我们还需要 bufio
的 Writer
。我们以只写模式打开文件 output.dat
,如果文件不存在则自动创建:
1 | outputFile, outputError := os.OpenFile(“output.dat”, os.O_WRONLY|os.O_CREATE, 0666) |
可以看到,OpenFile
函数有三个参数:文件名、一个或多个标志(使用逻辑运算符“|”连接),使用的文件权限。
我们通常会用到以下标志:
os.O_RDONLY
:只读os.O_WRONLY
:只写os.O_CREATE
:创建:如果指定文件不存在,就创建该文件。os.O_TRUNC
:截断:如果指定文件已存在,就将该文件的长度截为0。
在读文件的时候,文件的权限是被忽略的,所以在使用 OpenFile
时传入的第三个参数可以用0。而在写文件时,不管是 Unix 还是 Windows,都需要使用 0666
。
然后,我们创建一个写入器(缓冲区)对象:
1 | outputWriter := bufio.NewWriter(outputFile) |
接着,使用一个 for
循环,将字符串写入缓冲区,写 10 次:outputWriter.WriteString(outputString)
缓冲区的内容紧接着被完全写入文件:outputWriter.Flush()
如果写入的东西很简单,我们可以使用 fmt.Fprintf(outputFile, “Some test data.\n”)
直接将内容写入文件。fmt
包里的 F
开头的Print
函数可以直接写入任何io.Writer
,包括文件
不使用 fmt.FPrintf 函数,使用其他函数如何写文件:1
2
3
4
5
6
7
8
9
10package main
import "os"
func main() {
os.Stdout.WriteString("hello, world\n")
f, _ := os.OpenFile("test", os.O_CREATE|os.O_WRONLY, 0)
defer f.Close()
f.WriteString("hello, world in a file\n")
}
使用 os.Stdout.WriteString(“hello, world\n”)
,我们可以输出到屏幕。
我们以只写模式创建或打开文件“test”,并且忽略了可能发生的错误:f, _ := os.OpenFile(“test”, os.O_CREATE|os.O_WRONLY, 0)
我们不使用缓冲区,直接将内容写入文件:f.WriteString( )
文件拷贝
1 | package main |
注意defer
的使用:当打开目标文件时发生了错误,那么 defer
仍然能够确保 src.Close()
执行。如果不这么做,文件会一直保持打开状态并占用资源。
从命令行读取参数
os
包中有一个 string
类型的切片变量 os.Args
,用来处理一些基本的命令行参数,它在程序启动后读取命令行输入的参数。
1 | package main |
我们在 IDE 或编辑器中直接运行这个程序输出:Good Morning Alice
flag包
flag 包有一个扩展功能用来解析命令行选项。但是通常被用来替换基本常量,例如,在某些情况下我们希望在命令行给常量一些不一样的值。
下面的程序模拟了 Unix 的 echo 功能:
1 | package main |
flag.Parse()
扫描参数列表(或者常量列表)并设置flag
, flag.Arg(i)
表示第i
个参数。Parse()
之后flag.Arg(i)
全部可用,flag.Arg(0)
就是第一个真实的 flag
,而不是像 os.Args(0)
放置程序的名字。
flag.Narg()
返回参数的数量。解析后 flag
或常量就可用了。flag.Bool()
定义了一个默认值是 false
的 flag
:当在命令行出现了第一个参数(这里是 “n”),flag
被设置成 true
(NewLine 是 bool 类型)。flag
被解引用到 `NewLine,所以当值是
true 时将添加一个
newline(”\n”)`。
flag.PrintDefaults()
打印 flag
的使用帮助信息,本例中打印的是:1
-n=false: print newline
flag.VisitAll(fn func(*Flag))
是另一个有用的功能:按照字典顺序遍历 flag,并且对每个标签调用 fn
用 buffer 读取文件
1 | package main |
JSON 数据格式
数据结构要在网络中传输或保存到文件,就必须对其编码和解码;目前存在很多编码格式:JSON,XML,gob,Google 缓冲协议等等。Go 语言支持所有这些编码格式
通过把数据转换成纯文本,使用命名的字段来标注,让其具有可读性。这样的数据格式可以通过网络传输,而且是与平台无关的,任何类型的应用都能够读取和输出,不与操作系统和编程语言的类型相关。
序列化是在内存中把数据转换成指定格式(data -> string),反之亦然(string -> data structure)
尽管 XML 被广泛的应用,但是 JSON 更加简洁、轻量(占用更少的内存、磁盘及网络带宽)和更好的可读性,这也使它越来越受欢迎。
1 | package main |
json.Marshal()
的函数签名是 func Marshal(v interface{}) ([]byte, error)
,下面是数据编码后的 JSON 文本(实际上是一个 []byte
):
1 | { |
出于安全考虑,在 web 应用中最好使用json.MarshalforHTML()
函数,其对数据执行HTML转码,所以文本可以被安全地嵌在 HTML <script>
标签中。
json.NewEncoder()
的函数签名是 func NewEncoder(w io.Writer) *Encoder
,返回的Encoder
类型的指针可调用方法 Encode(v interface{})
,将数据对象v
的json编码写入 io.Writer
w 中。
不是所有的数据都可以编码为 JSON 类型:只有验证通过的数据结构才能被编码:
JSON 对象只支持字符串类型的
key
;要编码一个 Go map 类型,map
必须是map[string]T
(T
是 json 包中支持的任何类型)Channel,复杂类型和函数类型不能被编码
不支持循环数据结构;它将引起序列化进入一个无限循环
指针可以被编码,实际上是对指针指向的值进行编码(或者指针是 nil)
反序列化:
UnMarshal()
的函数签名是func Unmarshal(data []byte, v interface{}) error
把 JSON 解码为数据结构。
解码任意的数据:
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{} |
f
指向的值是一个map
,key 是一个字符串,value 是自身存储作为空接口类型的值:
1 | map[string]interface{} { |
要访问这个数据,我们可以使用类型断言
1 | m := f.(map[string]interface{}) |