Golang文件操作

文件操作

文件的基本介绍

  1. 文件是数据源(保存数据的地方)的一种,比如经常使用的Word文档、txt文件、Excel文件…都是文件。文件的主要主用就是保存数据,它既可以保存图片,也可以保存视频、音频等。
    (数据库本质上也是文件,一种特殊格式的文件 )
  2. 输入流和输出流
    文件在程序中是以流的形式进行操作的。
    在这里插入图片描述

:数据在数据源(文件)和程序(内存)之间经历的路径
输出流:数据从程序(内存)到数据源(文件)的路径
输入流:数据从数据源(文件)到程序(内存)的路径

  1. Golang提供的os包中定义了文件对象。os.File封装所用文件相关操作,File是一个结构体。

type File
type File struct {
// 内含隐藏或非导出字段
}
File概念说明:代表一个打开的文件对象/file指针/文件句柄

文件的基本操作

1.常用的文件操作函数和方法

1)打开一个文件进行读操作:
os.Open(name string)(file *File, err error)

Open打开一个文件用于读取。如果操作成功,返回的文件对象的方法可用于读取数据;对应的文件描述符具有O_RDONLY模式。如果出错,错误底层类型是*PathError。

2)关闭一个文件:
File.Close()实质上是func (f *File) Close()error方法

Close关闭文件f,使文件不能用于读写。它返回可能出现的错误。

故文件的操作是指针/地址操作

案例演示:

package main

import (
	"fmt"
	"os"
)

func main() {

	// 打开一个文件
	file, err := os.Open("E:/goproject/src/go_code/chapter13/文件.txt")
	if err != nil {
		fmt.Println("open file err :", err)
	}
	// 输出文件本身内容,可发现file是结构体指针类型包含了一个地址 *File
	fmt.Printf("file = %v\n", file) //file = &{0xc00010e780}
	fmt.Println(*file)              //{0xc00010e780} File结构体中的内容也是地址

	// 关闭文件
	err = file.Close()
	if err != nil {
		fmt.Println("file closed err: ", err)
	}
}

2. 读文件操作应用实例

1)读取文件内容并显示在终端(带缓冲区的方式),使用os.Opne()file.Close()bufio.NewReader()reader.Readstring函数和方法。

func NewReader
func NewReader(rd io.Reader) *Reader
NewReader创建一个具有默认大小(4096z字节)缓冲、从r读取的*Reader。

bufio包中定义的Reader
type Reader
type Reader struct {
// 内含隐藏或非导出字段
}Reader实现了给一个io.Reader接口对象附加缓冲。

func (*Reader) ReadString
func (b *Reader) ReadString(delim byte) (line string, err error)
ReadString读取直到第一次遇到delim字节,返回一个包含已读取的数据和delim字节的字符串。如果ReadString方法在读取到delim之前遇到了错误,它会返回在错误之前读取的数据以及该错误(一般是io.EOF)。当且仅当ReadString方法返回的切片不以delim结尾时,会返回一个非nil的错误。

func (b *Reader) ReadString(delim byte) (line string, err error)方法本质上是将file的内容读成[][]byte切片形式,每当遇见一个delim byte标识符会将已读出内容存(包括delim byte标识符)放在frag []byte中。所以该方法和for循环连用时能够读出所用的frag []byte,即读出所用的内容。

案例演示:

package main

import (
	"bufio"
	"fmt"
	"io"
	"os"
)

func main() {

	// 打开一个文件
	file, err := os.Open("E:/goproject/src/go_code/chapter13/文件.txt")
	if err != nil {
		fmt.Println("open file err :", err)
	}

	// 开一个文件,一般后直接使用defer方法,当函数退出后及时关闭文件
	//1简单:
	//defer file.Close() //及时关闭file句柄,否则会有内存泄漏	
	//2加匿名函数使用
	defer func() {
		err = file.Close()
		if err != nil {
			fmt.Println("close file err :", err)
		} else {
			fmt.Println("file closes successfully")
		}
	}() //使用defer+匿名函数方法及时关闭file句柄,

	// 创建一个*Reader,带缓冲区的
	/*
		const (
			defaultBufSize = 4096 //默认缓冲区4096个字节
		)
	*/
	// 缓冲的作用在于:不是一次性将整个文件读到内存,而是读一部分处理一部分,
	// 这样可以用于处理大文件

	reader := bufio.NewReader(file)
	for {
		str, err := reader.ReadString('\n') //读取到换行符时结束
		if err == io.EOF {                  //io.EOF表示文件的末尾
			break
		}
		// 输出内容
		fmt.Println(str) //str中包含了dilemma'\n'会产生两次换行
		// fmt.Print(str)

	}
	fmt.Println("文件读取结束")

}

2) 读取文件内容并显示在终端(使用ioutil一次将整个文件读入到内存中),这种方式适合文件不大的情况。相关方法和函数(ioutil.ReadFile)。
ioutil.ReadFile()该函数所调用的os.ReadFile()中封装了将文件的打开和关闭指令,故直接调用ioutil.ReadFile()该函数即可实现文件内容读取。

ioutil.ReadFile()介绍
func ReadFile(filename string) ([]byte, error)
ReadFile 从filename指定的文件中读取数据并返回文件的内容。成功的调用返回的err为nil而非EOF。因为本函数定义为读取整个文件,它不会将读取返回的EOF视为应报告的错误。

ioutil.ReadFile()的实质
func ReadFile(filename string) ([]byte, error){
return os.ReadFile(filename)
}
调用ioutil.ReadFile(filename string)实际返回的是return os.ReadFile(filename ) ([]byte, error)
// 在os.ReadFile()函数中实时上封装了文件开启os.Open()函数和文件关闭func(f *File)Close()方法
// 因此调用 ioutil.ReadFile()函数实现文件一次性读入不需写入开启/关闭文件操作

文件不大时,一次性读入操作案例演示:

package main

import (
	"fmt"
	"io/ioutil"
	"log"
)

func main() {
	//使用ioutil包中的ReadFile()函数一次性将文件内容读取到位
	file := "E:/goproject/src/go_code/chapter13/文件.txt"

	// ReadFile 返回一个切片和错误
	content, err := ioutil.ReadFile(file)
	if err != nil {
		log.Fatal(err) //显示错误信息,并终止程序,下面代码不再执行,

	} else {
		 fmt.Printf("file content:\n%s", content)
	}

	fmt.Println("test...")//当读取文件产生错误时,不会执行。
}

3. 写文件操作应用实例

1)使用os包中的func OpenFile()函数,bufio.NewWriter()函数,以及bufio包中,func(b *Writer) WriteString(s string)func(b *Writer) Flush()方法

func OpenFile
func OpenFile(name string, flag int, perm FileMode) (file *File, err error)

OpenFile是一个更一般性的文件打开函数,大多数调用者都应用OpenCreate代替本函数。它会使用指定的选项(如O_RDONLY等)、指定的模式(如0666等)打开指定名称的文件。如果操作成功,返回的文件对象可用于I/O。如果出错,错误底层类型是*PathError。

name:文件名;
flag int:标识,工作模式选项如下,模式之间可以搭配使用
const (
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 // 如果可能,打开时清空文件)
perm FileMode:文件模式,指定模式(如0666:任何人都可读写,不可执行);在Windows操作系统不生效,在Linux系统下用于操作权限。(参考尚硅谷-Linux课程中权限章节)

bufio.NewWriter()
func NewWriter(w io.Writer) *Writer

NewWriter创建一个具有默认大小缓冲、写入w的*Writer。

bufio包 type Writer
type Writer struct {
// 内含隐藏或非导出字段
}
Writer实现了为io.Writer接口对象提供缓冲。如果在向一个Writer类型值写入时遇到了错误,该对象将不再接受任何数据,且所有写操作都会返回该错误。在所有数据都写入后,调用者有义务调用Flush方法以保证所有的数据都交给了下层的io.Writer。

绑定在*Writer方法:
1.func (b *Writer) WriteString(s string) (int, error)
WriteString写入一个字符串。返回写入的字节数。如果返回值n < len(s),还会返回一个错误说明原因。
2. func (b *Writer) Flush() error
Flush方法将缓冲中的数据写入下层的io.Writer接口。

2)基本应用实例1
(1) 创建一个新文件,写入内容 5句"welcome to Beijin"。os.OpenFile使用的工作模式为os.O_WRONLY|os.O_CREATE
案例演示:

package main

import (
	"bufio"
	"fmt"
	"io/ioutil"
	"log"
	"os"
)

func main() {
	// 创建一个新文件,写入内容 5句"welcome to Beijin"
	// 打开一个不存在的文件,使用混合使用
	filePath := "E:/goproject/src/go_code/chapter13/文件write.txt"

	file, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE, 0666)
	// 在Windows系统下 perm fs.Filemodle 不起作用
	if err != nil {
		log.Fatal(err)
	}

	// 要及时关闭file句柄
	defer file.Close()

	// 准备写入的内容
	str := "Welcome to Beijin"
	// 使用带缓冲的*Writer,使用bufio.NewWriter()
	writer := bufio.NewWriter(file)
	for i := 0; i < 5; i++ {
		writer.WriteString(str + "\n")

	}
	// 因为writer是带缓存的,WriteString方法时
	// 写入的数据存放在writer(*bufio.Writer)的缓存切片中
	// 因此还需调用Flush方法,将缓冲中的数据写入下层的io.Writer接口
	// 实现写的操作
	writer.Flush() //如没有此操作,文件将不会更新写入内容

	// 查看文件内容
	content, err1 := ioutil.ReadFile(filePath)
	if err != nil {
		log.Fatal(err1)
	}
	fmt.Printf("文件write.txt内容:\n%s", content)
}

(2)打开一个存在的文件,将原有的内容覆盖成新的内容。os.OpenFile使用的工作模式为os.O_WRONLY|os.O_TRUNCos.O_TRUNC工作模式会将文件内容清空,使用时需谨慎
增加了另一种写入操作:使用func fmt.Fprint(w io.Writer, a ...interface{}) (n int, err error)函数+func(b *Writer) Flush()方法完成写入操作

func main() {
	// 打开一个文件,将原来的内容覆盖掉

	filePath := "E:\\goproject\\src\\go_code\\chapter13\\文件.txt"
	file, err := os.OpenFile(filePath, os.O_WRONLY|os.O_TRUNC, 0666)
	// os.O_TRUNC将文件内容清空,使用时需谨慎
	if err != nil {
		log.Fatal(err)
	}
	defer file.Close()
	writer := bufio.NewWriter(file)
	fmt.Fprint(writer, "将原来内容覆盖了\r\n")
	fmt.Fprint(writer, "welcome to shanghai")
	writer.Flush() //切不可忘

}

(3)在已有文件中追加内容
os.OpenFile使用的工作模式为os.O_WRONLY|os.O_APPEND

func main() {
	// 在已有文件中追加内容
	filePath := "E:\\goproject\\src\\go_code\\chapter13\\文件.txt"
	file, err := os.OpenFile(filePath, os.O_WRONLY|os.O_APPEND, 0666)
	// os.O_TRUNC将文件内容清空,使用时需谨慎
	if err != nil {
		log.Fatal(err)
	}
	defer file.Close()
	writer := bufio.NewWriter(file)
	// 增加内容
	str := "增加内容:ABC|ENGLISH\r\n"
	// 增加两次
	for i := 0; i < 2; i++ {
		writer.WriteString(str)
	}
	writer.Flush() //切不可忘
}

(4)打开一个文件,将原来的内容读出显示在终端,并追加内容。
os.OpenFile使用的工作模式为os.O_RDWR|os.O_APPEND

func main() {
	// 打开一个文件,将原来的内容读出显示在终端,并追加内容。
	filePath := "E:\\goproject\\src\\go_code\\chapter13\\文件.txt"

	file, err := os.OpenFile(filePath, os.O_RDWR|os.O_APPEND, 0666)
	if err != nil {
		log.Fatal(err)
	}
	defer file.Close()

	reader := bufio.NewReader(file)
	writer := bufio.NewWriter(file)
	// 打印原内容
	for {
		str, err := reader.ReadString('\n')
		if err != nil {
			if err == io.EOF {
				break
			}
			log.Fatal(err)
		}
		fmt.Print(str)
	}
	// 追加内容
	fmt.Fprintf(writer, "\n追加内容:\r\n")
	fmt.Fprintf(writer, "welcome to wuhan")
	writer.Flush() //切不可忘,否则写入数据还在缓存中
}

3)基本应用实例2
编写一个程序,将一个文件的内容,写入(导入)到另外一个文件。注:这两个文件本身已存在
使用:ioutil.ReadFileioutil.WriteFile函数完成案例。

ioutil包
func WriteFile(filename string, data []byte, perm os.FileMode) error
函数向filename指定的文件中写入数据。如果文件不存在将按给出的权限创建文件,否则在写入数据之前清空文件。
在Windows下perm不起作用,故使用该函数时,将内容导入目标文件前,目标文件会清空,即只保存了导入内容。

func main() {
	// 编写一个程序,将一个文件的内容,写入(导入)到另外一个文件。
	// 注:这两个文件本身已存在

	// 1.将源文件内容读取到内存
	// 2.将数据从内存写入到目标文件

	// 源文件
	filePath1 := "E:\\goproject\\src\\go_code\\chapter13\\文件write.txt"
	// 目标文件
	filePath2 := "E:\\goproject\\src\\go_code\\chapter13\\文件.txt"

	content, err := ioutil.ReadFile(filePath1)
	if err != nil {
		log.Fatal(err)
	}
	err = ioutil.WriteFile(filePath2, content, 0666)
	if err != nil {
		log.Fatal(err)
	}
}

4.判断文件/文件夹是否存在操作

golang中判断文件或文件夹是否存在的方法是使用os.Stat()函数返回的错误值error进行判断:

func Stat(name string) (fi FileInfo, err error)
Stat返回一个描述name指定的文件对象的FileInfo。如果指定的文件对象是一个符号链接,返回的FileInfo描述该符号链接指向的文件的信息,本函数会尝试跳转该链接。如果出错,返回的错误值为*PathError类型

1)如果返回的错误是nil,说明文件或文件夹存在
2)如果返回的错误类型,使用os.ISNotExist()函数判断为true,说明该文件或文件夹不存在
3)如果返回的错误类型为其它类型,则不确定是否存在
综述代码:
为使代码更具有逻辑性,编写一个函数PathExists() 实现对文件是否存在的判断

//自写一个路径判断函数
func PathExists(filename string) (bool, error) {
	if _, err := os.Stat(filename); err == nil { //文件或目录存在
		return true, nil
	} else if os.IsNotExist(err) {
		return false, nil
	} else {
		return false, err
	}

}

func main() {
	filePath := "E:\\goproject\\src\\go_code\\chapter14"
	// filePath := "E:\\goproject\\src\\go_code\\chapter13\\文件.txt"

	if judge, err := PathExists(filePath); judge {
		fmt.Printf("filePathe:%v,存在\n", filePath)
		fileInfo, _ := os.Stat(filePath) //接口类型是引用类型
		fmt.Println(fileInfo)
	} else if err == nil && !judge {
		fmt.Printf("filePathe:%v,不存在\n", filePath)
	} else {
		fmt.Printf("filePathe:%v,不确定是否存在\n", filePath)
	}

}

5.文件编程应用实例

1)拷贝操作
案例:将图片/电影/mp3等二进制文件拷贝到别的目录下。使用io.Copy函数

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)进行拷贝。

(1)对于数据量大的文件采用带有缓存的方式进行拷贝
操作说明:
1.由于io.Copy()的参数列表是io.Writerio.Readerinterface类型,故需要先获得符合这两接口类型的变量。
2.使用bufio.NewReader()bufio.NewWriter()函数获得*bufio.Reader*bufio.Writer结构体指针变量,该两者变量分别符合io.Writerio.Readerinterface类型,并且具有缓存功能。
3.bufio.NewReader()bufio.NewWriter()函数的参数列表类型为io.Readerio.Writerinterface类型,为此,使用os.Open(filePath)os.OpenFile()获得*File结构体指针变量,该变量同时是实现了io.Readerio.Writerinterface类型。
综述从3—>1步骤实现了文件的拷贝操作。

案例演示:
为使代码简洁,编写一个函数实现大文件的带缓存的拷贝CopyFile(),

// 编写一个函数,接收两个文件路径, srcFilePath dstFilePath
func CopyFile(dstFilePath, srcFilePath string) (written int64, err error) {

	srcfile, err := os.Open(srcFilePath)
	defer func() {
		if err = srcfile.Close(); err != nil {
			log.Fatal(err)
		}
	}()
	if err != nil {
		log.Fatal(err)
	}
	// 构建 src Reader
	srcReaer := bufio.NewReader(srcfile)

	//打开dstFilePath
	dstfile, err := os.OpenFile(dstFilePath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0666)
	defer func() {
		if err := dstfile.Close(); err != nil {
			log.Fatal(err)
		}
	}()
	if err != nil {
		log.Fatal(err)
	}
	// 构建 src Writer
	dstWriter := bufio.NewWriter(dstfile)

	return io.Copy(dstWriter, srcReaer)

}
func main() {
	// 完成文件的拷贝操作

	// 调用CopyFile() 完成文件拷贝
	srcFile := "C:\\Users\\Admin\\Desktop\\个人\\2.jpg"
	dstFile := "E:\\goproject\\src\\go_code\\chapter13\\jennie1.jpg"
	_, err := CopyFile(dstFile, srcFile)
	if err == nil {
		fmt.Println("拷贝成功")
	} else {
		fmt.Println("拷贝失败", err)
	}
}

(2)对于数据量小的文件采用可以直接进行拷贝
操作说明:
1.由于io.Copy()的参数列表是io.Writerio.Readerinterface类型,故需要先获得符合这两接口类型的变量。
2.使用os.Open(filePath)os.OpenFile()获得*File结构体指针变量,该变量同时是实现了io.Readerio.Writerinterface类型。
综述从2—>1步骤实现了文件的拷贝操作。
小文件的拷贝操作与大文件拷贝带有缓存操作相比,省去了建立具有缓存的*bufio.Reader*bufio.Writer结构体指针变量操作。

代码演示:

func main() {
	srcPath := "E:\\goproject\\src\\go_code\\chapter13\\jennie.jpg"
	dstPath := "E:\\goproject\\src\\go_code\\chapter13\\jenniecopy.jpg"

	srcfile, err := os.Open(srcPath)
	if err != nil {
		log.Fatal(err)
	}
	defer srcfile.Close()

	dstfile, err1 := os.OpenFile(dstPath, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0666)
	if err1 != nil {
		log.Fatal(err)
	}
	defer dstfile.Close()

	if _, err2 := io.Copy(dstfile, srcfile); err2 == nil {
		fmt.Println("copy successfully")
	} else {
		fmt.Println("fail to copy ")
	}
}

2)统计文件中英文、中文、数字、空格和其他字符数量
操作方法:
(1)定义一个结构体用于存放结果
(2)其绑定的方法Count用于统计各类型字符数

// 统计文件的各个类型的字符数
// 定义一个结构体,其字段是各种字符的数量,其绑定的方法用于统计各类型字符数
type CharCount struct {
	ChCount    int
	EnCount    int
	NumCount   int
	SpaceCount int
	OtherCount int
}
func (CC *CharCount) Count(FilePath string) {
	// 打开文件
	file, err := os.Open(FilePath)
	defer func() {
		if err = file.Close(); err != nil {
			log.Fatal(err)
		}
	}()
	if err != nil {
		log.Fatal(err)
	}
	// 使用带缓存的*bufio.Reader,
	// Reader.ReadString('\n')每读一行,统计一次各个类型的字符数
	Reader := bufio.NewReader(file)
	//循环读取 统计
	for {
		str, err := Reader.ReadString('\n')
		if err != nil && err != io.EOF {
			log.Fatal(err)
		}

		// 遍历 每行的每个字符
		for _, v := range str {
			switch { //类似if语句,分支结构的使用方法
			case v >= 'a' && v <= 'z':
				fallthrough //大小写字母是统一类,使用穿透处理,穿透一次
			case v >= 'A' && v <= 'Z':
				CC.EnCount++
			case v >= '0' && v <= '9':
				CC.NumCount++
			case v == ' ' || v == '\t':
				CC.SpaceCount++
			case unicode.Is(unicode.Han, v):
				CC.ChCount++
			default:
				CC.OtherCount++
			}
		}
		if err == io.EOF { //读取完后的标识是返回一个io.EOF error
			break
		}
	}
}

3)命令行参数
希望能够获取到命令行输入的各种参数。为使得
(1)使用os.Args

var Args []string
Args保管了命令行参数,第一个是程序名。

代码演示:

func main() {
	fmt.Println("命令行的参数有", len(os.Args))
	// 遍历os.Args切片,就可以得到所有的命令行参数值
	for i, v := range os.Args {
		fmt.Printf("args[%v]=%v\n", i, v)
	}

}

这里是引用


(2)使用flag包来解析命令行参数
方法(1)是比较原生的方式,对解析参数不是特别方便,特别是对于带有指定参数形式的命令行。Golang提供的flag包,可以更方便的解析命令行参数,而且参数顺序可以随意
具体操作:
1.使用flag.StringVar()flag.IntVar()方法。

func StringVar
func StringVar(p *string, name string, value string, usage string)
StringVar用指定的名称、默认值、使用信息注册一个string类型flag,并将flag的值保存到p指向的变量

func IntVar
func IntVar(p *int, name string, value int, usage string)
IntVar用指定的名称、默认值、使用信息注册一个int类型flag,并将flag的值保存到p指向的变量。

2.在所有flag都注册好了后,需调用flag.Parse()函数

func Parse()
从os.Args[1:]中解析注册的flag。必须在所有flag都注册好而未访问其值时执行。未注册却使用flag -help时,会返回ErrHelp。

代码演示:

func main() {
	// 定义几个变量,用于接收命令行的参数值
	var (
		user string
		pwd  string
		host string
		port int
	)

	// &user  接收用户命令行中输入的 -u 后面的参数值
	// "u",就是 -u指定参数/指定名称
	// "" 默认值
	// "用户说明,默认为空" 使用信息说明
	flag.StringVar(&user, "u", "", "用户说明,默认为空")
	flag.StringVar(&pwd, "pwd", "", "密码,默认为空")
	flag.StringVar(&host, "h", "localhost", "主机名,默认为localhost")
	flag.IntVar(&port, "port", 310, "端口号,默认为310")

	// 必须在所有flag都注册好而未访问其值时执行下面操作
	// 切不可忘,非常重要的操作
	flag.Parse()

	fmt.Printf("user=%v,pwd=%v,host=%v,port=%v", user, pwd, host, port)

}

在这里插入图片描述

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值