文件操作及序列化
一. 文件操作
1. 创建文件
- os.Create
func main() {
// 创建文件
file, err := os.Create("example.txt")
//os.Create 函数,在 Unix 或 Linux 系统上创建文件 默认权限是 0666
if err != nil {
panic(err)
}
defer file.Close()
fmt.Println("File created")
}
需要注意的是,如果文件已经存在,则 os.Create
函数会截断该文件并清空其内容。如果你想在文件已经存在时追加数据而不是清空其内容,可以使用 os.OpenFile
函数以追加模式打开文件。具体实现方式可以参考下面这个示例代码:
- os.OpenFile
func main() {
// 打开文件
file, err := os.OpenFile("example.txt", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0777) //只读写|创建|追加
if err != nil {
panic(err)
}
defer file.Close()
// 写入数据
data := []byte("hello, world!")
n, err := file.Write(data)
if err != nil {
panic(err)
}
fmt.Printf("Wrote %d bytes\n", n)
}
2. 打开文件
- os.Open / os.OpenFile
func main() {
// 打开文件
file, err := os.Open("example.txt")
if err != nil {
panic(err)
}
defer file.Close()
// 读取文件内容
buf := make([]byte, 1024)
n, err := file.Read(buf)
if err != nil {
panic(err)
}
fmt.Printf("%s", buf[:n])
}
- os.ReadFile / ioutil.ReadFile
// 使用os包的ReadFile函数读取文件内容
fileContent, err := os.ReadFile("example.txt")
if err != nil {
fmt.Println("Error reading file:", err)
return
}
fmt.Println(string(fileContent))
// 使用ioutil包的ReadFile函数读取文件内容
fileContent, err = ioutil.ReadFile("example.txt")
if err != nil {
fmt.Println("Error reading file:", err)
return
}
fmt.Println(string(fileContent))
3. 写入文件
- file.Write
func main() {
// 打开文件
file, err := os.Create("example.txt")
if err != nil {
panic(err)
}
defer file.Close()
// 写入数据
data := []byte("hello, world!")
n, err := file.Write(data)
if err != nil {
panic(err)
}
fmt.Printf("Wrote %d bytes\n", n)
}
4. 删除文件
func main() {
// 删除文件
err := os.Remove("example.txt")
if err != nil {
panic(err)
}
fmt.Println("File deleted")
}
5. 获取文件信息
func main() {
// 获取文件信息
fileInfo, err := os.Stat("example.txt")
if err != nil {
panic(err)
}
// 输出文件信息
fmt.Printf("Name: %s\n", fileInfo.Name())
fmt.Printf("Size: %d\n", fileInfo.Size())
fmt.Printf("Mode: %s\n", fileInfo.Mode()) //获取权限
fmt.Printf("ModTime: %s\n", fileInfo.ModTime()) //修改时间
fmt.Printf("IsDir: %t\n", fileInfo.IsDir()) //是否为目录
}
Name: example.txt
Size: 16
Mode: -rw-r--r--
ModTime: 2021-09-08 15:26:18.8577373 +0800 CST
IsDir: false
6. 复制文件
func main() {
// 打开源文件
src, err := os.Open("example.txt")
if err != nil {
panic(err)
}
defer src.Close()
// 创建目标文件
dst, err := os.Create("example_copy.txt")
if err != nil {
panic(err)
}
defer dst.Close()
// 复制文件内容
n, err := io.Copy(dst, src)
if err != nil {
panic(err)
}
fmt.Printf("Copied %d bytes\n", n)
}
7. 读取目录内容
func main() {
// 读取目录内容
files, err := ioutil.ReadDir(".") //建议使用os.ReadDir
if err != nil {
panic(err)
}
// 输出目录内容
for _, file := range files {
fmt.Println(file.Name())
}
}
8. 文件/目录重命名
err := os.Rename("oldfile.txt", "newfile.txt")
err := os.Rename("olddir", "newdir")
if err != nil {
fmt.Println("Error renaming file:", err)
return
}
构造sql
var sb strings.Builder
sb.WriteString(fmt.Sprintf("ALTER TABLE %s\n"),"xxx")
i := 0
for _, v := range params {
sb.WriteString(fmt.Sprintf("ADD COLUMN `%s` %s"),"xxx",v.Field,v.Type)
if i<len(params)-1 {
sb.WriteString(",\n")
} else{
sb.WriteString(";")
}
i++
}
sql := sb.String()
二. 正反序列化
1. JSON
type User struct {
Name string `json:"name"`
Age int `json:"age"`
}
func main() {
// 序列化消息
user := User{Name: "Alice", Age: 30}
jsonData, err := json.Marshal(user) //序列化
if err != nil {
panic(err)
}
// 反序列化消息
var user2 User
err = json.Unmarshal(jsonData, &user2) 反序列化
if err != nil {
panic(err)
}
fmt.Printf("%#v\n", user2)
}
2. CSV
type Person struct {
Name string
Age int
}
func main() {
// 数据
people := []Person{
{"Alice", 30},
{"Bob", 40},
}
file, err := os.Create("people.csv")
if err != nil {
log.Fatal(err)
}
defer file.Close()
writer := csv.NewWriter(file) // 创建一个新的 CSV 编写器,用于将 Go 对象序列化为 CSV 数据。
for _, person := range people {
err := writer.Write([]string{person.Name, fmt.Sprintf("%d", person.Age)}) //写
if err != nil {
log.Fatal(err)
}
}
writer.Flush()
// 从 CSV 文件中读取数据
file, err = os.Open("people.csv")
if err != nil {
log.Fatal(err)
}
defer file.Close()
reader := csv.NewReader(file) // 创建一个新的 CSV 读取器,用于将 CSV 数据反序列化为 Go 对象。
people = []Person{}
for {
record, err := reader.Read()
if err == io.EOF {
break
}
if err != nil {
log.Fatal(err)
}
age := 0
fmt.Sscanf(record[1], "%d", &age)
people = append(people, Person{Name: record[0], Age: age})
}
// 输出读取到的数据
fmt.Println(people)
}
注:fmt.Sscanf用法
package main
import "fmt"
func main() {
var (
old = "1.00000023e+06"
new float64
)
n, err := fmt.Sscanf(old, "%f", &new) //读取old,将old转化为float64的new
if err != nil {
fmt.Println(err.Error())
} else if 1 != n {
fmt.Println("n is not one")
}
fmt.Println(uint64(new))
}
3. Avro
func main() {
// 定义 Avro 模式
schemaJson := `{"type": "record",
"name": "User",
"fields": [
{"name": "Name", "type": "string"},
{"name": "Age", "type": "int"}
]
}`
schema, err := goavro.NewCodec(schemaJson) 创建Avro记录的模式。
if err != nil {
panic(err)
}
// 序列化消息
data := map[string]interface{}{
"Name": "lizhenjun",
"Age": 22,
}
binaryData, err := schema.BinaryFromNative(nil, data)
if err != nil {
panic(err)
}
// 反序列化消息
nativeData, _, err := schema.NativeFromBinary(binaryData)
if err != nil {
panic(err)
}
fmt.Printf("%#v\n", nativeData)
}
在上述示例中,首先定义了一个 Avro 模式,包含两个字段 Name 和 Age,表示用户名和年龄。接着,我们使用 goavro.Record
类型创建一个包含用户信息的记录,并使用 schema.BinaryFromNative
函数将其序列化为二进制数据。最后,我们使用 schema.NativeFromBinary
函数将序列化后的二进制数据反序列化为原始的记录,并输出反序列化后的结果
问题扩展:
1. go file.close()不关闭会占用什么资源,文件句柄?如何查看,close()的作用
如果在 Go 代码中打开了一个文件,但在使用完毕后没有调用 file.Close()
方法关闭文件,那么该文件会一直保持打开状态,直到程序退出时才会被关闭。这会占用操作系统的文件句柄资源,如果打开的文件数量过多,可能会导致程序运行时的性能问题或者因为资源耗尽而出现异常。
在类 Unix 系统中,可以使用 lsof
命令来查看当前打开的文件句柄情况。例如,lsof -p <pid>
命令可以查看指定进程的打开文件句柄列表。这里的 <pid>
指的是进程 ID,可以使用 ps
命令查看程序的进程 ID。
调用 file.Close()
方法可以显式地关闭文件,释放文件资源和文件句柄。Go 语言中的 file.Close()
方法会在文件使用完毕后自动调用,但建议在使用完文件后显式地调用 file.Close()
方法来释放文件资源,以避免因为资源耗尽而导致程序异常。
Go 语言中可以使用 os.File.Stat()
方法来获取文件的状态信息
func main() {
file, err := os.Open("test.txt")
if err != nil {
fmt.Println(err)
return
}
defer file.Close()
stat, err := file.Stat()
if err != nil {
fmt.Println(err)
return
}
fmt.Printf("File handle count: %d\n", stat.Sys().(*syscall.Stat_t).Nlink)
}
在上面的示例中,首先使用 os.Open()
方法打开一个文件,然后通过 file.Stat()
方法获取文件的状态信息。最后,我们通过访问 stat.Sys().(*syscall.Stat_t).Nlink
字段来获取文件的句柄数。
需要注意的是,stat.Sys()
方法返回一个 interface{}
类型的值,需要进行类型断言才能获取到底层的 syscall.Stat_t
类型,从而访问 Nlink
字段。此外,Nlink
字段返回的是硬链接的数量,而不是文件句柄 数。通常情况下,硬链接数量与文件句柄数是相同的。
2. go 如何获取文件的绝对路径
filepath.Abs()
函数来获取文件的绝对路径。该函数需要一个相对路径作为参数,并返回该路径的绝对路径形式
func main() {
// 获取当前工作目录
absPath, _ := filepath.Abs("Test/SMS.go") //相对路径为参数
if err != nil {
fmt.Println(err)
return
}
fmt.Println("Absolute path:", absPath)
}
3. 如何判断文件是否被其他进程使用
可以通过尝试以独占模式打开文件来判断文件是否被其他进程使用。如果文件已经被其他进程打开,则尝试以独占模式打开该文件会失败
func main() {
filename := "test.txt"
file, err := os.OpenFile(filename, os.O_WRONLY, 0600)
if err != nil {
if os.IsPermission(err) {
fmt.Printf("File '%s' is already in use\n", filename)
} else {
fmt.Println(err)
}
return
}
defer file.Close()
// 文件没有被其他进程使用,可以进行写操作
_, err = file.WriteString("Hello, world!\n")
if err != nil {
fmt.Println(err)
return
}
}
4. 如何实现软硬链接?(go实现)
在 Go 语言中,可以使用 os
包中的 Symlink()
函数实现软链接,使用 Link()
函数实现硬链接。以下是一个简单的示例程序,演示如何在 Linux 系统中创建软链接和硬链接:
func main() {
// 创建一个文件
file, err := os.Create("file.txt")
if err != nil {
panic(err)
}
file.Close()
// 创建软链接
if err := os.Symlink("file.txt", "softlink.txt"); err != nil {
panic(err)
}
// 创建硬链接
if err := os.Link("file.txt", "hardlink.txt"); err != nil {
panic(err)
}
// 查看软链接和硬链接的 inode
var fileStat, softlinkStat, hardlinkStat os.FileInfo
if fileStat, err = os.Stat("file.txt"); err != nil {
panic(err)
}
if softlinkStat, err = os.Stat("softlink.txt"); err != nil {
panic(err)
}
if hardlinkStat, err = os.Stat("hardlink.txt"); err != nil {
panic(err)
}
fmt.Printf("file.txt inode: %d\n", fileStat.Sys().(*syscall.Stat_t).Ino)
fmt.Printf("softlink.txt inode: %d\n", softlinkStat.Sys().(*syscall.Stat_t).Ino)
fmt.Printf("hardlink.txt inode: %d\n", hardlinkStat.Sys().(*syscall.Stat_t).Ino)
}
5. 软硬链接的区别?
简单理解:软链接用windows中的概念来理解就相当于一个快捷方式;而硬链接实际上是文件的一个别名
1)硬链接原文件&链接文件共用一个inode号,说明他们是同一个文件;而软链接原文件&链接文件拥有不同的inode号,表明他们是两个不同的文件;
2)链接数目(inode)是不一样的,软链接的链接数目不会增加,硬链接会相应的加一;
3)在文件属性上软链接明确写出了是链接文件,而硬链接没有写出来,因为在本质上硬链接文件和原文件是完全平等关系,就像文件被复制了一份一样;
4)文件大小是不一样的,硬链接文件显示的大小是跟原文件是一样的,而这里软链接显示的大小与原文件就不同了
硬链接和源文件是同一份文件,而软连接是独立的文件,类似于快捷方式,存储着源文件的位置信息便于指向。
使用限制上,不能对目录创建硬链接,不能对不同文件系统创建硬链接,不能对不存在的文件创建硬链接;
可以对目录创建软连接,可以跨文件系统创建软连接,可以对不存在的文件创建软连接。
6. 字符串拼接其他方法?
-
+
操作符str1 := "Hello, " str2 := "world!" result := str1 + str2 fmt.Println(result) // 输出:Hello, world!
使用
+
操作符进行字符串拼接会产生新的字符串对象,因此对于大量字符串拼接的操作,会产生大量的字符串分配和垃圾回收操作,可能会影响程序的性能。 -
fmt.Sprintf()
函数,拼接字符串返回到变量str1 := "Hello, " str2 := "world!" result := fmt.Sprintf("%s%s", str1, str2) fmt.Println(result) // 输出:Hello, world!
fmt.Sprintf()
函数也会产生新的字符串对象,因此对于大量字符串拼接的操作,也可能会影响程序的性能。 -
strings.Join()
函数,可以将一个字符串数组或切片中的所有元素拼接成一个字符串strs := []string{"Hello", ", ", "world!"} result := strings.Join(strs, " let's go!") fmt.Println(result) // 输出:Hello, world! let's go!
strings.Join()
函数会将所有字符串拼接到一起,对于大量字符串拼接的操作,可能会产生较长的字符串,占用较多的内存。 -
bytes.Buffer
类型,bytes.Buffer
类型是一个缓冲区,可以用来动态构造字符串var buffer bytes.Buffer buffer.WriteString("Hello, ") buffer.WriteString("world!") result := buffer.String() fmt.Println(result) // 输出:Hello, world!
使用
bytes.Buffer
类型进行字符串拼接时,可以避免产生大量的字符串分配和垃圾回收操作,因此对于大量字符串拼接的操作,使用bytes.Buffer
类型可能是更好的选择。需要注意的是,使用
bytes.Buffer
类型进行字符串拼接时,需要注意缓冲区的大小,以避免频繁的扩容操作。可以使用bytes.NewBuffer()
函数创建一个指定大小的缓冲区,或者使用bytes.Buffer.Grow()
方法预分配缓冲区的大小。 -
strings.builder
类型,var sb strings.Builder sb.WriteString("Hello, ") sb.WriteString("world!") result := sb.String() fmt.Println(result) // 输出:Hello, world!
strings.Builder
和bytes.Buffer
都是用于构建字符串的类型,它们在实现上有一些区别。- 字符串类型不同
strings.Builder
是专门用于构建字符串的类型,它的底层实现是一个[]string
类型的切片。bytes.Buffer
则是一个通用的缓冲区类型,可以用于构建任何类型的字节序列,包括字符串、字节数组、JSON、XML 等。- 扩容方式不同
在进行字符串拼接时,如果字符串构建器的容量不足,需要进行扩容操作。
strings.Builder
和bytes.Buffer
在扩容时的方式略有不同。strings.Builder
在扩容时,会将底层的[]string
切片扩容,每次扩容会增加当前容量的两倍,直到容量达到所需大小。bytes.Buffer
在扩容时,会使用一个简单的算法,每次扩容会增加当前容量的一倍加上一个预定义的常数(默认为 64),直到容量达到所需大小。- 适用场景不同
strings.Builder
和bytes.Buffer
适用的场景略有不同。strings.Builder
适用于构建字符串,而bytes.Buffer
则适用于构建任何类型的字节序列。由于
strings.Builder
是专门用于构建字符串的类型,因此在进行大量字符串拼接时,使用strings.Builder
可以获得更好的性能。- 其他方法不同
除了构建字符串的方法外,
strings.Builder
和bytes.Buffer
还提供了一些其他的方法,略有不同。strings.Builder
提供了Grow(n int)
方法,可以预分配字符串构建器的内存,以避免频繁的扩容操作。bytes.Buffer
则提供了一些与字节序列相关的方法,例如Bytes()
、Next(n int)
、Read(p []byte) (n int, err error)
、ReadByte() (byte, error)
等。可以根据实际的需求选择合适的类型进行字符串或字节序列的构建。
7. 怎样获取文件的后缀名?
path/filepath
包的 Ext()
函数获取文件的后缀名。如果文件名中包含多个点号,则只返回最后一个
func main() {
filename := "./example.txt.gz.rar"
ext := filepath.Ext(filename)
fmt.Println(ext) // 输出:.rar
}
8. 为什么要用json作为前后端数据传输媒介?
使用 JSON 作为前后端数据传输媒介有以下几个优点:
- 跨语言支持
JSON 是一种轻量级的数据交换格式,被广泛支持和应用于各种编程语言中,包括 JavaScript、Java、Python、Go 等等。因此,使用 JSON 作为前后端数据传输媒介,可以方便地实现跨语言的数据交换。
- 可读性和可维护性
与二进制格式相比,JSON 是一种文本格式,具有良好的可读性和可维护性。开发人员可以直接查看 JSON 数据,以便调试和排错。此外,JSON 格式的可读性和可维护性也使得开发人员可以更方便地对数据进行处理和转换。
- 支持结构化数据
JSON 支持结构化数据,可以表示复杂的数据结构,例如数组、对象等。开发人员可以将复杂的数据结构进行序列化和反序列化,并进行传输和存储。
- 轻量级和高效性能
JSON 是一种轻量级的数据交换格式,相比其他数据交换格式,例如 XML,JSON 的数据格式更加简洁,占用的存储空间更小,并且序列化和反序列化的速度也更快。使得 JSON 在网络传输和存储方面具有更高的性能。
综上所述,使用 JSON 作为前后端数据传输媒介,具有跨语言支持、可读性和可维护性、支持结构化数据、轻量级和高效性能等优点。这使得 JSON 成为了一种广泛使用的数据交换格式,被广泛应用于前后端数据交互、API 设计、数据存储等领域。
9. csv和json互转实践运用
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
}
func main() {
// 打开 CSV 文件
file, err := os.Open("data.csv")
if err != nil {
panic(err)
}
defer file.Close()
// 创建 CSV 读取器
reader := csv.NewReader(file)
// 读取 CSV 文件内容到对象切片
var persons []Person
for {
record, err := reader.Read()
if err == io.EOF {
break
}
if err != nil {
panic(err)
}
name := record[0]
age := record[1]
person := Person{Name: name, Age: age}
persons = append(persons, person)
}
// 将切片转换为 JSON 格式
data, err := json.Marshal(persons)
if err != nil {
panic(err)
}
// 将 JSON 数据写入文件
file, err = os.Create("data.json")
if err != nil {
panic(err)
}
defer file.Close()
_, err = file.Write(data)
if err != nil {
panic(err)
}
// 输出写入的序列化json内容
fmt.Println(string(data))
}
10. 如何指定csv数据的分隔符?
func main() {
// 创建 CSV 文件
file, err := os.Create("data.csv")
if err != nil {
panic(err)
}
defer file.Close()
// 写入 CSV 文件
writer := csv.NewWriter(file)
writer.Comma = '|' //设置分隔符'|',默认为',' 设置其他分隔符后,wps打开默认一行数据存储在一个单元格上
defer writer.Flush()
// 写入 CSV 文件的列名
columns := []string{"name", "age", "gender"}
if err := writer.Write(columns); err != nil {
panic(err)
}
// 写入 CSV 文件的数据行
data := [][]string{
{"Alice", "20", "female"},
{"Bob", "30", "male"},
{"Charlie", "40", "male"},
}
for _, row := range data {
if err := writer.Write(row); err != nil {
panic(err)
}
}
}
11. AVRO示例
package main
import (
"fmt"
"github.com/linkedin/goavro"
"net/http"
)
type Person struct {
name string
age int
}
//模拟前端发送请求
func encodeAvro() (binaryData []byte) {
// 定义 Avro 模式
schemaJson := `{"type": "record",
"name": "User",
"fields": [
{"name": "Name", "type": "string"},
{"name": "Age", "type": "int"}
]
}`
schema, err := goavro.NewCodec(schemaJson) 创建Avro记录的模式。
if err != nil {
panic(err)
}
// 序列化消息
data := map[string]interface{}{
"Name": "lizhenjun",
"Age": 22,
}
binaryData, err = schema.BinaryFromNative(nil, data)
if err != nil {
panic(err)
}
fmt.Println("Avro序列化后的数据:", binaryData)
return binaryData
}
func main() {
// 定义 Avro Schema,用于描述从前端传输过来的数据结构
schemaJSON := `{"type": "record",
"name": "Person",
"fields": [
{"name": "name", "type": "string"},
{"name": "age", "type": "int"}
]
}`
schema, err := goavro.NewCodec(schemaJSON)
if err != nil {
panic(err)
}
http.HandleFunc("/api/person", func(w http.ResponseWriter, r *http.Request) {
// 从 HTTP 请求中读取 Avro 格式的二进制数据
binaryData := encodeAvro()
//binaryData, err := ioutil.ReadAll(r.Body)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
// 反序列化消息
nativeData, _, err := schema.NativeFromBinary(binaryData)
if err != nil {
panic(err)
}
fmt.Printf("Avro反序列化后的数据:%#v\n", nativeData)
fmt.Println("成功处理请求!")
})
// 启动 HTTP 服务器
fmt.Println("启动服务器,监听端口 8080...")
http.ListenAndServe(":8080", nil)
}
代码开启了一个http后端服务,用encodeAvro()
方法模拟从前端请求中接受avro二进制数据,通过浏览器地址访问API触发监听事件,从而对前端发送的已编码的二进制avro数据进行解码处理,得到一个字典对象,可从对象中提取需要的信息,运行结果如下: