【GO语言卵细胞级别教程】12.Go 语言文件操作秘籍:高效技巧解锁!
目录
🥰微信公众号:【给点知识】分享小知识,快速成长,欢迎关注呀!(底部点击二维码)
🥰本项目演示代码仓库:https://gitee.com/gdzsfun/csdn-study-go 演示项目仓库
🥰本项目创建方式:【GO语言卵细胞级别教程】05.项目创建和函数讲解
🥰学习宗旨:活到老,学到老。
😍写作宗旨:致力于改变现有文章难读难懂问题。
今日分享诗句:
###########
蓬门未识绮罗香,
拟托良媒益自伤。
谁爱风流高格调,
共怜时世俭梳妆。
敢将十指夸针巧,
不把双眉斗画长。
苦恨年年压金线,
为他人作嫁衣裳。
1.文件模块简介
1.1概述
文件操作GO语言提供了os模块和io流
1.2打开文件
Go语言提供了丰富的文件操作功能,下面是一些常见的文件操作:
-
打开文件: 使用
os.Open
函数打开文件,它返回一个*os.File
对象和一个可能的错误。可以指定打开文件的模式(只读、只写、追加等)。file, err := os.Open("filename.txt") if err != nil { // 处理错误 } defer file.Close() // 确保在函数结束时关闭文件
-
创建文件: 使用
os.Create
函数创建文件,如果文件已存在则截断文件内容。它返回一个*os.File
对象和一个可能的错误。file, err := os.Create("filename.txt") if err != nil { // 处理错误 } defer file.Close() // 确保在函数结束时关闭文件
-
读取文件内容: 使用
file.Read
或file.ReadAt
方法从文件中读取数据。Read
方法将数据读取到指定的字节切片中,ReadAt
方法从指定的偏移量开始读取数据。它们返回读取的字节数和可能的错误。buffer := make([]byte, 1024) numBytes, err := file.Read(buffer) if err != nil { // 处理错误 }
-
写入文件内容: 使用
file.Write
或file.WriteAt
方法向文件中写入数据。Write
方法从当前位置开始写入数据,WriteAt
方法从指定的偏移量开始写入数据。它们返回写入的字节数和可能的错误。data := []byte("Hello, World!") numBytes, err := file.Write(data) if err != nil { // 处理错误 }
-
重命名文件: 使用
os.Rename
函数将文件重命名为新的名称。err := os.Rename("oldname.txt", "newname.txt") if err != nil { // 处理错误 }
-
删除文件: 使用
os.Remove
函数删除文件。err := os.Remove("filename.txt") if err != nil { // 处理错误 }
-
获取文件信息: 使用
os.Stat
函数获取文件的详细信息,包括文件名、大小、修改时间等。fileInfo, err := os.Stat("filename.txt") if err != nil { // 处理错误 } fmt.Println("文件名:", fileInfo.Name()) fmt.Println("文件大小:", fileInfo.Size()) fmt.Println("修改时间:", fileInfo.ModTime())
这只是一些常见的文件操作示例,Go语言的os
和io
包提供了更多功能丰富的文件操作函数。
1.3实战案例
- 获取文件基本信息和内容,具体步骤入下:
- 首先使用os.Open打开文件获取句柄
- 使用句柄获取文件的基本信息,名称、路径等
- 使用句柄获取文件的属性信息:调用Stat()方法获取属性结构体
- 使用属性结构体获取文件的具体属性:大小、权限等
package mystudy import ( "fmt" "os" "path" // path只能处理一种路径\\ "path/filepath" // filepath 可以处理相对复杂的分隔符 提供了相对路径和绝对路径的处理方法 ) func DemoFile01() { fmt.Println("------文件操作------") file, err := os.Open("D:/07.go/02.gostudy/goproject/src/com/gdzs/main/main.go") if err != nil { fmt.Println("Error:", err) return } defer file.Close() fmt.Println("文件绝对路径:", file.Name()) fmt.Println("文件名:", path.Base("D:/07.go/02.gostudy/goproject/src/com/gdzs/main/main")) fmt.Println("文件名称:", filepath.Base("D:\\07.go\\02.gostudy\\goproject\\src\\com\\gdzs\\main\\main.go")) fmt.Println(os.UserHomeDir()) fileInfo, err := file.Stat() if err != nil { fmt.Println("Error:", err) return } fmt.Println("文件模式和权限:", fileInfo.Mode()) fmt.Println("文件大小:", fileInfo.Size()) fmt.Println("------读取文件的内容------") data := make([]byte, 1000) for{ if numBytes, err := file.Read(data);numBytes > 0{ fmt.Printf("%q, %v, %v", data[:numBytes], err, numBytes) }else{ fmt.Println("output ok:") break } } } // 输出结果 ------文件操作------ 文件绝对路径: D:/07.go/02.gostudy/goproject/src/com/gdzs/main/main.go 文件名: main 文件名称: main.go C:\Users\12862 <nil> 文件模式和权限: -rw-rw-rw- 文件大小: 2724 ------读取文件的内容------ "package main\r\n\r\nimport (\r\n\t\"fmt\"\r\n\t\"com.gdzs/goproject/src/com/gdzs/mystudy\"\r\n\t_\"github.com/gin-gonic/gin\"\r\n)\r\n\r\nfunc init(){\r\n\tfmt.Println(\"init函数执 行了\")\r\n}\r\n//“
- 文件的打开、创建、读取、写入、重命名、获取信息等操作
- 打开文件:os.Open
- 创建文件:os.Create
- 读取文件:句柄.Read(buffer)
- 写入文件:句柄.Write(data)
package mystudy import ( "fmt" "os" _"path" // path只能处理一种路径\\ _"path/filepath" // filepath 可以处理相对复杂的分隔符 提供了相对路径和绝对路径的处理方法 ) //打开文件函数 func OpenFile(filePath string) *os.File { fmt.Println("------打开文件------") var filePhth string = filePath // 打开文件:如果要以何种模式打开可以使用 // file, err := os.OpenFile("data.txt", os.O_RDONLY, 0644) // os.O_RDONLY参数表示只读模式,0644是文件权限。如果文件打开成功, // 将输出"File opened successfully.",否则输出错误信息。 file, err := os.Open(filePhth) if err !=nil{ panic(err) return nil } return file } // 创建文件 func CreateFile(fileName string) *os.File { fmt.Println("------创建文件------") file, err :=os.Create(fileName) if err != nil { panic(fmt.Sprintf("创建文件失败: %s", err)) } return file } // 读取文件 func ReadFile(file os.File){ // 将指针移动到头部 _, err := file.Seek(0, 0) buffer := make([]byte, 13) numBytes, err := file.Read(buffer) fmt.Println("读取文件大小:", numBytes, err) fmt.Printf("文件内容:%q\n", buffer[:]) } // 写入文件内容 func WriteFile(file *os.File){ fmt.Println("---写入文件内容---") data := []byte("hello, World!") numBytes, _ := file.Write(data) fmt.Println(numBytes) } // 重命名文件 func RenameFile(oldName string, fileName string){ err := os.Rename(oldName, fileName) fmt.Println("err:", err) } // 获取文件信息 func GetFileInfo(fileName string){ fileInfo, _ := os.Stat(fileName) fmt.Println("文件名:", fileInfo.Name()) fmt.Println("文件大小", fileInfo.Size()) fmt.Println("修改时间", fileInfo.ModTime()) } func DemoFile01() { fmt.Println("------文件操作------") defer func(){ if r:= recover();r != nil{ fmt.Println("Panic:", r) return } }() var filePath string = "D:\\07.go\\02.gostudy\\goproject\\src\\com\\gdzs\\main\\main.go" // 获取文件信息 GetFileInfo(filePath) file := OpenFile(filePath) if file != nil{ fmt.Println(file) }else{ fmt.Println("打开文件失败") } // 创建文件 createFile := CreateFile("gdzs.go") fmt.Println(createFile) // 写入数据 WriteFile(createFile) // 读取文件 ReadFile(*createFile) createFile.Close() RenameFile("gdzs.go", "gdzs.txt") } // 输出结果 ------文件操作------ 文件名: main.go 文件大小 2724 修改时间 2024-04-15 15:27:30.7824836 +0800 CST ------打开文件------ &{0xc0003ff180} ------创建文件------ &{0xc0003ff400} ---写入文件内容--- 13 读取文件大小: 13 <nil> 文件内容:"hello, World!" err: <nil>
2.io流操作文件
文本作者公众号:
2.1io流简介
io流一般用于处理文件不是很大的。
有了os模块可以操作文件,那么为什么还需要io流呢?
IO 流适用于许多不同的场景,特别是在处理数据流、文件和网络传输时。以下是一些适合使用 IO 流的常见场景:
- 文件读写:IO 流提供了读取和写入文件数据的高级接口,可以逐块地读取或写入文件内容。这对于处理大型文件或需要逐行读取文本文件等情况非常有用。
- 网络传输:在网络编程中,IO 流用于读取和写入网络套接字数据。通过使用
net.Conn
类型的 IO 流,您可以从网络连接中读取数据,或将数据写入到网络连接中,实现与远程服务器的通信。 - 数据流处理:IO 流适用于处理连续的数据流,而不是一次性加载所有数据。这在处理大量数据或需要实时处理数据的情况下非常有用。通过逐块地读取和处理数据流,可以降低内存消耗,并提高处理效率。
- 数据转换处理:IO 流提供了灵活的接口和方法,可以对数据进行转换和处理。通过组合多个 IO 流、使用缓冲区、使用编码解码器等,可以实现各种数据转换和处理操作,如压缩、加密、解析等。
- 多路复用:IO 流支持多路复用,可以同时处理多个数据源或数据目标。通过使用并发或异步的方式,可以同时处理多个 IO 流,从而提高系统的并发性和效率。
os.File
类型实现了 io.Reader
、io.Writer
、io.Closer
等接口,这使得 os.File
可以通过 Read()
、Write()
等方法进行数据的读取和写入。这样,我们可以使用 os
包打开文件,然后使用 io
包提供的通用接口和函数来处理文件数据。
2.2IO流操作文件实战
- io的两种读取方式:使用io/ioutil模块 ioutil.ReadFile(filePath):这个方法已经内置了关闭和打开文件,所以这里不需要关注文件打开关闭,可以直接调用读取方法读取
- 另外一只是一行行 读使用io需要写文件的读写与关闭
file2, _ := os.Open(filePath) defer file2.Close() // 创建一个流 reader := bufio.NewReader(file2) for{ str, err := reader.ReadString('\n') if err == nil || err == io.EOF{ fmt.Println(str) } if err == io.EOF { fmt.Println("---读完了---") break } else if err != nil { fmt.Println("Failed to read file:", err) break } }
- 代码实践
package mystudy // io流读取文件 import ( "fmt" "io/ioutil" "os" "bufio" "io" ) func DemoIoFile(){ var filePath string = "D:/07.go/02.gostudy/gdzs.txt" defer func(){ if err := recover();err != nil{ fmt.Println("报异常:", err) return } }() //io读取文件:自带了文件的开关 file, err := ioutil.ReadFile(filePath) if err != nil { fmt.Println("读取出错,错误为:", err) } fmt.Printf("%v\n",string(file)) //带有缓存的读取方式 fmt.Println("---带有缓存的读取方式---") file2, _ := os.Open(filePath) fmt.Println(file2) defer file2.Close() // 创建一个流 reader := bufio.NewReader(file2) for{ str, err := reader.ReadString('\n') if err == nil || err == io.EOF{ fmt.Println(str) } if err == io.EOF { fmt.Println("---读完了---") break } else if err != nil { fmt.Println("Failed to read file:", err) break } } }
2.3其他的用途:
-
标准输入读取:
package main import ( "bufio" "fmt" "os" ) func main() { scanner := bufio.NewScanner(os.Stdin) for scanner.Scan() { line := scanner.Text() fmt.Println("Input:", line) } if err := scanner.Err(); err != nil { fmt.Println("Error:", err) } }
-
标准输出写入:
package main import ( "bufio" "fmt" "os" ) func main() { writer := bufio.NewWriter(os.Stdout) message := "Hello, World!" _, err := writer.WriteString(message) if err != nil { fmt.Println("Error:", err) } err = writer.Flush() if err != nil { fmt.Println("Error:", err) } }
-
网络通信中的缓冲读取:
package main import ( "bufio" "fmt" "net" ) func main() { conn, err := net.Dial("tcp", "example.com:80") if err != nil { fmt.Println("Error:", err) return } defer conn.Close() reader := bufio.NewReader(conn) for { line, err := reader.ReadString('\n') if err != nil { fmt.Println("Error:", err) break } fmt.Println("Received:", line) } }
-
字符串缓冲读写:
package main import ( "bufio" "fmt" "strings" ) func main() { input := "Hello\nWorld\n" reader := bufio.NewReader(strings.NewReader(input)) for { line, err := reader.ReadString('\n') if err != nil { break } fmt.Println("Line:", line) } writer := bufio.NewWriter(strings.NewWriter()) message := "Hello, World!" _, err := writer.WriteString(message) if err != nil { fmt.Println("Error:", err) } err = writer.Flush() if err != nil { fmt.Println("Error:", err) } output := writer.String() fmt.Println("Output:", output) }
3.文件写入综合实践
- 需要os.OpenFile()打开文件
- 使用bufio写入内容
package mystudy // 文件写入 import "fmt" import "os" import "bufio" func DemoWriteFile(){ var filePath = "D:/07.go/02.gostudy/gdzs.txt" file, _:= os.OpenFile(filePath, os.O_RDWR | os.O_APPEND | os.O_CREATE, 0666) writer := bufio.NewWriter(file) for i:=0; i<=6; i++{ writer.WriteString("你好,中国") } writer.Flush() fmt.Println(file) defer file.Close() }
- 参数详解
在 Go 语言中,os.OpenFile()
函数的第二个参数是一个标志位,用于指定文件的打开模式和权限。在您提到的例子中,0666
是一个表示文件打开模式和权限的八进制数。
在八进制数0666
中,每个数字代表一组权限,共有四组权限:所有者权限、所有者所属组权限、其他人权限以及特殊权限。每组权限由三个二进制位表示,分别代表读、写和执行权限。
具体解释如下:- 第一组(最高位)代表所有者权限。
0
表示没有权限,6
表示所有者具有读和写权限。 - 第二组代表所有者所属组权限。
6
表示所有者所属组具有读和写权限。 - 第三组代表其他人权限。
6
表示其他人具有读和写权限。 - 第四组(最低位)代表特殊权限,例如粘着位、SGID 位、SUID 位等。在这种情况下,
0
表示没有特殊权限。
将0666
转换为二进制表示为110 110 110
,意味着所有者、所有者所属组和其他人都具有读和写权限。
因此,os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE, 0666)
使用0666
打开模式表示创建文件(如果文件不存在)并为该文件设置读和写权限,使得所有者、所有者所属组和其他人都可以读取和写入该文件。
需要注意的是,使用0666
权限设置可能会导致较为宽松的权限,具体权限设置应根据实际需求和安全性考虑进行选择。
- 第一组(最高位)代表所有者权限。
- st权限
在文件权限中,“st” 表示文件的 Set UID (SUID) 和 Set GID (SGID) 权限。- Set UID (SUID):当一个可执行文件被设置了 SUID 权限时,无论谁执行该文件,都会暂时以该文件的所有者身份来执行。这意味着执行者将具有该文件所有者的权限和特权。SUID 权限对于某些需要特定权限才能执行的程序非常有用,例如密码更改程序
passwd
。 - Set GID (SGID):当一个可执行文件被设置了 SGID 权限时,无论谁执行该文件,都会以该文件所属组的身份来执行。与 SUID 类似,SGID 也允许执行者暂时获得执行文件所属组的权限和特权。
这些权限标志通常在文件的权限位中表示为 “s” 或 “S”,具体取决于执行者是否同时具有执行权限。 - 小写 “s” 表示设置了 SUID 或 SGID 权限,并且对应的执行权限也被设置。
- 大写 “S” 表示设置了 SUID 或 SGID 权限,但对应的执行权限未被设置。
以下是一些示例: -rwsr-xr-x
:具有 SUID 权限的可执行文件,所有者具有读、写和执行权限,组和其他人具有执行权限。-rwxr-sr-x
:具有 SGID 权限的可执行文件,所有者具有读、写和执行权限,组具有读、执行权限,其他人具有执行权限。
设置 SUID 和 SGID 权限需要谨慎,以确保文件的安全性和权限控制。
- Set UID (SUID):当一个可执行文件被设置了 SUID 权限时,无论谁执行该文件,都会暂时以该文件的所有者身份来执行。这意味着执行者将具有该文件所有者的权限和特权。SUID 权限对于某些需要特定权限才能执行的程序非常有用,例如密码更改程序
4.文件复制
复制的原理就是,从一个文件中读取数据,然后写入新的文件中,这里使用ioutil包实现
package mystudy
// 文件复制
import (
f"fmt"
"io/ioutil"
)
func DemoFileCopy(){
f.Println("---文件复制---")
filePath1 := "gdzs.txt"
filePath2 := "demo02.txt"
// 读取文件
content, err:= ioutil.ReadFile(filePath1)
f.Println(content)
// 写入文件
err = ioutil.WriteFile(filePath2, content, 0666)
if err != nil{
f.Println("复制文件-写入错误", err)
}
}
5.os包和bufio包操作文件的区别
5.1概述
os
包和bufio
包是Go语言标准库中用于文件操作的两个不同的包,它们有一些区别和不同的用途。
os 包:
os
包提供了与操作系统交互的功能,包括文件和目录的创建、打开、读取、写入、删除等操作。它提供了底层的文件操作接口,可以直接操作文件描述符。os
包中的函数返回的错误类型是error
,需要显式地进行错误处理。它适用于对文件进行较底层、原始的读写操作。
bufio 包:
bufio
包提供了带缓冲功能的 I/O 操作,可以提高文件读写的效率。它基于io.Reader
和io.Writer
接口,提供了带缓冲的读取(bufio.Reader
)和写入(bufio.Writer
)操作。bufio
包内部维护了一个缓冲区,可以减少对底层文件的实际读写次数。它还提供了一些便利的函数,如按行读取文本文件等。bufio
包中的函数返回的错误类型是error
,需要显式地进行错误处理。它适用于高效的文件读写操作。
5.2代码演示
示例代码:
使用 os
包进行文件读写:
package main
import (
"fmt"
"log"
"os"
)
func main() {
file, err := os.OpenFile("filename.txt", os.O_WRONLY|os.O_CREATE, 0644)
if err != nil {
log.Fatal(err)
}
defer file.Close()
data := []byte("Hello, World!")
_, err = file.Write(data)
if err != nil {
log.Fatal(err)
}
readData := make([]byte, 1024)
_, err = file.Read(readData)
if err != nil {
log.Fatal(err)
}
fmt.Printf("读取的数据:%s\n", readData)
}
使用 bufio
包进行文件读写:
package main
import (
"bufio"
"fmt"
"log"
"os"
)
func main() {
file, err := os.OpenFile("filename.txt", os.O_WRONLY|os.O_CREATE, 0644)
if err != nil {
log.Fatal(err)
}
defer file.Close()
writer := bufio.NewWriter(file)
data := []byte("Hello, World!")
_, err = writer.Write(data)
if err != nil {
log.Fatal(err)
}
writer.Flush()
reader := bufio.NewReader(file)
readData, err := reader.ReadString('\n')
if err != nil {
log.Fatal(err)
}
fmt.Printf("读取的数据:%s\n", readData)
}
注意,在以上的示例代码中,我们使用了不同的读取方式,os
包直接调用了file.Read
方法读取数据,而bufio
包通过bufio.NewReader
创建了一个带缓冲的读取器,并使用reader.ReadString
按行读取数据。
简而言之,os
包适用于较底层的文件读写操作,而bufio
包提供了更高级、带缓冲的文件读写功能。选择使用哪个包取决于您的具体需求和场景。
5.3特点和用法
当涉及到文件操作时,os
包和bufio
包在功能和用途上有一些区别,下面将进一步详细介绍它们的特点和用法。
os 包的特点和用法:
os
包提供了一组函数,用于创建、打开、读取、写入、删除和操作文件和目录。- 它提供了底层的文件操作接口,可以直接操作文件描述符。
os
包中的函数返回的错误类型是error
,需要显式地进行错误处理。os
包提供的函数可以用于读取和写入任意类型的数据,但需要手动处理数据的格式和解析。- 适用于对文件进行较底层、原始的读写操作。
- 提供了文件的权限设置、文件信息获取等功能。
bufio 包的特点和用法: bufio
包提供了带缓冲功能的 I/O 操作,可以提高文件读写的效率。- 它基于
io.Reader
和io.Writer
接口,提供了带缓冲的读取和写入操作。 bufio
包内部维护了一个缓冲区,可以减少对底层文件的实际读写次数,提高性能。bufio
包提供了一些便利的函数,如按行读取文本文件、读取指定字节数的数据等。bufio
包中的函数返回的错误类型是error
,需要显式地进行错误处理。- 适用于高效的文件读写操作,特别是处理大量数据时。
使用 os
包读取文件的所有行:
package main
import (
"bufio"
"fmt"
"log"
"os"
)
func main() {
file, err := os.Open("filename.txt")
if err != nil {
log.Fatal(err)
}
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := scanner.Text()
fmt.Println(line)
}
if err := scanner.Err(); err != nil {
log.Fatal(err)
}
}
使用 bufio
包写入文件的所有行:
package main
import (
"bufio"
"log"
"os"
)
func main() {
file, err := os.OpenFile("filename.txt", os.O_WRONLY|os.O_CREATE, 0644)
if err != nil {
log.Fatal(err)
}
defer file.Close()
writer := bufio.NewWriter(file)
lines := []string{"Line 1", "Line 2", "Line 3"}
for _, line := range lines {
_, err := writer.WriteString(line + "\n")
if err != nil {
log.Fatal(err)
}
}
writer.Flush()
}
5.4问题
- 100G的文件,内存只有8G 使用那种方式读取呢?
那么最好的选择是使用逐行读取的方式,以避免将整个文件加载到内存中。可以使用bufio.Scanner
来按行读取文件。bufio.Scanner
会逐行地读取文件内容,而不会一次性将整个文件加载到内存中。这种方法可以在有限的内存下有效地处理大型文件。
示例代码:package main import ( "bufio" "fmt" "log" "os" ) func main() { file, err := os.Open("largefile.txt") if err != nil { log.Fatal(err) } defer file.Close() scanner := bufio.NewScanner(file) for scanner.Scan() { line := scanner.Text() // 在这里处理每一行的数据 fmt.Println(line) } if err := scanner.Err(); err != nil { log.Fatal(err) } }
通过逐行读取文件,可以在有限的内存下处理大型文件,并逐行对文件内容进行处理,而不会超出内存限制。