文件基本概念
-
文件是数据源的一种,主要作用就是保存数据。
-
文件是以流的形式来操作的:
(1) 流:数据在数据源(文件)和程序(内存)之间经历的路径;
(2) 输入流: 数据从数据源到程序的路径,也就是读文件;
(2) 输出流: 数据从程序到数据源的路径,也就是写文件。 -
文件处理就是主要依赖 os.File结构体。
-
File代表一个打开的文件句柄,是一个指针。
-
文件打开处理完毕后,必须要关闭,打开是一个函数不是方法,关闭是file.Close()绑定方法,不关闭文件后续会造成内存泄漏,一定养成良好习惯,凡是涉及到os.Open(),os.FileOpen()后立刻后面写上 defer <file变量>.Close()
下方几个示例涵盖文件操作相关的流程,用代码比描述更能说明问题和解释问题
示例1: 打开关闭文件流程
package main
import (
"fmt"
"os"
)
func main() {
//打开D盘下的test.txt文件
/*
os包下的func Open(name string) (file *File, err error)
Open打开一个文件用于读取。如果操作成功,返回的文件对象的方法可用于读取数据;
对应的文件描述符具有O_RDONLY模式。如果出错,错误底层类型是*PathError。
*/
file , err := os.Open("D:/test.txt") //file可以被称作 对象 ,指针,句柄 都可以
if err != nil { //习惯性判断,防止出错程序终止
fmt.Println("打开失败,错误为:",err)
}
//打开成功输出文件内容
fmt.Println("打开成功,内容为:", file) //看出file就是一个指针
//关闭文件!!最重要,也会返回一个err错误,
err1 := file.Close()
if err1 != nil { //习惯性判断,防止出错程序终止
fmt.Println("打开失败,错误为:",err1)
}
}
示例2:读取一个文件(带有缓冲区)并显示在终端,需要os.File,file.Close(),bufio.NewReader(),reader.ReadString
package main
import (
"bufio"
"fmt"
"io"
"os"
)
func main() {
/*
import bufio
func NewReader(rd io.Reader) *Reader
NewReader创建一个具有默认大小缓冲、从r读取的*Reader,绑定之前定义的file文件
func (b *Reader) ReadString(delim byte) (line string, err error)
ReadString读取直到第一次遇到delim字节,返回一个包含已读取的数据和delim字节的字符串。
如果ReadString方法在读取到delim之前遇到了错误,它会返回在错误之前读取的数据以及该错误(一般是io.EOF)。
当且仅当ReadString方法返回的切片不以delim结尾时,会返回一个非nil的错误。
*/
//公认的文件处理写法是打开文件立刻后面增加 defer关闭文件
file , err := os.Open("D:/test.txt")
if err != nil {
fmt.Println("发现错误,错误为:",err)
}
defer file.Close()
//创建一个具有默认大小缓冲的4096bytes,这时从源码看到的reader指针
reader := bufio.NewReader(file)
//文件有多行,所以需要循环读取,每次delim用换行结尾'\n',换行就结束
for {
str , err1 := reader.ReadString('\n')
if err1 == io.EOF { //io.EOF是文件的末尾,读到末尾就直接不玩了,直接break就好了
break
}
fmt.Print(str)
}
fmt.Println("文件读取结束")
}
实例3:一次性读取全部内容,io/ioutil.ReadFile(file)
package main
import (
"fmt"
"io/ioutil"
)
func main() {
/*
一次性读取全部内容,没有显式的Open,也没有Close,已经被封装到Readfile中了。
只适合文件不大的,如果读大数据,效率特别低!
*/
file := "D:/test.txt"
content , err := ioutil.ReadFile(file) //一次性读取到位, 返回时一个切片
if err != nil {
fmt.Println("读取错误,错误为:",err)
}
fmt.Printf("%T\n %v",content,string(content)) //content为切片,强转string
}
示例4:如何创建并且写文件
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
/*
写文件是需要的函数
func OpenFile(name string, flag int, perm FileMode) (file *File, err error)
OpenFile是一个更一般性的文件打开函数,大多数调用者都应用Open或Create代替本函数。
它会使用指定的选项(如O_RDONLY等)、指定的模式(如0666等)打开指定名称的文件。
如果操作成功,返回的文件对象可用于I/O。如果出错,错误底层类型是*PathError。
flag中传递的os.xx
O_RDONLY int = syscall.O_RDONLY // 只读模式打开文件
O_WRONLY int = syscall.O_WRONLY // 只写模式打开文件
O_RDWR int = syscall.O_RDWR // 读写模式打开文件
O_APPEND int = syscall.O_APPEND // 写操作时将数据附加到文件尾部
O_CREATE int = syscall.O_CREAT // 如果不存在将创建一个新文件
O_EXCL int = syscall.O_EXCL // 和O_CREATE配合使用,文件必须不存在
O_SYNC int = syscall.O_SYNC // 打开文件用于同步I/O
O_TRUNC int = syscall.O_TRUNC // 如果可能,打开时清空文件
)
*/
//参数1:文件路径 参数2:只读,读写,只写等 参数3:文件权限,linux 返回值是两个跟之前读取时一样。
//需要os.OpenFile(),bufio.NewWriter()函数完成
file, err := os.OpenFile("D:/write.txt", os.O_WRONLY|os.O_CREATE, 0666)
if err != nil {
fmt.Println("请检查错误为:", err)
return
}
defer file.Close()
//写入内容为鸡你太美
str := "鸡你太美" + "\r\n" // \r是解决windows记事本\n不识别
//写入时,使用带缓存的*writer,缓存是4096Bytes
writer := bufio.NewWriter(file)
for i := 0; i < 5; i++ {
writer.WriteString(str)
}
//写完了务必记得东西都写到缓存里,如果不适用flush(),文件里什么也没有
writer.Flush()
//如果要覆盖,则file, err := os.OpenFile("D:/write.txt", os.O_WRONLY|os.O_TRUNC, 0666)
//如果要追加,则file, err := os.OpenFile("D:/write.txt", os.O_WRONLY|os.O_APPEND, 0666)
//str := "律师函警告" 会追加到文件下
}
示例5:如何把一个文件内容读取出来写到另一个文件,假设两个文件都已经存在
package main
import (
"fmt"
"io/ioutil"
)
func main() {
/*
把一个文件内容读入内存,写到另外一个文件中
使用ioutil.ReadFile()和ioutil.WriteFile()
*/
file1 := "D:/writed.txt"
file2 := "E:/writee.txt"
slice, err := ioutil.ReadFile(file1)
if err != nil {
fmt.Println("读取错误,错误为:", err)
return
}
err1 := ioutil.WriteFile(file2,slice,0666) //参数1:目的路径 参数2:写内容的切片 参数3:linux下生效的权限管理
if err1 != nil { //return值只有一个err
fmt.Println("读取错误,错误为:", err1)
}
}
示例6:文件拷贝示例,使用io.Copy()来完成
package main
import (
"bufio"
"fmt"
"io"
"os"
)
//自己写一个函数,传参为字符串,方便后面直接调用,在函数中完成io.Copy()两个指针参数的构建
func copyFile(dstFile string, srcFile string) (written int64, err error) {
//构建reader
file, err := os.Open(srcFile) //os.Open()只能打开已经存在的文件
if err != nil {
fmt.Println("发现错误,错误为:", err)
return
}
defer file.Close()
reader := bufio.NewReader(file)
//构建writer
file1, err := os.OpenFile(dstFile, os.O_WRONLY|os.O_CREATE, 0666) //os.OpenFile()可以创建文件并打开
if err != nil {
fmt.Println("请检查错误为:", err)
return
}
defer file1.Close() //无论是os.Open(),os.OpenFile(),最后都要关闭句柄,防止内存泄漏
writer := bufio.NewWriter(file)
//构建好的reader和writer作为参数传递到io.Copy()函数中,完成拷贝
return io.Copy(writer,reader) //完成构建传入,让函数返回值是完成功能,并且可以传入字符串
}
func main() {
/*
func Copy(dst Writer, src Reader) (written int64, err error)
将src的数据拷贝到dst,直到在src上到达EOF或发生错误。返回拷贝的字节数和遇到的第一个错误。
对成功的调用,返回值err为nil而非EOF,因为Copy定义为从src读取直到EOF,
它不会将读取到EOF视为应报告的错误。
如果src实现了WriterTo接口,本函数会调用src.WriteTo(dst)进行拷贝;
否则如果dst实现了ReaderFrom接口,本函数会调用dst.ReadFrom(src)进行拷贝。
类似运维ansible工具使用了,src到dst
*/
srcFile := "D:/writed.txt"
dstFile := "E:/鸡你太美.txt"
//这里不能直接使用Copy传递路径,因为是reader指针和writer指针,必须要构建函数,让函数传参是字符串
_, err := copyFile(dstFile,srcFile)
if err == nil {
fmt.Println("拷贝成功")
} else {
fmt.Println("拷贝失败,失败原因: ",err)
return
}
}
示例7: 读取一个文本,统计各种字符的数量,包括中文
package main
import (
"bufio"
"fmt"
"io"
"os"
)
type SumCount struct {
charCount int //英文字符
sinoCount int //中文字符
digCount int //数字
spaceCount int //空格
otherCount int //其他ascii字符
}
func main() {
/*
打开一个文件,创建一个reader,每读一行,<SumCount>.<变量>++,进行一次统计
最后把结构体变量对应的每个字段做相应的变更
*/
var sumCount SumCount
file, err := os.Open("E:/鸡你太美.txt")
if err != nil {
fmt.Println("打开文件有误,错误为:",err)
return //程序结束
}
defer file.Close()
reader := bufio.NewReader(file) //把带缓存的reader指针创建出来
for {
str, err := reader.ReadString('\n')
if err == io.EOF {
break
}
str1 := []rune(str) //通过把字符串转换成切片,就可以统计出中文了
for _, v := range str1 { //v为切片中的每一个带下标的元素
switch {
case v > 'a' && v < 'z':
fallthrough //穿透用法,同下,switch 无参数则和if分支条件判断一样了
case v > 'A' && v < 'Z':
sumCount.charCount++
case v > '0' && v < '9':
sumCount.digCount++
case v > 127: //凡是不属于ascii编码的,就加1好了
sumCount.sinoCount++
case v == ' ' || v == '\t' :
sumCount.spaceCount++
default:
sumCount.otherCount++
}
}
}
fmt.Println(sumCount) //{20 4 8 2 8}
}