前言
本文主要介绍了Go语言中文件读写的相关操作。
文件是什么?
计算机中的文件是存储在外部介质(通常是磁盘)上的数据集合,文件分为文本文件和二进制文件。
Go中所有跟文件相关操作都使用OS这个模块来实现。
打开关闭文件
os.Open(path)
函数能够打开一个文件返回1个文件句柄,从而实现文件读取相关功能。
返回一个*File(文件类型的指针)
和一个err(错误)
。
对得到的文件实例调用close()
方法对文件进行关闭。
//打开和关闭文件 func main() { file,err:=os.Open("./test.txt") if err!=nil{ fmt.Println("open file faild",err) } file.Close() }
文件读取
在golang中读取文件有3种方式
func (f *File) Read(b []byte) (n int, err error) {.....}
它接收一个字节切片,返回 读取的字节数、错误,读到文件末尾时会返回0
和io.EOF(end of file)
package main import ( "fmt" "io" "os" ) func main() { //1.打开文件 fileObj, err := os.Open("./main.go") if err != nil { fmt.Printf("Open file faild %v", err) return } //返回1个指针类型文件句柄&{0xc000074780} fmt.Println(fileObj) //最后不要忘记 关闭文件 defer fileObj.Close() //2.读取文件:每次读取长度控制在128个字节 var tmp [128]byte for { //每次读给Read传1个长度为tmp[:]的口袋,存每次读取的数据。 n, err := fileObj.Read(tmp[:]) //io.EOF if err == io.EOF { fmt.Println("文件读完了") return } //其他错误 if err != nil { fmt.Printf("Open file faild %v", err) } fmt.Printf("读了%d个字节\n", n) //把每次读完得128个字节强转为字符串,输出 fmt.Println(string(tmp[:n])) //每次读128个字节,最后1次读,肯定不是128所以意味着读完了!退出 if n < 128 { return } } }
乱码
Golang的字符串是由Utf-8编码之后的字节序列,所以不能修改,1个汉字占用3个字节、1个英文字母占用1个字节的存储空间。
go使用rune(1个int32类型的数字) 表示中文字符,使用byte(1个uint8类型的数字)表示英文。
我们从1个大文件中读取英文是没有问题的,如果读取汉字1次读取128个字节,有时候会出现把1个中文给拆分成2份,就会出现乱码。
2.bufio读取文件
bufio可以支持我们的程序从缓存中读取文件。bufio返回的文件句柄支持ReadString(‘\n’)以字符串的形式逐行读取。
package main import ( "bufio" "fmt" "io" "os" ) func main() { fileObj, err := os.Open("./main.go") if err != nil { fmt.Printf("Open file faild error:%s", err) return } defer fileObj.Close() reader := bufio.NewReader(fileObj) for { line, err := reader.ReadString('\n') //文件末尾 读完了 if err == io.EOF { fmt.Printf("Sorry the end of file error:%s", err) return } //读的过程中出错了 if err != nil { fmt.Printf("read file fild error:%s", err) return } fmt.Print(line) } }
bufio.NewReade( )既可以接收fileObj,也可以获取os.Stdin屏幕输入。
bufio.NewReader() 参数为接口类型
只要实现了Read()方法,都可以被传进去!
package main import ( "bufio" "fmt" "os" ) //获取用户输入时如果有空格 func userScan() { var s string fmt.Print("请输入内容:") fmt.Scanln(&s) fmt.Printf("你输入的是%s\n", s) } func useBufio() { var s1 string /* 使用bufio及可以接收 fileObj、也可以获取os.Stdin屏幕输入 说明:bufio.NewReader(参数)为接口类型 只要实现了Read()方法,都可以被传进去! */ reader := bufio.NewReader(os.Stdin) fmt.Print("请输入内容:") s1, _ = reader.ReadString('\n') fmt.Printf("你输入的内容:%s\n", s1) } func main() { // userScan() useBufio() }
3.ioutill打开文件
ioutill直接读取整个文件,不需要处理IO EOF错误。
package main import ( "fmt" "io/ioutil" ) func main() { ret, err := ioutil.ReadFile("./main.go") if err != nil { fmt.Printf("Read file faild,erro:%s", err) return } fmt.Println(string(ret)) }
Linux文件权限设计思想启示
package main import "fmt" /* linux文件系统使用二进制位 代表 1个权限,因为二进制运算和存储都很快。 分别使用3个位来表示以下3种权限 100 读 010 写 001 执行 */ /*Linux文件权限为什么使用 4、2、1代表 读、写、执行权限呢? 因为4、2、1这3个数字中任意2个十进制数字相加的的结果,正好=它们转换成2进制向与(|)的结果。 */ const ( read int = 4 //100 write int = 2 //010 execute int = 1 //001 //拥有读执行r+x权限 5 101 //拥有读写r+w权限 6 110 //拥有所有r+w+x权限 7 111 //又有写和执w+x权限 3 011 ) /*所以在Linux文件权限中: 7----->满权角色 5----->角色1 3----->角色2 ............ */ //Linux用户 就是 user func judgeRole(per int) { fmt.Printf("用户眼中的权限 10进制:%d linux眼中的权限:%b \n", per, per) } func main() { judgeRole(read | write) //6----110 judgeRole(read | write | execute) //7----111 }
根据Linux系统这种文件权限设计思想,也可以做自己web开发中RBAC权限。
文件写入操作
os.OpenFile()
函数能够以指定模式打开1个文件,返回1个文件句柄,从而实现文件写入相关功能。
func OpenFile(name string, flag int, perm FileMode) (*File, error) { ... }
name
:要打开的文件名 flag
:打开文件的模式。 模式有以下几种:
模式 | 含义 |
---|---|
os.O_WRONLY |
只写 |
os.O_CREATE |
创建文件 |
os.O_RDONLY |
只读 |
os.O_RDWR |
读写 |
os.O_TRUNC |
清空 |
os.O_APPEND |
追加 |
perm
:文件权限,一个八进制数。r(读)04,w(写)02,x(执行)01。
1.Write和WriteString
package main import ( "fmt" "os" ) func main() { //打开文件写内容 fileObj, err := os.OpenFile("./xx.txt", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 777) defer fileObj.Close() if err != nil { fmt.Printf("Open file faild %s", err) return } //Write fileObj.Write([]byte("Hello world.\n")) //WriteString fileObj.WriteString("您好世界。\n") }
2.bufio.NewWriter
bufio是先把文件内容写到缓冲区中,然后flush到硬盘。
package main import ( "bufio" "fmt" "os" ) func main() { //打开文件写内容 fileObj, err := os.OpenFile("./xx.txt", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 777) defer fileObj.Close() if err != nil { fmt.Printf("Open file faild %s", err) return } //bufio.NewWriter writer := bufio.NewWriter(fileObj) for i := 0; i < 10; i++ { writer.WriteString("Hello China\n") } writer.Flush() }
3.ioutil.WriteFile
package main import ( "fmt" "io/ioutil" ) func main() { str := "Los Angeles" err := ioutil.WriteFile("./xx.txt", []byte(str), 777) if err != nil { fmt.Println("Write file faild ", err) return } }
package main import ( "os" ) func main() { // var ret [10]byte fileObj, _ := os.OpenFile("./main.go", os.O_WRONLY|os.O_APPEND, 777) //seek:offset偏移多少:9 whence 从哪里偏移? fileObj.Seek(13, 1) // //插入内容 fileObj.Write([]byte{'/', '/'}) fileObj.WriteString("写点注释啊!\n") defer fileObj.Close() }
文件中插入内容
package main import ( "fmt" "io" "os" "strings" ) func newSeek(offset int64) int64 { fileObj, _ := os.Open("./main.go") //seek:offset偏移多少:9 whence 从哪里偏移? newSeek, _ := fileObj.Seek(offset, 1) // //插入内容 defer fileObj.Close() return newSeek } type metadata struct { name string start int end int step int appendFlag int } func appendFile(name string, content string) int64 { fileObj, _ := os.OpenFile(name, os.O_APPEND, 777) fileObj.WriteString(content) //获取当前 curOffset, _ := fileObj.Seek(0, os.SEEK_CUR) defer fileObj.Close() return curOffset } func restructFile(order metadata) string { // var ret string //中间文件的名称 s1 := strings.Split(order.name, "/") s1[len(s1)-1] = fmt.Sprintf("tmp_%s", s1[len(s1)-1]) tmpName := strings.Join(s1, "/") newFileObj, _ := os.OpenFile(tmpName, os.O_CREATE|os.O_WRONLY|order.appendFlag, 777) fileObj, err := os.Open(order.name) if err != nil { fmt.Println("打开文件时错误") } //没有指定结束边界就以文件最后位置 if order.end == -1 { fi, err := fileObj.Stat() if err != nil { // Could not obtain stat, handle error } order.end = int(fi.Size()) } //创建指定长度的数组 var bag = make([]byte, order.step, order.step) //从指定位置开始 fileObj.Seek(int64(order.start), 0) //计算需要获取的量 total := order.end - order.start //开始读文件 for i := 0; i < total; i += order.step { var currentPostion = order.start + i var left = order.end - currentPostion //最后1次不能被整除的情况 if left < order.step { bag = bag[:left+1] } bag = bag[:] n, err := fileObj.Read(bag) if err == io.EOF { fmt.Println("文件读完了") break } if err != nil { fmt.Printf("读取文件时错误%v", err) break } if n == 0 { break } newFileObj.WriteString(string(bag[:n])) // ret += string(bag[:n]) } defer newFileObj.Close() defer fileObj.Close() return tmpName } func main() { var struct1 = metadata{ name: "./test.txt", start: 0, //从文件的多少字节之后开始 end: 16, //读取到多少个字节后结束 step: 3, //一次读取多少字节 appendFlag: os.O_TRUNC, } tmpFile := restructFile(struct1) curOffset := appendFile(tmpFile, ` 吾令羲和弭节兮,望崦嵫而勿迫。 路漫漫其修远兮,吾将上下而求索。 `) struct1.start = int(curOffset) struct1.end = -1 struct1.appendFlag = os.O_APPEND restructFile(struct1) os.Remove(struct1.name) os.Rename("./tmp_test.txt", struct1.name) }
日志库
用户可以指定日志级别:debug、infro、error
支持用户格式化输出日志内容到stdout
支持用户格式化输出日志内容写入到文件,且可以指定日志文件切割时间。
github地址