天天看点

Go文件操作

目录

  • 示例1: 打开和关闭文件
  • 示例2: 打开文件并读取内容
  • 示例3: 一次性读取文件
  • 示例4: 带缓冲的Reader读文件
  • 示例5: 创建文件并写入内容
  • 示例6: 写文件的四种方式
  • 示例7: 把一个文件内容写入到另一个文件
  • 示例8:使用bufio获取用户输入
  • 示例9: 判断文件或目录是否存在
  • 示例10: 拷贝文件、图片音视频
  • 示例11: 遍历目录
    • 遍历目录
    • 仅遍历目录,忽略文件
  • 示例12: 修改文件名
  • 示例13:创建目录
  • 示例14:删除文件

对于文件,我们并不陌生,文件是数据源(保存数据的地方)的一种,比如大家经常使用的word文档,txt文件,Excel文件...等等都是文件。文件最主要的作用就是保存数据,它既可以保存一张图片,也可以保存视频,声音......

文件在程序中是以流的形式来操作的。

Go文件操作

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

输出流:数据从程序(内存)到数据源(文件)的路径

输入流:数据从数据源(文件)到程序(内存)的路径

输入与输出都是相对于内存而言的,从内存向外流就是输出,从外部向内存流就是输入

在Go中,我们操作文件的方法在os包中,会经常使用到os.File结构体 Go语言标准库文档

Go文件操作
Go文件操作
Go文件操作

示例1: 打开和关闭文件

package main

import (
    "fmt"
    "os"
)

func main() {

    //打开文件(/Users/xxx/Go/src/file.txt)
    //概念说明:file的叫法
    //1.file 叫 file对象
    //2.file 叫 file指针
    //3.file 叫 file文件句柄
    file, err := os.Open("/Users/itbsl/Go/src/file.txt")
    if err != nil {
        fmt.Println("文件打开失败,原因是:", err)
        //os.Exit(0)
    }
    defer func() {
        //文件及时关闭
        err = file.Close()
        if err != nil {
            fmt.Println("文件关闭失败,原因是", err)
        }
    }()
}
           

示例2: 打开文件并读取内容

使用Read()函数按照字节读

package main

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

func main() {

	file, err := os.Open("./test.txt")
	if err != nil {
		fmt.Printf("open file failed, err:%v\n", err)
		return
	}
	defer func() {
		err = file.Close()
		if err != nil {
			fmt.Printf("close file failed, err:%v\n", err)
		}
	}()

	var content []byte
	var tmp = make([]byte, 128)
	for {
		n, err := file.Read(tmp)
		//为什么是tmp[:n]而不是tmp[:]?
		//因为当读取到最后一行的内容长度不足tmp的长度的时候
		//新读取的内容只会覆盖前半部分上次读取到的tmp的内容,
		//后半部分还是上一次读取的内容,如果用tmp[:]就会导致
		//后半部分久内容又会被重新赋值一次,这其实是错误的
		content = append(content, tmp[:n]...)
		if err == io.EOF {//读到文件末尾
			break
		}
	}
	fmt.Printf("读取出来的内容为:\n")
	fmt.Printf("%q\n", string(content))
}
           

示例3: 一次性读取文件

读取文件内容并显示在终端,将文件内容一次性读取到终端,适用于文件不大的情况。

package main

import (
    "fmt"
    "io/ioutil"
)

func main() {

    //打开文件,文件路径相对于GOPATH开始,或者写全路径(/Users/xxx/Go/src/file.txt)
    file, err := ioutil.ReadFile("src/file.txt")
    if err != nil {
        fmt.Println("文件打开失败,原因是:", err)
    }

    fmt.Printf("%s", string(file))
}
           

示例4: 带缓冲的Reader读文件

读取文件的内容并显示在终端(带缓冲区的方式),使用

os.Open

,

file.Close

,

bufio.NewReader

,

reader.ReadString

函数和方法。适合读取大文件

1.使用ReadBytes方法

代码1:

package main

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

func main() {

	file, err := os.Open("./test.txt")
	if err != nil {
		log.Fatalf("open file failed, err: %v\n", err)
	}
	defer func() {
		err = file.Close()
		if err != nil {
			log.Fatalf("close file failed, err: %v\n", err)
		}
	}()

	//定义变量result用来存储读取结果
	var result string
	//创建一个带有缓冲区的reader
	reader := bufio.NewReader(file)
	for {
		buf, err := reader.ReadBytes('\n')
		if err != nil && err == io.EOF { //EOF代表文件的末尾
			//注意:为什么要判断err是否等于io.EOF?
			//因为存在这种情况,文件有内容的最后那一行尾部没有换行
			//当使用ReadBytes或者ReadString方法按照'\n'换行读取时,读到尾部没有换行的这种情况时就会报io.EOF错误
			//此时buf是读取到了内容的,如果忽略掉了,那么最终的读取结果会少了最后一行的内容
			result += string(buf)
			break
		}
		result += string(buf)
	}
	fmt.Println(result)
}
           

代码2:

package main

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

func main() {

	file, err := os.Open("./test.txt")
	if err != nil {
		log.Fatalf("open file failed, err: %v\n", err)
	}
	defer func() {
		err = file.Close()
		if err != nil {
			log.Fatalf("close file failed, err: %v\n", err)
		}
	}()

	//定义变量result用来存储读取结果
	var result string
	//创建一个带有缓冲区的reader
	reader := bufio.NewReader(file)
	for {
		buf, err := reader.ReadBytes('\n')
		if err != nil {
			if err == io.EOF { //EOF代表文件的末尾
			//注意:为什么要判断err是否等于io.EOF?
			//因为存在这种情况,文件有内容的最后那一行尾部没有换行
			//当使用ReadBytes或者ReadString方法按照'\n'换行读取时,读到尾部没有换行的这种情况时就会报io.EOF错误
			//此时buf是读取到了内容的,如果忽略掉了,那么最终的读取结果会少了最后一行的内容
				result += string(buf)
				break
			} else {
				log.Fatalf("ReadBytes failed, err: %v\n", err)
			}
		}
		result += string(buf)
	}
	fmt.Println(result)
}
           

2.ReadString方法

package main

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

func main() {

    //打开文件
    file, err := os.Open("./files/test.txt")
    if err != nil {
        fmt.Println("文件打开失败,原因是:", err)
        return
    }

    //当函数退出时,要及时的关闭file
    defer func() {
        //文件及时关闭
        err = file.Close()
        if err != nil {
            fmt.Println("文件关闭失败,原因是", err)
        }
    }()

    //创建一个 *Reader,是带缓冲的
    reader := bufio.NewReader(file)
    var result string
    //循环读取文件内容
    for {
        str, err := reader.ReadString('\n') //读到一个换行就结束
        result += str
        if err == io.EOF {//io.EOF代表文件的末尾
            //注意:如果文件最后一行文字没有换行,则会一直读取到文件末尾,
            //所以即使是判断读到了文件末尾,也要把读取的内容输出一下
            break
        }
    }
    fmt.Println(result)
}
           

示例5: 创建文件并写入内容

Go文件操作

第二个参数:文件代开模式(可以组合);第三个参数:权限控制(如0755)

Go文件操作
package main

import (
	"fmt"
	"os"
)

func main() {

	//1.创建文件file.txt
	file, err := os.OpenFile("src/file.txt", os.O_WRONLY | os.O_CREATE, 0755)
	if err != nil {
		fmt.Println("文件打开/创建失败,原因是:", err)
		return
	}

	defer func() {
		err  = file.Close()
		if err != nil {
			fmt.Println("文件关闭失败,原因是:", err)
		}
	}()

	//写入数据
	var str = "暗黑西游狮驼岭,斗战胜佛孙悟空。\n"

	for i := 0; i < 5; i++ {
		file.WriteString(str)
	}
}
           

示例6: 写文件的四种方式

1.使用WriteAt()搭配Seek()方法实现写文件功能

package main

import (
	"io"
	"log"
	"os"
)

func main() {

	file, err := os.OpenFile("./test.txt", os.O_RDWR|os.O_CREATE, 0755)
	if err != nil {
		log.Fatalf("open file failed, err: %v\n", err)
	}
	defer func() {
		err = file.Close()
		if err != nil {
			log.Fatalf("close file failed, err: %v\n", err)
		}
	}()
    //Seek(): 修改文件的读写指针位置.
    //参数1: 偏移量. 正:向文件尾部偏移, 负:向文件头部偏移
    //参数2: 偏移起始位置
    //       io.SeekStart: 文件起始位置
    //       io.SeekCurrent: 文件当前位置
    //       io.SeekEnd: 文件结尾位置
    //返回值:表示从文件起始位置,到当前文件读写指针位置的偏移量。
    //WriteAt(): 在文件指定偏移位置,写入[]byte,通常搭配Seek()
    //参数1: 待写入的数据
    //参数2: 偏移量
    //返回: 实际写出的字节数
	for i := 0; i < 5; i++ {
		offset, _ := file.Seek(-3, io.SeekEnd)
		_, _ = file.WriteAt([]byte("你好"), offset)
	}
}
           

注意: 由于使用的OpenFile函数打开的文件,所以在选择打开模式的时候不能选择

os.O_APPEND

模式,因为该模式表示的是在文件末尾追加,这与WriteAt在指定的位置写是想冲突的,虽然我在测试的时候加上

os.O_APPEND

模式并没有报错,但是代码执行完之后发现,想要写入的内容并没有真正的写入到文件中。

写入前

Go文件操作

写入后

Go文件操作

2.一次性写文件

package main

import (
	"io/ioutil"
	"log"
)

func main() {
	str := "hello树先生"
	//如果文件已存在,则会清空原来的内容,写入新内容,如果文件不存在,则会创建文件并写入内容
	err := ioutil.WriteFile("./test.txt", []byte(str), 0755)
	if err != nil {
		log.Fatalf("写入文件错误,错误为:%v\n", err)
	}
}
           

3.使用带缓冲的方式写文件

package main

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

func main() {

	//1.创建文件file.txt
	file, err := os.OpenFile("src/file.txt", os.O_WRONLY | os.O_CREATE | os.O_TRUNC, 0755)
	defer func() {
		err  = file.Close()
		if err != nil {
			fmt.Println("文件关闭失败,原因是:", err)
		}
	}()

	if err != nil {
		fmt.Println("文件创建失败,原因是:", err)
		return
	}

	//写入数据
	var str = "你好,世界\n"

	//写入时,使用带缓存的*Writer
	writer := bufio.NewWriter(file)

	for i := 0; i < 5; i++ {
		writer.WriteString(str)
	}

	//因为writer是带缓存的,因此在调用writeString方法时,其实内容是先写入到缓存
	//因此需要调用Flush方法,将缓存数据写入到文件中,否则文件中会丢失数据
	writer.Flush()
}
           

示例7: 把一个文件内容写入到另一个文件

package main

import (
    "fmt"
    "io/ioutil"
)

func main() {

    //打开文件,文件路径相对于GOPATH开始,或者写全路径(/Users/xxx/Go/src/file.txt)
    data, err := ioutil.ReadFile("src/1.txt")
    if err != nil {
        fmt.Println("文件打开失败,原因是:", err)
    }

    err = ioutil.WriteFile("src/2.txt", data, 0755)

    if err != nil {
        fmt.Println("文件写入失败,原因是:", err)
    }
}
           

示例8:使用bufio获取用户输入

package main

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

func main() {
    var s string
    var reader = bufio.NewReader(os.Stdin)
    s, _ = reader.ReadString('\n')
    fmt.Printf("读取到的内容为:%s\n", s)
}
           

示例9: 判断文件或目录是否存在

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

(1)如果返回的错误为nil,说明文件或文件夹存在

(2)如果返回的类型使用os.IsNotExist()判断为true,说明文件或文件夹不存在

(3)如果返回的错误为其它类型,则不确定是否存在

package main

import (
	"fmt"
	"os"
)

func main() {

	isExist, err := isFileExists("src/sfile.txt")
	if err != nil {
		fmt.Println("发生错误:", err)
	}

	if isExist {
		fmt.Println("存在")
	} else {
		fmt.Println("不存在")
	}
}

//判断文件或者目录是否存在
func isFileExists(path string) (bool, error) {

	_, err := os.Stat(path)
	if err == nil {
		return true, nil
	}
	if os.IsNotExist(err) {
		return false, nil
	}
	return false, err
}
           

示例10: 拷贝文件、图片音视频

io.Copy方法

package main

import (
	"fmt"
	"io"
	"os"
)
func CopyFile(srcFileName string, dstFileName string) (int64, error) {

	//源文件处理
	srcFile, err := os.Open(srcFileName)
	defer func() {
		err = srcFile.Close()
		if err != nil {
			fmt.Println("源文件关闭失败,原因是:", err)
		}
	}()

	if err != nil {
		fmt.Println("源文件打开失败,原因是:", err)
		return 0, err
	}

	//目标文件处理
	dstFile, err := os.OpenFile(dstFileName, os.O_CREATE | os.O_WRONLY, 0755)
	defer func() {
		err = dstFile.Close()
		if err != nil {
			fmt.Println("目标文件关闭失败,原因是:", err)
		}
	}()
	if err != nil {
		fmt.Println("目标文件打开失败,原因是:", err)
		return 0, err
	}

	return io.Copy(dstFile, srcFile)
}

func main() {

	result, err := CopyFile("src/dst.jpeg", "src/哈哈.jpeg")

	if err == nil {
		fmt.Println("拷贝成功!拷贝的字节数为: ", result)
	}
}
           

对于大文件,我们还可以采用下面的方式

package main

import (
	"io"
	"log"
	"os"
)

func CopyFile(srcFileName string, dstFileName string) {
	//打开源文件
	srcFile, err := os.Open(srcFileName)
	if err != nil {
		log.Fatalf("源文件读取失败,原因是:%v\n", err)
	}
	defer func() {
		err = srcFile.Close()
		if err != nil {
			log.Fatalf("源文件关闭失败,原因是:%v\n", err)
		}
	}()

	//创建目标文件,稍后会向这个目标文件写入拷贝内容
	distFile, err := os.Create(dstFileName)
	if err != nil {
		log.Fatalf("目标文件创建失败,原因是:%v\n", err)
	}
	defer func() {
		err = distFile.Close()
		if err != nil {
			log.Fatalf("目标文件关闭失败,原因是:%v\n", err)
		}
	}()
	//定义指定长度的字节切片,每次最多读取指定长度
	var tmp = make([]byte, 1024*4)
	//循环读取并写入
	for {
		n, err := srcFile.Read(tmp)
		n, _ = distFile.Write(tmp[:n])
		if err != nil {
			if err == io.EOF {//读到了文件末尾,并且写入完毕,任务完成返回(关闭文件的操作由defer来完成)
				return
			} else {
				log.Fatalf("拷贝过程中发生错误,错误原因为:%v\n", err)
			}
		}
	}
}

func main() {
	CopyFile("./worm.mp4", "./dist.mp4")
}
           

示例11: 遍历目录

遍历目录

package main

//我们读写的文件一般存放于目录中.因此,有时需要指定到某一个目录下,根据目录存储的状况
//再进行文件的特定操作.接下来我们看看目录的基本操作方法.
import (
	"fmt"
	"log"
	"os"
)
//打开目录
//打开目录我们也使用OpenFile函数,但要指定不同的参数来通知系统,要打开的是一个目录文件.
//func OpenFile(name string, flag int, perm FileMode) (file *File, err error)
//参数1: name,表示要打开的目录名称.使用绝对路径较多
//参数2: flag,表示打开文件的读写模式
//参数3: perm,表示打开权限.但对于目录来说有所不同,通常传os.ModeDir.
//返回值:由于是操作目录,所以file是指向目录的文件指针.err中保存错误信息

//读目录内容
//这与读文件有所不同.目录中存放的是文件名和子目录名.所以使用Readdir函数
//func (f *File) Readdir(n int) (fi []FileInfo, err error)
//如果n>0,Readdir函数会返回一个最多n个成员的切片。这时,如果Readdir返回一个空切片,
//它会返回一个非nil的错误说明原因。如果到达了目录f的结尾,返回值err会是io.EOF。
//
//如果n<=0,Readdir函数返回目录中剩余所有文件对象的FileInfo构成的切片。
//此时,如果Readdir调用成功(读取所有内容直到结尾),它会返回该切片和nil的错误值。
//如果在到达结尾前遇到错误,会返回之前成功读取的FileInfo构成的切片和该错误。

func main() {
	//不推荐,因为通过查看ioutil.ReadDir()函数可知,官方使用的是os.Open()函数打开的目录
	//file, err := os.OpenFile("./dir", os.O_RDWR, os.ModeDir)
	file, err := os.Open("./dir")
	if err != nil {
		log.Fatalf("文件打开失败,原因是:%v\n", err)
	}
	defer func() {
		err = file.Close()
		if err != nil {
			log.Fatalf("文件关闭失败,原因是:%v\n", err)
		}
	}()
	//Readdir方法返回一个FileInfo接口类型的切片和一个error类型的错误
	infos, err := file.Readdir(-1)
	for _, info := range infos {
		fmt.Printf("%v, %v\n", info.Name(), info.IsDir())
	}
}
           

仅遍历目录,忽略文件

方法1:使用os包

package main

import (
    "fmt"
    "os"
)

var dirNames = make([]string, 0, 50)
var pathSeparator = string(os.PathSeparator)
func traverseDir(filePath string) error {
    file, err := os.Open(filePath)
    if err != nil {
        return err
    }
    fileInfo, err := file.Readdir(0)
    if err != nil {
        return err
    }

    for _, value := range fileInfo {
        if value.IsDir() {
            dirNames = append(dirNames, value.Name())
            err = traverseDir(filePath+pathSeparator+value.Name())
            if err != nil {
                return err
            }
        }
    }
    return err
}

func main() {

    var filePath = "./dir"
    err := traverseDir(filePath)
    if err != nil {
        fmt.Println(err)
    }
    fmt.Println(dirNames)
}
           

方法2:使用ioutil包

package main

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

var dirNames = make([]string, 0, 50)
var pathSeparator = string(os.PathSeparator)
func traverseDir(filePath string) error {
    fileInfos, err := ioutil.ReadDir(filePath)
    if err != nil {
        return err
    }
    for _, fileInfo :=range fileInfos {
        if fileInfo.IsDir() {
            dirNames = append(dirNames, fileInfo.Name())
            err =  traverseDir(filePath+pathSeparator+fileInfo.Name())
            if err != nil {
                return err
            }
        }
    }
    return err
}

func main() {

    var filePath = "./dir"
    err := traverseDir(filePath)
    if err != nil {
        fmt.Println(err)
    }
    fmt.Println(dirNames)
}
           

示例12: 修改文件名

package main

import (
    "fmt"
    "io/ioutil"
    "os"
    "strings"
)

var pathSeparator = string(os.PathSeparator)
//重命名文件
func renameFileName(filePath string, old string, new string) error {
    files, err := ioutil.ReadDir(filePath)
    if err != nil {
        return err
    }
    for _, fileInfo := range files {
        if !fileInfo.IsDir() {
            err = os.Rename(filePath + pathSeparator + fileInfo.Name(),
                filePath + pathSeparator + strings.Replace(fileInfo.Name(), old, new, -1),
            )
            if err != nil {
                return err
            }
        }
    }
    return err
}

func main() {
    var filePath = "./dir"
    err := renameFileName(filePath, "f", "kkk")
    if err != nil {
        fmt.Printf("错误: %v\n", err)
    }
}
           

示例13:创建目录

package main

import (
	"fmt"
	"os"
)

func main() {
	//Mkdir使用指定的权限和名称创建一个目录。如果出错,会返回*PathError底层类型的错误。
	err := os.Mkdir("./foo", 0755)
	if os.IsExist(err) {
		fmt.Println("目录已存在")
		return
	}

	//MkdirAll使用指定的权限和名称创建一个目录,包括任何必要的上级目录,并返回nil,否则返回错误。
	//权限位perm会应用在每一个被本函数创建的目录上。如果path指定了一个已经存在的目录,MkdirAll不做任何操作并返回nil。
	err = os.MkdirAll("./foo/bar", 0755)
	if err != nil {
		fmt.Printf("%v\n", err)
		return
	}
}
           

示例14:删除文件

package main

import (
	"fmt"
	"os"
)

func main() {
	//Remove删除name指定的文件或目录。如果出错,会返回*PathError底层类型的错误。
	//该方法不能删除非空目录,如果想删除目录以及目录下的所有文件,可以使用RemoveAll
	err := os.Remove("./def")
	if os.IsNotExist(err) {
		fmt.Println("您要删除的文件或目录不存在")
		return
	}
	if err != nil {
		fmt.Println(err)
	}

	//RemoveAll删除path指定的文件,或目录及它包含的任何下级对象。
	//它会尝试删除所有东西,除非遇到错误并返回。
	//如果path指定的对象不存在,RemoveAll会返回nil而不返回错误。
	err = os.RemoveAll("./def")
	if err != nil {
		fmt.Println(err)
	}
}