go语言文件处理-01
go语言json文件的读写操作
JSON 是一种使用 UTF-8 编码的纯文本格式,采用完全独立于语言的文本格式,由于写起来比 XML 格式方便,并且更为紧凑,同时所需的处理时间也更少,致使 JSON 格式越来越流行,特别是在通过网络连接传送数据方面。
开发人员可以使用 JSON 传输简单的字符串、数字、布尔值,也可以传输一个数组或者一个更复杂的复合结构。在 Web 开发领域中,JSON 被广泛应用于 Web 服务端程序和客户端之间的数据通信。
Go语言内建对 JSON 的支持,使用内置的 encoding/json 标准库,开发人员可以轻松使用Go程序生成和解析 JSON 格式的数据。
写json文件
使用go语言创建一个json文件非常方便,示例代码如下:
type Website struct {
Name string `xml:"name,attr"`
Url string
Course []string
}
func main() {
info := []Website{
{"golang", "https://www.golang.com", []string{"go", "lang"}},
{"python", "https://www.python.com", []string{"py", "thon"}},
}
filePtr, err := os.Create("info.json")
defer func() {
if err = filePtr.Close(); err != nil {
fmt.Println("创建json文件失败", err.Error())
}
}()
if err != nil {
fmt.Println(err.Error())
return
}
// 创建json编码器
encoder := json.NewEncoder(filePtr)
err = encoder.Encode(info)
if err != nil {
fmt.Println("编码错误", err.Error())
}else {
fmt.Println("编码成功")
}
}
读取json文件
读取json数据与写json数据一样简单:
func main() {
// 读取json文件数据
filePtr, err := os.Open("./info.json")
if err != nil {
fmt.Println(err.Error())
return
}
defer func() {
if err = filePtr.Close(); err != nil {
fmt.Println(err.Error())
}
}()
var info []Website
// 创建json解码器
decoder := json.NewDecoder(filePtr)
if err = decoder.Decode(&info); err != nil {
fmt.Println(err.Error())
}else{
fmt.Println(info)
}
}
顺便提一下,还有一种叫做 BSON (Binary JSON) 的格式与 JSON 非常类似,与 JSON 相比,BSON 着眼于提高存储和扫描效率。BSON 文档中的大型元素以长度字段为前缀以便于扫描。在某些情况下,由于长度前缀和显式数组索引的存在,BSON 使用的空间会多于 JSON。
go语言xml文件的读写操作
XML(extensible Markup Language)格式被广泛用作一种数据交换格式,并且自成一种文件格式。与上一节介绍的 JSON 相比 XML 要复杂得多,而且手动写起来相对乏味得多。
在 JSON 还未像现在这么广泛使用时,XML 的使用相当广泛。XML 作为一种数据交换和信息传递的格式,使用还是很广泛的,现在很多开放平台接口,基本都会支持 XML 格式。
Go语言内置的 encoding/xml 包可以用在结构体和 XML 格式之间进行编解码,其方式跟 encoding/json 包类似。然而与 JSON 相比 XML 的编码和解码在功能上更苛刻得多,这是由于 encoding/xml 包要求结构体的字段包含格式合理的标签,而 JSON 格式却不需要。
写xml文件
使用encoding/xml包可以很方便的将xml数据存储到文件中:
type Website struct {
Name string `xml:"name,attr"`
Url string
Course []string
}
func main() {
info := Website{"golang", "https://www.golang.com", []string{"go", "lang"}}
// 创建xml文件
filePtr, err := os.Create("./info.xml")
if err != nil {
fmt.Println(err.Error())
return
}
defer func() {
if err = filePtr.Close(); err != nil {
fmt.Println(err)
}
}()
// 创建xml编码器
encoder := xml.NewEncoder(filePtr)
if err = encoder.Encode(info); err != nil {
fmt.Println("编码失败", err.Error())
}else{
fmt.Println("编码成功")
}
}
读取xml文件
读 XML 文件比写 XML 文件稍微复杂,特别是在必须处理一些我们自定义字段的时候(例如日期)。但是,如果我们使用合理的打上 XML 标签的结构体,就不会复杂。示例代码如下:
type Website struct {
Name string `xml:"name,attr"`
Url string
Course []string
}
func main() {
filePtr, err := os.Open("./info.xml")
if err != nil {
fmt.Println(err.Error())
return
}
defer func() {
if err = filePtr.Close(); err != nil {
fmt.Println(err.Error())
}
}()
var website Website
decoder := xml.NewDecoder(filePtr)
if err = decoder.Decode(&website); err != nil {
fmt.Println(err.Error())
}else{
fmt.Println(website)
}
}
正如写 XML 时一样,我们无需关心对所读取的 XML 数据进行转义,xml.NewDecoder.Decode() 函数会自动处理这些。
xml 包还支持更为复杂的标签,包括嵌套。 例如标签名为 'xml:"Books>Author"' 产生的是
go语言使用Gob传输数据
为了让某个数据结构能够在网络上传输或能够保存至文件,它必须被编码然后再解码。当然已经有许多可用的编码方式了,比如 JSON、XML、Google 的 protocol buffers 等等。而现在又多了一种,由Go语言 encoding/gob 包提供的方式。
Gob 是Go语言自己以二进制形式序列化和反序列化程序数据的格式,可以在 encoding 包中找到。这种格式的数据简称为 Gob(即 Go binary 的缩写)。类似于 Python 的“pickle”和 Java 的“Serialization”。
Gob 和 JSON 的 pack 之类的方法一样,由发送端使用 Encoder 对数据结构进行编码。在接收端收到消息之后,接收端使用 Decoder 将序列化的数据变化成本地变量。
Go语言可以通过 JSON 或 Gob 来序列化 struct 对象,虽然 JSON 的序列化更为通用,但利用 Gob 编码可以实现 JSON 所不能支持的 struct 的方法序列化,利用 Gob 包序列化 struct 保存到本地也十分简单。
Gob 不是可外部定义、语言无关的编码方式,它的首选的是二进制格式,而不是像 JSON 或 XML 那样的文本格式。Gob 并不是一种不同于 Go 的语言,而是在编码和解码过程中用到了 Go 的反射。
Gob通常用于远程方法调用参数和结果的传输,以及应用程序和机器之间的数据传输。它和 JSON 或 XML 有什么不同呢?Gob 特定的用于纯 Go 的环境中,例如两个用Go语言写的服务之间的通信。这样的话服务可以被实现得更加高效和优化。
Gob 文件或流是完全自描述的,它里面包含的所有类型都有一个对应的描述,并且都是可以用Go语言解码,而不需要了解文件的内容。
只有可导出的字段会被编码,零值会被忽略。在解码结构体的时候,只有同时匹配名称和可兼容类型的字段才会被解码。当源数据类型增加新字段后,Gob 解码客户端仍然可以以这种方式正常工作。解码客户端会继续识别以前存在的字段,并且还提供了很大的灵活性,比如在发送者看来,整数被编码成没有固定长度的可变长度,而忽略具体的 Go 类型。
加入有这样一个结构体T:
type T struct { X, Y, Z int }
var t = T{X: 7, Y: 0, Z: 8}
而在接收时可以用一个结构体U类型的变量u来接收这个值:
type U struct { X, Y *int8 }
var u U
接收时,X 的值是 7,Y 的值是 0(Y 的值并没有从 t 中传递过来,因为它是零值)和 JSON 的使用方式一样,Gob 使用通用的 io.Writer 接口,通过 NewEncoder() 函数创建 Encoder 对象并调用 Encode(),相反的过程使用通用的 io.Reader 接口,通过 NewDecoder() 函数创建 Decoder 对象并调用 Decode 。
创建gob文件
下面通过简单的示例程序来演示Go语言是如何创建 gob 文件的,代码如下所示:
func main() {
info := map[string]string{
"name": "golang",
"website": "https://www.golang.com",
}
filePtr, err := os.OpenFile("demo.gob", os.O_RDWR|os.O_CREATE, 0777)
if err != nil {
fmt.Println(err)
}
defer filePtr.Close()
// 创建gob编码器
encoder := gob.NewEncoder(filePtr)
if err = encoder.Encode(&info); err != nil {
fmt.Println(err.Error())
}
}
读取gob文件
读取gob文件与创建gob文件同样简单:
func main() {
filePtr, _ := os.OpenFile("./demo.gob", os.O_RDONLY, 0755)
defer filePtr.Close()
decoder := gob.NewDecoder(filePtr)
var m map[string]string
_ = decoder.Decode(&m)
fmt.Println(m, m["name"])
}
go语言纯文本文件的读写操作
Go语言提供了很多文件操作的支持,在不同场景下,有对应的处理方式,本节我们来介绍一下文本文件的读写操作。
写纯文本文件
由于go语言fmt包中打印函数强大而灵活。写纯文本数据非常简单而直接:
func main() {
filePtr, err := os.OpenFile("./out.txt", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
if err != nil {
fmt.Println(err)
return
}
defer filePtr.Close()
str := "hello world, 你好 世界\n"
// 写入时,使用带缓存的*Writer
writer := bufio.NewWriter(filePtr)
for i := 0; i < 3; i++ {
writer.WriteString(str)
}
// 因为writer是带缓存的,因此在调用WriteString方法时,内容是先写入缓存的
// 所以要调用Flush方法,将缓存的数据真正写入到文件中
writer.Flush()
}
读纯文本文件
打开并读取一个纯文本格式的数据跟写入纯文本格式数据一样简单。要解析文本来重建原始数据可能稍微复杂,这需根据格式的复杂性而定。
func main() {
file, err := os.Open("./out.txt")
if err != nil {
fmt.Println(err.Error())
return
}
defer file.Close()
// 创建一个带缓存的*Reader
reader := bufio.NewReader(file)
for {
str, err := reader.ReadString('\n')
if err == io.EOF {
break
}
fmt.Printf(str)
}
fmt.Println("文件读取结束")
}
go语言二进制文件的读写操作
Go语言的二进制(gob)格式是一个自描述的二进制序列。从其内部表示来看,Go语言的二进制格式由一个 0 块或者更多块的序列组成,其中的每一块都包含一个字节数,一个由 0 个或者多个 typeId-typeSpecification 对组成的序列,以及一个 typeId-value 对。
如果 typeId-value 对的 typeId 是预先定义好的(例如 bool、int 和 string 等),则这些 typeId-typeSpecification 对可以省略。否则就用类型对来描述一个自定义类型(如一个自定义的结构体)。类型对和值对之间的 typeId 没有区别。
正如我们将看到的,我们无需了解其内部结构就可以使用 gob 格式, 因为 encoding/gob 包会在幕后为我们打理好一切底层细节。
Go语言中的 encoding/gob 包也提供了与 encoding/json 包一样的编码解码功能,并且容易使用。通常而言如果对肉眼可读性不做要求,gob 格式是Go语言上用于文件存储和网络传输最为方便的格式。
写go语言二进制文件
下面通过一个简单的示例来演示一下Go语言是如何生成一个二进制文件的,代码如下所示:
func main() {
str := "这是一个二进制文件的内容 hello world 20220829"
// 以写入的方式打开文件
filePtr, err := os.OpenFile("bytes.gob", os.O_WRONLY|os.O_CREATE, 0644)
if err != nil {
fmt.Println(err)
return
}
defer filePtr.Close()
// 生成gob格式的编码器
encoder := gob.NewEncoder(filePtr)
// 将内容通过编码器写入到文件中
_ = encoder.Encode(str)
}
读go语言二进制文件
读gob数据和写一样简单,代码如下:
func main() {
filePtr, err := os.Open("./bytes.gob")
if err != nil {
fmt.Println(err)
return
}
defer filePtr.Close()
decoder := gob.NewDecoder(filePtr)
var ret string
if err = decoder.Decode(&ret); err != nil {
fmt.Println(err)
}else{
fmt.Println(ret)
}
}
go语言自定义二进制文件的读写操作
虽然Go语言的 encoding/gob 包非常易用,而且使用时所需代码量也非常少,但是我们仍有可能需要创建自定义的二进制格式。自定义的二进制格式有可能做到最紧凑的数据表示,并且读写速度可以非常快。
不过,在实际使用中,我们发现以Go语言二进制格式的读写通常比自定义格式要快非常多,而且创建的文件也不会大很多。但如果我们必须通过满足 gob.GobEncoder 和 gob.GobDecoder 接口来处理一些不可被 gob 编码的数据,这些优势就有可能会失去。
在有些情况下我们可能需要与一些使用自定义二进制格式的软件交互,因此了解如何处理二进制文件就非常有用。
写自定义二进制文件
Go语言的 encoding/binary 包中的 binary.Write() 函数使得以二进制格式写数据非常简单,函数原型如下:
func Write(w io.Writer, order ByteOrder, data interface{}) error
Write 函数可以将参数 data 的 binary 编码格式写入参数 w 中,参数 data 必须是定长值、定长值的切片、定长值的指针。参数 order 指定写入数据的字节序,写入结构体时,名字中有_的字段会置为 0。
下面通过一个简单的示例程序来演示一下 Write 函数的使用,示例代码如下:
type Website struct {
Url int32
}
func main() {
file, err := os.Create("byte.bin")
if err != nil {
fmt.Println(err)
return
}
defer file.Close()
for i := 0; i < 10; i++ {
info := Website{int32(i)}
var binBuffer bytes.Buffer
err = binary.Write(&binBuffer, binary.LittleEndian, &info)
if err != nil {
fmt.Println(err)
continue
}
byteSlice := binBuffer.Bytes()
_, _ = file.Write(byteSlice)
}
}
读自定义二进制文件
读取自定义的二进制数据与写自定义二进制数据一样简单。我们无需解析这类数据,只需使用与写数据时相同的字节顺序将数据读进相同类型的值中。
示例代码如下:
type Website struct {
Url int32
}
func main() {
file, err := os.Open("byte.bin")
if err != nil {
fmt.Println(err)
return
}
defer file.Close()
for i := 0; i < 10; i++ {
b := make([]byte, 4)
_, err = file.Read(b)
if err != nil {
fmt.Println(err)
continue
}
buffer := bytes.NewBuffer(b)
w := Website{}
err = binary.Read(buffer, binary.LittleEndian, &w)
if err != nil {
fmt.Println(err)
}
fmt.Printf("第 %d 个值为 %+v:\n", i, w)
}
}
至此,我们完成了对自定义二进制数据的读和写操作。只要小心选择表示长度的整数符号和大小,并将该长度值写在变长值(如切片)的内容之前,那么使用二进制数据进行工作并不难。
Go语言对二进制文件的支持还包括随机访问。这种情况下,我们必须使用 os.OpenFile() 函数来打开文件(而非 os.Open()),并给它传入合理的权限标志和模式(例如 os.O_RDWR 表示可读写)参数。
然后,就可以使用 os.File.Seek() 方法来在文件中定位并读写,或者使用 os.File.ReadAt() 和 os.File.WriteAt() 方法来从特定的字节偏移中读取或者写入数据。
Go语言还提供了其他常用的方法,包括 os.File.Stat() 方法,它返回的 os.FileInfo 包含了文件大小、权限以及日期时间等细节信息。