数据读写
使用数据读写离不开io包,例如io包提供了两个重要的接口定义如下:
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}
主要介绍io接口的实现者和调用者。
常见的io接口实现者例如:
- os.File
- strings.Reader/Writer
- bufio.Reader/Writer
常见的io接口调用者例如:
- fmt.Fscan/Fprint
- bufio读写
fmt包
io接口调用者
函数命名格式
函数类型:
- scan 读取
- print 输出
开头输入输出流格式:
- 无 标准输入输出流
- S 字符串
- F 指定输入或输出方式
结尾处理格式:
- 无 直接输入输出
- f 自定义格式化
- ln 结尾输出换行或以换行为结尾符输入
以上排列组合18个函数,命名方式:开头输入格式+函数类型+结尾格式
比如以字符串为输入流,自定义格式化的读取函数:S+scan+f → fmt.Sscanf
参数
(输入输出方式(可选,实现io.Writer或io.Reader接口),格式化(可选),目标变量…)
例如func Fscanf(r io.Reader, format string, a ...interface{})
scan的a要传地址print的a传值
返回值
n int, err error
scan的n代表成功载入了多少个目标变量。
print的n代表成功输出了多少字节。
err输出错误
示例
获取控制台输入
var a int
var s string
for {
_, err := fmt.Scan(&a, &s)
if err != nil {
fmt.Println("输入错误")
return
}
fmt.Printf("整数:%d,字符串:%s\n", a, s)
}
读文件写文件(为了方便看没有写错误处理,但实际开发不要忽视错误)
var s, x string
fmt.Scan(&s) //从控制台读取字符串
file, _ := os.Create("a.txt") //创建文件
defer file.Close() //延迟关闭文件
fmt.Fprint(file, s) //写入文件
file.Seek(0, 0) //设置文件指针为文件起始位置
fmt.Fscan(file, &x) //读文件
fmt.Println(x) //输出文件内容
OS包
io接口实现者
打开文件
func OpenFile(*name* string, *flag* int, *perm* FileMode) (*File, error)
参数:
- name:文件路径
- flag:位掩码参数,指定文件访问模式
O_RDONLY
只读O_WRONLY
只写O_RDWR
读写O_APPEND
写操作时将数据附加到文件尾部O_CREATE
如果文件不存在创建新文件O_EXCL
文件必须不存在,配合上一条使用O_SYNC
打开文件用于同步IOO_TRUNC
打开时清空文件
- perm:模式和权限位置,不同的操作系统能生效的位不一样,模式位置一般不需要设置,后9位是权限位,每3位一组,分别是文件所有者、文件所属组、其他用户的读、写、运行权限。例如八进制777代表所有用户都有该文件的全部权限。
返回:
- *os.File:文件对象
- error:错误
例如:
打开相对路径文件a.txt,读写都可以,如果不存在则创建该文件并且设置所有人都有读写权限。
file, err := os.OpenFile("a.txt", os.O_RDWR|os.O_CREATE, 0666)
os包里还提供了两个常用封装函数:
func Open(name string) (*File, error) {
return OpenFile(name, O_RDONLY, 0)
}
func Create(name string) (*File, error) {
return OpenFile(name, O_RDWR|O_CREATE|O_TRUNC, 0666)
}
关闭文件
Close()
函数关闭一个打开的文件描述符,如果反复打开文件且不关闭会造成资源泄露。
一般写法如下:
file, err := os.Open("a.txt")
if err != nil {
//错误处理...
}
defer file.Close()//延迟启动关闭文件
//文件处理...
Close()
函数有一个返回值err,关闭未打开的文件或重复关闭文件会报错,一般不需要检查这个错误。
文件读取
func (*f* *File) Read(*b* []byte) (*n* int, *err* error)
从文件里文件指针处(文件默认读取和写入的位置)向后读取最多len(b)字节数据并写入b。
返回读取字节数和错误。
文件终止标志是读取0个字节并且返回err为io.EOF。
使用此方法成功会让文件指针向后移动n。
func (*f* *File) ReadAt(*b* []byte, *off* int64) (*n* int, *err* error)
ReadAt和Read功能一样,但会从off位置开始读取,而不是文件指针处,并且读取后不会改变文件指针。
雷区注意:
假如用一个长度1024的byte切片b去读一个100字节的文件file,文件指针在文件开头。
使用*file*.Read(*b*)
返回100,nil
使用*file*.ReadAt(*b*, 0)
返回100,EOF
在循环读取文件的时候可以用n==0判断是否结束循环,而不是err。这样在使用ReadAt的时候就不会漏读。
示例:
file, err := os.Open("a.txt")
if err != nil {
fmt.Println("打开文件错误:", err)
return
}
defer file.Close()
b := make([]byte, 1024)
off := int64(0)
for {
n, _ := file.Read(b)
if n == 0 {
break
}
fmt.Println(string(b[:n]))
}
for {
n, _ := file.ReadAt(b, off)
off += int64(n)
if n == 0 {
break
}
fmt.Println(string(b[:n]))
}
文件写入
func (*f* *File) Write(*b* []byte) (*n* int, *err* error)
从文件指针处向后写入b,在写入区域内的旧数据会被覆盖。
如果打开文件的时候使用了*O_APPEND
*那么会先把文件指针设定到文件结尾处再写入。
Write调用成功不代表此时磁盘里的数据已经被写入,只代表写入内核缓存完成了。如果想要立刻写入磁盘则需要在打开文件的时候指定*O_SYNC
,在写入后调用file*.Sync()
方法。但这样会消耗性能,一般不会这么做。
返回写入字节数和可能的任何错误。
func (*f* *File) WriteAt(*b* []byte, *off* int64) (*n* int, *err* error)
功能和Write一样,指定读取位置,不改变文件指针。
func (*f* *File) WriteString(*s* string) (*n* int, *err* error)
功能和Write一样,但参数是string
示例:
file, err := os.OpenFile("a.txt", os.O_RDWR, 0)
if err != nil {
fmt.Println("打开文件错误:", err)
return
}
defer file.Close()
_, err = file.Write([]byte("aaa"))
if err != nil {
fmt.Println("写入文件错误:", err)
return
}
改变文件指针位置
func (*f* *File) Seek(*offset* int64, *whence* int) (*ret* int64, *err* error)
offset:相对偏移量,正数往后,负数往前。
whence:相对位置,0为相对文件开头,1为当前位置,2为相对文件结尾。
相对位置应使用io.*SeekStart
* io.*SeekCurrent
* io.*SeekEnd*
书写。
bufio包
io接口调用者以及实现者。
频繁对少量数据读写会占用io影响性能,使用bufio可以利用缓存一次性大块数据读写以减少io调用。
读数据:
一次性从原读者读满缓存,之后调用读的时候从缓存取,缓存读完了则会再次从原读者读满缓存。如果一次性读的数据大小超过缓存的大小则不走缓存直接从原读者读。
写数据:
写的数据会先留在缓冲区,直到写的数据超过缓存大小,则一次性把缓存数据写给原写者。如果一次性写的数据大小超过缓存大小则直接写给原写者。
下面给出了一个我自定义的io接口实现者模拟底层io读写,并且每次触发读写的时候会打印信息。
type myIO struct {
data []byte
ptr int64
}
func newMyIO() myIO {
return myIO{
data: make([]byte, 0),
ptr: 0,
}
}
func (m *myIO) Seek(offset int64, whence int) (int64, error) {
switch whence {
case 0:
m.ptr = offset
case 1:
m.ptr += offset
case 2:
m.ptr = int64(len(m.data)) + offset
}
return m.ptr, nil
}
func (m *myIO) Write(data []byte) (n int, err error) {
tmpSlice := append([]byte{}, m.data[m.ptr:]...)
m.data = append(append(m.data[:m.ptr], data...), tmpSlice...)
m.ptr += int64(len(data))
fmt.Println("write:", string(data))
return len(data), nil
}
func (m *myIO) Read(data []byte) (n int, err error) {
n = 0
for i := 0; i < len(data) && m.ptr < int64(len(m.data)); i++ {
data[i] = m.data[m.ptr]
m.ptr++
n++
}
fmt.Println("read:", string(data))
return n, nil
}
func (m *myIO) string() string {
fmt.Println("show:", string(m.data))
return string(m.data)
}
写操作
声明Writer
myio := newMyIO()
writer1 := bufio.NewWriterSize(&myio, 20)//指定缓存大小20
writer2 := bufio.NewReader(&myio)//默认缓存大小4096
写入数据
writer.Write([]byte("abc"))//写入byte切片
writer.WriteString("ABC")//写入字符串
writer.WriteByte('T')//写入单个byte
writer.WriteRune('啊')//写入单个rune
缓存效果:
myio := newMyIO()
writer := bufio.NewWriterSize(&myio, 20)
writer.WriteString("111")
writer.WriteString("222")
writer.WriteString("333")
writer.WriteString("444")
writer.WriteString("555")
writer.WriteString("666")
writer.WriteString("777")//write: 11122233344455566677
writer.WriteString("888")
writer.WriteString("999")
myio.string()//show: 11122233344455566677
writer.Flush()//write: 7888999
myio.string()//show: 111222333444555666777888999
执行上述代码,输出:
write: 11122233344455566677
show: 11122233344455566677
write: 7888999
show: 111222333444555666777888999
注释标记了每行执行后的输出。
指定了缓存大小为20,在写“777”的时候超过了缓存大小,这时一次性写入。之后在缓存里剩下的可以调用Flush手动把缓存里的数据写入。
读操作
声明Reader
myio := newMyIO()
reader1 := bufio.NewReaderSize(&myio, 20)
reader2 := bufio.NewReader(&myio)
读出数据
buf = make([]byte, 5)
n, err := reader.Read(buf)//读字符切片长度的数据到字符切片,返回长度和错误
line, isPrefix, err := reader.ReadLine()//读出一行数据,如果读完没有碰到换行符则isPrefix为true
peek, err := reader.Peek(4)//读前n个字符切片数据但不移动指针
readString, err := reader.ReadString('a')//读出指定字符前(包括此字符)的字符串
readByte, err := reader.ReadByte()//读取一个字符
bytes, err := reader.ReadBytes('a')//读出指定字符前(包括此字符)的字符切片
line, err := reader.ReadSlice('a')//读出指定字符前(包括此字符)的字符切片,但此切片为内部缓冲区切片,下次读取时就会失效
r, size, err := reader.ReadRune()//读出一个Rune字符,而且返回Rune字符长度
缓存效果:
myio := newMyIO()
myio.Write([]byte("1234567890123456789012345678901234567890"))//write: 1234567890123456789012345678901234567890
myio.Seek(0, 0)
reader := bufio.NewReaderSize(&myio, 16)
buf := make([]byte, 5)
reader.Read(buf)//read: 1234567890123456
fmt.Println(string(buf))//12345
buf = make([]byte, 5)
reader.Read(buf)
fmt.Println(string(buf))//67890
buf = make([]byte, 5)
reader.Read(buf)
fmt.Println(string(buf))//12345
buf = make([]byte, 5)
reader.Read(buf)
fmt.Println(string(buf))//6
buf = make([]byte, 5)
reader.Read(buf)//read: 7890123456789012
fmt.Println(string(buf))//78901
输出:
write: 1234567890123456789012345678901234567890
read: 1234567890123456
12345
67890
12345
6
read: 7890123456789012
78901
读的时候先一次性读满缓存,然后再次读时候直接从缓存取。
声明Scanner
scanner := bufio.NewScanner(reader)
scanner是一个带有缓存的数据扫描器。方便分行读数据。
指定分割符(默认是换行符):
//指定逗号为分隔符
scanner.Split(func(data []byte, atEOF bool) (advance int, token []byte, err error) {
if i := strings.Index(string(data), ","); i >= 0 {
return i + 1, data[:i], nil
}
if atEOF && len(data) > 0 {
return len(data), data, nil
}
return 0, nil, nil
})
//下面是库里提供的几个分隔方法
scanner.Split(bufio.ScanWords)//空格分隔
scanner.Split(bufio.ScanLines)//换行分隔
scanner.Split(bufio.ScanBytes)//每个字节分隔
scanner.Split(bufio.ScanRunes)//每个Rune字符分隔
使用:
//这是一个套壳strings.Reader
//在调用Read的同时会打印read信息
type myStrReader struct {
s *strings.Reader
}
func (m myStrReader) Read(p []byte) (n int, err error) {
read, err := m.s.Read(p)
fmt.Println("Read:", string(p))
return read, err
}
func main() {
reader := myStrReader{s: strings.NewReader("1234 567 8901 234567")}
scanner := bufio.NewScanner(reader)
scanner.Split(bufio.ScanWords)
for scanner.Scan() {
fmt.Println(scanner.Text())
}
}
以上代码输出:
Read: 1234 567 8901 234567
1234
567
8901
Read:
234567
scanner.Scan():调用后如果没有缓存则会读取全部数据到缓存(缓存大小4096,无法指定大小),然后根据分隔方法获取扫描一次的数据保存到text里,成功扫描到了新数据则会返回true。如果此次扫描数据是缓存里最后一组数据则会触发一次读,发现是读到结尾了则下一次调用会直接返回false。
scanner.Text():返回text内容。
示例
读文件并写入到新文件
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
// 打开输入文件
inputFile, err := os.Open("input.txt")
if err != nil {
fmt.Println("无法打开输入文件:", err)
return
}
defer inputFile.Close()
// 创建一个扫描器
scanner := bufio.NewScanner(inputFile)
// 打开输出文件
outputFile, err := os.Create("output.txt")
if err != nil {
fmt.Println("无法创建输出文件:", err)
return
}
defer outputFile.Close()
// 创建一个写入器
writer := bufio.NewWriter(outputFile)
// 逐行读取输入文件,并写入输出文件
for scanner.Scan() {
line := scanner.Text()
// 在这里你可以对每一行进行处理
// 这里简单地写入到输出文件
_, err := writer.WriteString(line + "\n")
if err != nil {
fmt.Println("写入文件时发生错误:", err)
return
}
}
// 检查扫描过程中是否发生错误
if err := scanner.Err(); err != nil {
fmt.Println("扫描文件时发生错误:", err)
return
}
// 刷新写入器缓冲区,确保所有数据都写入文件
err = writer.Flush()
if err != nil {
fmt.Println("刷新写入器缓冲区时发生错误:", err)
return
}
fmt.Println("文件处理完成.")
}
从控制台读取指令
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
// 创建一个带有标准输入的Scanner
scanner := bufio.NewScanner(os.Stdin)
fmt.Print("请输入指令: ")
// 循环读取每一行输入
for scanner.Scan() {
// 获取用户输入的文本
command := scanner.Text()
// 在这里处理用户输入的指令
fmt.Println("你输入的指令是:", command)
// 再次提示用户输入
fmt.Print("请输入指令: ")
}
// 检查扫描过程中是否发生错误
if err := scanner.Err(); err != nil {
fmt.Println("读取输入时发生错误:", err)
}
}