在Go语言当中,I/O的操作主要被封装在以下几个包中:
io
:为I/O提供基本的接口,在这个包中最为重要的是两个接口---Reader
和Writer
接口。
io/ioutil
:封装了一些比较实用的I/O函数。
fmt
:实现了格式化的I/O。
bufio
:实现带缓冲的I/O。
我们先来介绍io
这个包。
io包
我们刚才说到,io
包中最为重要的是两个接口,Reader
和Writer
接口,只要实现了这两个接口,它就有了I/O的功能。
Reader接口
Reader
接口的定义如下:
type Reader interface {
Read(p []byte) (n int, err error)
}
io.Reader
的规则是比较复杂的,我们来大致说明一下:
(1)Read方法最多读取
len(p)
字节的数据,并保存到p;
(2)Read方法返回读取的字节数以及任何发生的错误信息;
(3)读取的字节数n
要满足0<=n<=len(p)
;
(4)当读取的字节数不足以填满p
时,方法会立即返回,而不会等待更多的数据;
(5)读取的过程中遇到错误,会返回读取的字节数n
以及相应的错误err
;
(6)在底层数据流结束时,方法会返回n>0
字节,但是遇到错误可能会中断(EOF),也可以返回nil
;
(7)在第6中情况下,再次调用Read
方法的时候,肯定会返回(0,EOF);
(8)调用Read方法时,如果n>0
时,优先处理读入的数据,然后再返回错误err
,EOF也要这样处理;
(9)Read方法不鼓励返回n=0
或者err=nil
的情况。
我们用一个例子来谈谈这个接口的用法:
func ReadFrom(reader io.Reader, num int) ([]byte, error) {
p := make([]byte, num)
n, err := reader.Read(p)
if n > 0 {
return p[:n], nil
}
return p, err
}
该函数将io.Reader
作为参数,这样一来,ReadFrom
函数可以从任意地方读取数据,只要来源实现了io.Reader
接口:
// 从标准输入读取
data, err = ReadFrom(os.Stdin, 11)
// 从普通文件读取,其中file是os.File的实例
data, err = ReadFrom(file, 9)
// 从字符串中读取
data, err = ReadFrom(strings.NewReader("from string"), 12)
有一点需要注意,那就是io.EOF
变量的定义,var EOF = errors.New("EOF")
是error
类型。
Writer接口
定义如下:
type Writer interface {
Write(p []byte) (n int, err error)
}
规则:
(1)Write方法向底层数据流写入
len(p)
字节的数据,这些数据来自于切片p;
(2)返回被写入的字节数n
,其中0<=n<=len(p)
;
(3)如果n<len(p)
,则必须返回一些非nil
的err
;
(4)如果中途出现问题,也要返回非nil
的err
;
(5)Write方法绝对不能修改切片p
以及其中的数据。
在fmt
标准库中,有一组函数:Fprint/Fprintf/Fprintln
,他们都接收一个io.Writer
类型参数。比如我们最常用的fmt.Println
函数:
func Println(a ...interface{}) (n int, err error) {
return Fprintln(os.Stdout, a...)
}
很显然,fmt.Println
会将内容输出到标准输出中。
fmt:格式化的I/O
fmt
包实现了格式化的I/O函数,类似于C语言中的printf
和scanf
。
Print函数序列
在Go语言的fmt
中,其实有很多的输出函数类型,但大致的包括三类:Sprintf/Fprintf/Printf
函数通过指定的格式输出或格式化内容;Sprint/Fprint/Print
函数只是使用默认的格式输出或格式化内容;Sprintln/Fpirntln/Println
函数使用默认的格式输出或格式化内容,同时会在最后加上"换行符"。
我们看到最后一组函数中存在着一个ln
,它们之间会将两者内容直接通过一个空格连接起来:
result1 := fmt.Sprintln("example.com", 2019)
result2 := fmt.Sprint("example.com", 2019)
result1
的结果是example.com 2019
,而result2
的结果是example.com2019
.从而起到了连接字符串的作用。
Stringer接口
定义如下:
type Stringer interface {
String() string
}
我们通过一个例子来具体讲解一下:
我们做如下定义:
type Person struct {
Name string
Age int
Sex int
}
给Person
实现String
方法:
p := &Person{"Alice", 28, 0}
fmt.Println(p)
接下来,我们再为Person
增加String
方法:
func (this *Person) String() string {
buffer := bytes.NewBufferString("This is ")
buffer.WriteString(this.Name + ", ")
if this.Sex == 0 {
buffer.WriteString("he")
} else {
buffer.WriteString("she")
}
buffer.WriteString("is")
buffer.WriteString(strconv.Itoa(this.Age))
buffer.WriteString("years old.")
return buffer.String()
}
此时,我们再运行刚才那两行代码,得到的结果会是:
This is Alice, he is 28 years old
Formatter接口
定义:
type Formatter interface {
Format(f State, c rune)
}
Formatter
接口由带有定制的格式化符的值所实现,Format
的实现可调用Sprintf
或Fprintf(f)
等函数来生成其输出。
我们继续前面的那个例子:
func (this *Person) Format(f fmt.State, c rune) {
if c == 'L' {
f.Write([]byte(this.String())
f.Write([]byte("Person has three paraments.")
} else {
f.Write([]byte(fmt.Sprintln(this.String())))
}
}
这样就实现了Formatter
的接口。
在这里有几点需要说明:
1)Formatter
接口可以实现自定义占位符,同时fmt
包中和类型相对应的预定义占位符会无效。因此在例子中Format
的实现加上了else
子句。
2)实现了Foramtter
接口,相应的Stringer
接口就不起作用了,但实现了Formatter
接口的类型应该实现Stringer
接口,这样方便在Format
方法中调用String()
方法。就像上面这样。
3)Format
方法的第二个参数是占位符中%后的字母(有精度和宽度会被忽略,只保留字母)
GoStringer方法
定义:
type GoStringer interface {
GoString() string
}
该接口定义了类型的Go语言语法格式,用于打印格式化占位符为%#v
的值。
但该接口一般是不需要的。
Scan序列函数
和Print
序列函数一样,我们也将Scan
序列函数分为三类:
1)Scan/FScan/Sscan
var (name string age int) n, _ := fmt.Sscan("Alice 28", &name, &age)
fmt.Println(n, name, age)
这一组函数将连续由空格分隔的值存储为连续的实参。
2)Scanf/FScanf/Sscanf
var (name string age int) n, _ := fmt.Sscanf("Alice 28", "%s %d", &name, &age)
fmt.Println(n, name, age)
这组函数将连续由空格分隔的值存储为连续实参,其格式为format
决定,换行符处停止扫描。
3)Scanln/FScanln/Sscanln
var (name string age int) n, _ := fmt.Sscanln("Alice 28", &name, &age)
fmt.Println(n, name, age)
Scanln/FScanln/Sscanln
的表现和上一组是一样的,遇到\n
停止。
在一般情况下,我们使用最后这一句函数。
文本处理
Go原因标准库中有几个包专门用于处理文本。
strings
是一个字符串操作包,通常一般的字符串操作需求都可以在这个包中找到。
vbytes
:byte slice
便利操作,在Go语言中string
是内置类型。同时它与普通的slice
类型有着相似的性质。
strconv
:在Go语言中是没有隐式转换的,需要用这个包的方法来实现转换。
regexp
:正则表达式,这个包提供了正则表达式的功能。
unicode
:Unicode
编码,UTF-8/16
编码。