前言
本文主要介绍了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地址
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南