Go核心开发学习笔记(廿五) —— 文件处理一:文件CRUD和复制统计

文件基本概念

  1. 文件是数据源的一种,主要作用就是保存数据。

  2. 文件是以流的形式来操作的:
    (1) 流:数据在数据源(文件)和程序(内存)之间经历的路径;
    (2) 输入流: 数据从数据源到程序的路径,也就是读文件;
    (2) 输出流: 数据从程序到数据源的路径,也就是写文件。

  3. 文件处理就是主要依赖 os.File结构体

  4. File代表一个打开的文件句柄,是一个指针

  5. 文件打开处理完毕后,必须要关闭,打开是一个函数不是方法,关闭是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}
}
发布了49 篇原创文章 · 获赞 18 · 访问量 4001

猜你喜欢

转载自blog.csdn.net/weixin_41047549/article/details/90318327