当前位置: 首页 > news >正文

【Go】Go文件操作详解

1. 前言

相信如果看过之前文章的朋友们一定知道我想讲什么了?灵魂三问:文件是什么?为什么需要文件?文件怎么操作?前面章节我们已经能够编写各种各样的功能代码了,但是一个很现实的问题就是我们没有任何 持久化存储 的能力!也就是说我们之前编写的任何代码中的数据都保存在内存中(内存具有掉电易失性),举一个例子:我们想开发一款魔塔游戏,要求拥有存档和读档的能力!我们就可以使用文件的读写操作来实现持久化功能

2. 编码小历史

由于文件操作涉及到编码规则以及字符集的影响,因此我将先介绍有关编码的历史发展脉络,编码集的发展阶段大致可以总结如下:ASCII -> GBK -> Unicode -> UTF-8

  • ASCII编码

众所周知,计算机起源于美国,对于老美来说只有26个英文字母,算上各种特殊符号也绝不会超过128个,因此表示一个字符使用7个比特位即可(2^7 = 128)因此使用1个字节来存储一个字符是绰绰有余的,因此诞生了赫赫有名的 “ASCII” 编码字符集

  • GBK编码

但是当计算机发展到东亚国家以后就不同了,中文、韩文、日文的字符也想在计算机中显示怎么办?哪怕是常见的中文也有几千个(1个字节肯定是存不下了)因此引入了扩展的中文字符集——GBK编码,使用2个字节表示一个字符,同时还必须保证兼容ASCII编码集。两个字节足以表示65536个字符,应对当前场景应该是没问题了

  • Unicode编码

随着计算机不断在世界范围内普及,每个国家都进行扩展,造成的后果就是很难统一管理维护。于是ISO国际化标准组织收集了世界范围内的字符,编写了"万国码"——Unicode编码,该种编码方式对于任何一个字符都使用两个字节进行存储:表示范围为65536个字符

  • UTF-8编码

虽然Unicode编码已经可以达到统一的效果了,但是老美不乐意了:原本只需要使用一个字节表示的a、b、c现在全部需要扩大两倍存储空间,这不闹吗???最后诞生了"UTF-8"字符集(可以与unicode相互转化),最终的效果就是对于ASCII码表字符使用1个字节存储,对于中文统一使用3个字节存储,其余文字字符各有对应规则

3. Go的字符和字节

在我看来字符就是字节比特流(一串二进制数),两者是等价的:比如 ‘a’ <=> 97,在对应的UTF-8规则集下两者具有对应关系,在Go语言当中字符类型可以有如下两种声明方式:

  1. 语法格式:var ch byte 或者 var ch uint8表示该字符占用1个字节
  2. 语法格式:var ch rune 或者 var ch int32表示该字符占用4个字节

使用1个字节表示字符:

func main() {// 声明方式1var ch1 bytech1 = 'a'fmt.Println(ch1)var ch2 uint8ch2 = 'b'fmt.Println(ch2)
}

使用4个字节表示字符:

func main() {// 声明方式2var ch3 runech3 = '米'fmt.Println(ch3)var ch4 int32ch4 = '饭'fmt.Println(ch4)
}

💡 注意:在Go语言源码层面byte类型与uint8类型是一样的,rune类型与int32类型是一样的(如果追踪源码会发现如下语句:type byte = uint8; type rune = int32)

4. 字符串

4.1 字符串存储原理

Go语言当中的string数据类型占用16个字节,其中前8个字节是一个指针,存储底层字节数组的起始地址;后8个字节是一个整数,存储字符串的长度(以字节为单位)

画板

string对应数据结构实现可以在源码包src/runtime/string.go中看到:

  • str:指向底层字节数组的地址
  • len:底层字节数组长度,可通过len内置函数获取

4.2 字符串的遍历

字符串遍历有如下两种方式:

  1. 使用len函数按照长度进行遍历
func main() {// 字符串遍历方式1:按照len遍历var str = "米饭666"for i := 0; i < len(str); i++ {fmt.Printf("%d, %c\n", str[i], str[i])}
}

代码运行结果:

  1. 使用range迭代遍历
func main() {// 字符串遍历方式2:按照range遍历for _, v := range str {fmt.Printf("%d, %c\n", v, v)}
}

代码运行结果:

💡 总结:Go语言当中的字符串使用len遍历时,获取的是每一个字节的内容(若包含中文则会出现乱码结果);当使用range进行遍历时,内部会帮助解析成unicode字符

4.3 字符串和字节串的转化

由于字符串的底层是一个字节数组,因此字符串和字节串存在相互转化的关系:

  • 字符串转字节串:字节数组 = []byte(字符串)
func main() {// 字符串转字节串var str = "米饭666"var byteArr = []byte(str)fmt.Println(byteArr)
}

代码运行结果:

  • 字节串转字符串:字符串 = string(字节数组)
func main() {// 字节串转字符串var byteArr = []byte{231, 177, 179, 233, 165, 173, 54, 54, 54}var str = string(byteArr)fmt.Println(str)
}

代码运行结果:

5. 读写文件

5.1 读文件

首先我们在当前main.go同级创建文件file.txt内容如下:

我们可以通过file, err := os.Open(file_path)打开一个文件,如果打开成功则返回对应的文件句柄

5.1.1 方式1:使用Read方法

语法格式:n, err := file.Read(空字节切片)

  • 如果读取失败则会将错误内容写入err中
  • 如果读取成功那么n就是读取的字节数,读取内容会存放到空字节切片中
func main() {// 打开文件file, err := os.Open("./file.txt")if err != nil {fmt.Println("打开文件失败, err:", err)}// 关闭文件句柄defer file.Close()var data = make([]byte, 1024)n, err := file.Read(data)if err != nil {fmt.Println("读取文件失败. err:", err)}fmt.Printf("读取到的字节数为:%d\n", n)fmt.Printf("读取内容为:%s\n", string(data[:n]))
}

代码运行结果:

5.1.2 方式2:使用ReadString方法

上述方式是按照字节进行读取的,我们可以使用内置包bufio.NewReader().ReadString按照指定分隔符进行读取(比如换行符\n读取一行)

func main() {// 打开文件file, err := os.Open("./file.txt")if err != nil {fmt.Println("打开文件失败!", err)}defer file.Close()// 读取文件reader := bufio.NewReader(file)for true {line, err := reader.ReadString('\n')if err == io.EOF {// 读取到结束fmt.Print(line)break}fmt.Print(line)}
}

代码运行结果:

5.1.3 方式3:使用ReadFile方法

我们可以使用ioutil.ReadFile(file_path)方法直接读取整个文件内容,适用于小型文件

func main() {// 读取文件content, err := ioutil.ReadFile("./file.txt")if err != nil {fmt.Println("读取文件失败!", err)}fmt.Println(string(content))
}

代码运行结果:

5.2 写文件

我们可以通过file, err := os.OpenFile(file_path, flag, fileMode)更一般性的打开一个文件

  • file_path:文件所在路径
  • flag:打开方式标记,比如os.O_CREATE:文件不存在则新建,os.O_REONLY:只读模式,os.O_APPEND:追加写入模式
  • fileMode:文件权限:例如0777表示 rwxrwxrwx
5.2.1 方式1:使用Write方法
func main() {// 一般性的打开文件file, err := os.OpenFile("./file.txt", os.O_CREATE|os.O_WRONLY, 0666)if err != nil {fmt.Println("打开文件失败!", err)}defer file.Close()// Write写入字节数组file.Write([]byte("Hello World\nHello ricejson\n"))
}
5.2.2 方式2:使用WriteString方法
func main() {// 一般性的打开文件file, err := os.OpenFile("./file.txt", os.O_CREATE|os.O_WRONLY, 0666)if err != nil {fmt.Println("打开文件失败!", err)}defer file.Close()// WriteString写入字符串writer := bufio.NewWriter(file)writer.WriteString("Hello world\n Hello ricejson\n")// 强制刷新缓冲区writer.Flush()
}
5.2.3 方式3:使用WriteFile方法
func main() {// WriteFile直接写文件ioutil.WriteFile("file.txt", []byte("Hello World\n"), 0666)
}
http://www.lryc.cn/news/515920.html

相关文章:

  • [react+ts] useRef获取自定义组件dom或方法声明
  • AI 将在今年获得“永久记忆”,2028美国会耗尽能源储备
  • 【视频笔记】基于PyTorch从零构建多模态(视觉)大模型 by Umar Jamil【持续更新】
  • 解决 C++ 中头文件相互引用和解耦问题
  • 河马剧场(短剧)APP的邀请码怎么填写
  • 01:C语言的本质
  • 第1章:数据库基础
  • C++教程 | string类的定义和初始化方法
  • React中的合成事件
  • [SMARTFORMS] 创建FORM
  • 成都和力九垠科技有限公司九垠赢系统Common存在任意文件上传漏洞
  • 基于Python的考研学习系统
  • 『SQLite』几种向表中插入数据的方法
  • 什么是Kafka的重平衡机制?
  • pdf预览 报:Failed to load module script
  • AI 角色扮演法的深度剖析与实践
  • weblogic问题
  • Qt仿音乐播放器:客户端唯一化
  • ceph文件系统
  • 【数据结构-堆】力扣2530. 执行 K 次操作后的最大分数
  • Java jdk8新特性:Stream 流
  • 房产销售系统(源码+数据库+文档)
  • Vue 项目自动化部署:Coding + Jenkins + Nginx 实践分享
  • 从零开始开发纯血鸿蒙应用之实现起始页
  • CG顶会论文阅读|《科技论文写作》硕士课程报告
  • 【Python运维】使用Python与Docker进行高效的容器化应用管理
  • 【人工智能】基于Python与OpenCV构建简单车道检测算法:自动驾驶技术的入门与实践
  • 实时数仓: Hudi 表管理、Flink 性能调优或治理工具脚本
  • Kotlin 数据类与密封类
  • 大模型推理加速调研(框架、方法)