首先,strings包里面的reader.go文件并没有write(有WriteTo),所以我们需要到别的地方去找这个接口的实现。
最简单的就是文件操作的代码里面了,因为涉及到文件必然有写的操作。
所以我们在os包的file.go文件里找到了它的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
func (f *File) Write(b []byte) (n int, err error) {
if err := f.checkValid("write"); err != nil {
return 0, err
}
n, e := f.write(b)
if n < 0 {
n = 0
}
if n != len(b) {
err = io.ErrShortWrite
}

epipecheck(f, e)

if e != nil {
err = &PathError{"write", f.name, e}
}
return n, err
}

接下来,我们写一段例子来实现它。

1
2
3
4
5
6
7
8
9
10
func main()  {
f, err := os.OpenFile("test.txt",os.O_RDWR|os.O_CREATE|os.O_APPEND,os.ModePerm)
if(err!=nil){
fmt.Println(err)
}
f.Write([]byte(string("hello mama miya")))
fmt.Println(f)
}
result:
创建了一个test.txt,并成功将hello mama miya写入

注意的是,在Write内部,还调用了writes这个私有方法。

1
2
3
4
5
6
7
8
9
10
// write writes len(b) bytes to the File.
// It returns the number of bytes written and an error, if any.
func (f *File) write(b []byte) (n int, err error) {
f.l.Lock()
defer f.l.Unlock()
if f.isConsole {
return f.writeConsole(b)
}
return fixCount(syscall.Write(f.fd, b))
}

虽然已经超出了io包的范畴,不过也可以简单得说下。
*File也是一个结构

1
2
3
4
5
6
7
8
9
10
11
12
13
type file struct {
fd syscall.Handle
name string
dirinfo *dirInfo // nil unless directory being read
l sync.Mutex // used to implement windows pread/pwrite

// only for console io
isConsole bool
lastbits []byte // first few bytes of the last incomplete rune in last write
readuint16 []uint16 // buffer to hold uint16s obtained with ReadConsole
readbyte []byte // buffer to hold decoding of readuint16 from utf16 to utf8
readbyteOffset int // readbyte[readOffset:] is yet to be consumed with file.Read
}

这是windows版的定义,linux版还不知道。这里的l是一个锁。
在写文件的时候,会上锁,写完,会解锁。

type Closer interface {}

这个真没什么好说的了,io读和写,必然涉及到关闭,所以Close也是必须的方法,写代码的时候千万不能漏。

type Seeker interface {}

继续看文件读写的方法。Seek同样在其中被实现了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Seek sets the offset for the next Read or Write on file to offset, interpreted
// according to whence: 0 means relative to the origin of the file, 1 means
// relative to the current offset, and 2 means relative to the end.
// It returns the new offset and an error, if any.
// The behavior of Seek on a file opened with O_APPEND is not specified.
func (f *File) Seek(offset int64, whence int) (ret int64, err error) {
if err := f.checkValid("seek"); err != nil {
return 0, err
}
r, e := f.seek(offset, whence)
if e == nil && f.dirinfo != nil && r != 0 {
e = syscall.EISDIR
}
if e != nil {
return 0, &PathError{"seek", f.name, e}
}
return r, nil
}

查看注释,Seek就设置下一次读或者写的偏移量。根据这个whence,0意味着相对于文件的起始,1意味着当前的偏移位置,2意味着相对于文件的末尾。他会返回新的偏移量和一个错误。

在io.go里面,针对whence,定义了一组常量,可以拿来使用。

1
2
3
4
5
6
// Seek whence values.
const (
SeekStart = 0 // seek relative to the origin of the file
SeekCurrent = 1 // seek relative to the current offset
SeekEnd = 2 // seek relative to the end
)

其实在file.go里面也有定义

1
2
3
4
5
6
// Deprecated: Use io.SeekStart, io.SeekCurrent, and io.SeekEnd.
const (
SEEK_SET int = 0 // seek relative to the origin of the file
SEEK_CUR int = 1 // seek relative to the current offset
SEEK_END int = 2 // seek relative to the end
)

不过看一下注释,已经被Deprecated掉了,所以不要再去使用了。

接着,我们来试一下怎么用吧。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func main()  {
f, err := os.OpenFile("test.txt",os.O_RDWR|os.O_CREATE|os.O_APPEND,os.ModePerm)
if(err!=nil){
fmt.Println(err)
}
//f.Write([]byte(string("hello mama miya")))
rs,err := f.Seek(2,io.SeekStart)
if(err!=nil){
//报错
}
defer f.Close()
fmt.Println(rs)
fd,err := ioutil.ReadAll(f)
fmt.Println(string(fd))
}
result:
2
llo mama miya

把代码改成

1
2
3
4
5
rs,err := f.Seek(2,io.SeekCurrent)

result:
2
llo mama miya

把代码改成

1
2
3
4
rs,err := f.Seek(2,io.SeekEnd)

result:
17

看上去SeekCurrent和SeekEnd的用法似乎不大,可能多还是用在写上面吧。