GO语言中的I/O操作

格式化输出

输出格式 输出内容
%t 单词 true 或 false
%b 表示为二进制
%d 表示为十进制
%e (=%.6e)有 6 位小数部分的科学计数法,如 -1234.456e+78
%f (=%.6f)有 6 位小数部分,如 123.456123
%g 根据实际情况采用 %e 或 %f 格式(获得更简洁、准确的输出)
%s 直接输出字符串或者字节数组
%v 值的默认格式表示
%+v 类似 %v,但输出结构体时会添加字段名
%#v 值的 Go 语法表示
值的类型的 Go 语法表示

标准输入

fmt.Println("please input two word")
var word1 string 
var word2 string
fmt.Scan(&word1, &word2) //读入多个单词,空格分隔。如果输入了更多单词会被缓存起来,丢给下一次scan

fmt.Println("please input an int")
var i int
fmt.Scanf("%d", &i) //类似于Scan,转为特定格式的数据  会返回数据和error

例子

package main

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

func Ma() {
	go func() {
		for {
			a := ""
			n, err := fmt.Scanln(&a)
			if err != nil {
				if err != io.EOF {
					fmt.Println(err, "====")
				}
			}
			if n == 0 {
				continue
			}
			fmt.Println("你输入了", a)
			if a == "abc" {
				os.Exit(111)
			}
		}
	}()
	time.Sleep(1 * time.Hour)
}

打开文件

func os.Open(name string) (*os.File, error)
fout, err := os.OpenFile("data/verse.txt", os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666)
  • os.O_WRONLY :以只写的方式打开文件

  • os.O_RDWR :读写方式打开

  • os.O_RDONLY :只读

  • os.O_TRUNC :把文件之前的内容先清空掉,需要配合os.O_WRONLY

  • os.O_CREATE :如果文件不存在则先创建

  • os.O_APPEND :追加方式写

  • os.O_SYNC :以同步I/O的方式打开

  • 0666 :新建文件的权限设置。 注意:这里一定是四位数,3位数在linux中会出现权限不符合预期的情况

读文件

方法

cont := make([]byte, 10)
fin.Read(cont) //读出len(cont)个字节,返回成功读取的字节数
fin.ReadAt(cont, int64(n)) //从指定的位置开始读len(cont)个字节
fin.Seek(int64(n), 0) //重新定位。whence: 0从文件开头计算偏移量,1从当前位置计算偏移量,2到文件末尾的偏移量

1、整个文件读取入内存

直接将数据直接读取入内存,是效率最高的一种方式,但此种方式,仅适用于小文件,对于大文件,则不适合,因为比较浪费内存。

1.1 直接指定文件名读取

有两种方法

第一种:使用 os.ReadFile

package main

import (
	"fmt"
	"os"
)

func main() {
	content, err := os.ReadFile("a.txt")
	if err != nil {
		panic(err)
	}
	fmt.Println(string(content))
}

第二种:使用 ioutil.ReadFile

package main

import (
	"io/ioutil"
	"fmt"
)

func main() {
	content, err := ioutil.ReadFile("a.txt")
	if err != nil {
		panic(err)
	}
	fmt.Println(string(content))
}

其实在 Go 1.16 开始,ioutil.ReadFile 就等价于 os.ReadFile,二者是完全一致的

// ReadFile reads the file named by filename and returns the contents.
// A successful call returns err == nil, not err == EOF. Because ReadFile
// reads the whole file, it does not treat an EOF from Read as an error
// to be reported.
//
// As of Go 1.16, this function simply calls os.ReadFile.
func ReadFile(filename string) ([]byte, error) {
	return os.ReadFile(filename)
}

1.2 先创建句柄再读取

如果仅是读取,可以使用高级函数 os.Open

package main

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

func main() {
	file, err := os.Open("a.txt")
	if err != nil {
		panic(err)
	}
	defer file.Close()
	content, err := ioutil.ReadAll(file)
	fmt.Println(string(content))
}

之所以说它是高级函数,是因为它是只读模式的 os.OpenFile

// Open opens the named file for reading. If successful, methods on
// the returned file can be used for reading; the associated file
// descriptor has mode O_RDONLY.
// If there is an error, it will be of type *PathError.
func Open(name string) (*File, error) {
	return OpenFile(name, O_RDONLY, 0)
}

因此,你也可以直接使用 os.OpenFile,只是要多加两个参数

package main

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

func main() {
	file, err := os.OpenFile("a.txt", os.O_RDONLY, 0)
	if err != nil {
		panic(err)
	}
	defer file.Close()
	content, err := ioutil.ReadAll(file)
	fmt.Println(string(content))
}

2、每次只读取一行

一次性读取所有的数据,太耗费内存,因此可以指定每次只读取一行数据。

方法有三种:

bufio.ReadLine()
bufio.ReadBytes('\n')
bufio.ReadString('\n')

在 bufio 的源码注释中,曾说道 bufio.ReadLine() 是低级库,不太适合普通用户使用,更推荐用户使用 bufio.ReadBytesbufio.ReadString 去读取单行数据。

因此,这里不再介绍 bufio.ReadLine()

2.1 使用 bufio.ReadBytes

package main

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

func main() {
	// 创建句柄
	fi, err := os.Open("christmas_apple.py")
	if err != nil {
		panic(err)
	}
	defer fi.Close()
	// 创建 Reader
	r := bufio.NewReader(fi)

	for {
		lineBytes, err := r.ReadBytes('\n')
		line := strings.TrimSpace(string(lineBytes))//去掉两边的空格
		if err != nil && err != io.EOF {
			panic(err)
		}
		if err == io.EOF {
			break
		}
		fmt.Println(line)
	}
}

2.2 使用 bufio.ReadString

package main

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

func main() {
	// 创建句柄
	fi, err := os.Open("a.txt")
	if err != nil {
		panic(err)
	}
    defer fi.Close()
	// 创建 Reader
	r := bufio.NewReader(fi)//读文件文件建议用bufio.Reader

	for { //无限循环
		line, err := r.ReadString('\n')//指定分隔符
		line = strings.TrimSpace(line) //去除两边的空格
		if err != nil && err != io.EOF {
			panic(err)
		}
		if err == io.EOF {
            if line>0{
                fmt.Println(line)
            }
			break
		}
		fmt.Println(line)
	}
}

3、每次只读取固定字节数

每次仅读取一行数据,可以解决内存占用过大的问题,但要注意的是,并不是所有的文件都有换行符 \n。

因此对于一些不换行的大文件来说,还得再想想其他办法。

3.1 使用 os 库

通用的做法是:

先创建一个文件句柄,可以使用 os.Open 或者 os.OpenFile
然后 bufio.NewReader 创建一个 Reader
然后在 for 循环里调用 Reader 的 Read 函数,每次仅读取固定字节数量的数据。

package main

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

func main() {
	// 创建句柄
	fi, err := os.Open("a.txt")
	if err != nil {
		panic(err)
	}
    
    defer fi.Close()

	// 创建 Reader
	r := bufio.NewReader(fi)

	// 每次读取 1024 个字节
	buf := make([]byte, 1024)
	for {
		n, err := r.Read(buf)//用创建的切片来接收读取的数据  返回的n是成功读取的字节数
		if err != nil && err != io.EOF {
			panic(err)
		}
        //最后一行就会把EOF读到,所以不能使用EOF返回
        //if err == io.EOF {
		//	break
		//}
		if n == 0 {
			break
		}
		fmt.Println(string(buf[:n]))
	}
}

3.2 使用 syscall 库(不推荐)

os 库本质上也是调用 syscall 库,但由于 syscall 过于底层,如非特殊需要,一般不会使用 syscall

本篇为了内容的完整度,这里也使用 syscall 来举个例子。

本例中,会每次读取 100 字节的数据,并发送到通道中,由另外一个协程进行读取并打印出来。

package main

import (
	"fmt"
	"sync"
	"syscall"
)

func main() {
	fd, err := syscall.Open("christmas_apple.py", syscall.O_RDONLY, 0)
	if err != nil {
		fmt.Println("Failed on open: ", err)
	}
	defer syscall.Close(fd)

	var wg sync.WaitGroup
	wg.Add(2)
	dataChan := make(chan []byte)
	go func() {
		wg.Done()
		for {
			data := make([]byte, 100)
			n, _ := syscall.Read(fd, data)
			if n == 0 {
				break
			}
			dataChan <- data
		}
		close(dataChan)
	}()

	go func() {
		defer wg.Done()
		for {
			select {
			case data, ok := <-dataChan:
				if !ok {
					return
				}

				fmt.Printf(string(data))
			default:

			}
		}
	}()
	wg.Wait()
}

写文件

defer fout.Close() //别忘了关闭文件句柄
writer := bufio.NewWriter(fout)
writer.WriteString("明月多情应笑我")
writer.WriteString("\n") //需要手动写入换行符
writer.flush() //强行将缓存写入文件

例子

package main

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

func main() {
	file, err := os.OpenFile("1", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0755) //以追加,如果没有就创建和只写的模式打开文件 并设置文件的权限为0755
	fmt.Println(err)
	if err != nil {
		panic(err)
	}
	defer file.Close()   //用defer设置关闭文件
	r := bufio.NewWriter(file)  //创建writer
	nn, err := r.Write([]byte("用write写\n"))  //将字符串转换成byte切片写入
	fmt.Println(nn, err)     
	nn, err = r.WriteString("用WriteString写\n")  //直接写入字符串
	fmt.Println(nn, err)
	r.Flush()    //最后一定要刷新缓存,将内容写入磁盘
}

读写文件综合例子

package homework6

import (
	"bufio"
	"errors"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"strings"
)

var new_txt = "bigtxt.txt"
var bigfile, err = os.OpenFile(new_txt, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0755)

func Init() {
	if err != nil {
		panic(errors.New("无法创建新文件"))
	}
}

// 关闭
func exit() {
	if bigfile != nil {
		bigfile.Close()
	}
}

// 整理文件
func bigtxt(path string) error {
	infos, err := os.ReadDir(path)
	if err != nil {
		return err
	}
	writer := bufio.NewWriter(bigfile)
	for _, d := range infos {
		p := filepath.Join(path, d.Name())
		if d.IsDir() {
			bigtxt(p)
		}
		if strings.HasSuffix(p, ".txt") {
			fmt.Println(p)
			oldtxt, err := os.Open(p)
			if err != nil {
				panic(fmt.Errorf("文件%s打开失败", p))
			}
			defer oldtxt.Close()
			buff := make([]byte, 1024)
			reader := bufio.NewReader(oldtxt)
			for {
				n, err := reader.Read(buff)
				if err != nil && err != io.EOF {
					panic(err)
				}
				if n == 0 {
					break
				}
				writer.Write(buff[:n])
			}
		}
	}
	writer.Flush()
	return nil
}

创建文件/目录

os.Create(name string)//创建文件
os.Mkdir(name string, perm fs.FileMode)//创建目录
os.MkdirAll(path string, perm fs.FileMode)//增强版Mkdir,沿途的目录不存在时会一并创建
os.Rename(oldpath string, newpath string)//给文件或目录重命名,还可以实现move的功能
os.Remove(name string)//删除文件或目录,目录不为空时才能删除成功
os.RemoveAll(path string)//增强版Remove,所有子目录会递归删除

文件路径的操作

  • os.Getwd() (dir string, err error) 获取当前执行命令的目录
  • filepath.Join(elem ...string) string 拼接路径
  • filepath.Dir(path string) string 获取文件的目录名
  • filepath.Base(path string) string 获取文件的文件名
  • filepath.Abs(path string) (string, error) 获取文件的绝对路径
  • filepath.Split(path string) (dir, file string) 切割文件路径 返回目录名和文件名
  • filepath.Ext(path string) string 获取文件的后缀
  • os.Stat(name string) (FileInfo, error) 获取文件的状态信息
    • FileInfo.IsDir() 是否为文件夹
    • FileInfo.Name() 文件名
    • FileInfo.ModTime() 文件的修改时间
    • FileInfo.Mode() 文件的权限
    • FileInfo.Size() 文件的字节数
package main

import (
	"fmt"
	"os"
	"path/filepath"
)

func main() {
	pwd, _ := os.Getwd()          // 获取到当前目录,相当于python里的os.getcwd()
	fmt.Println("当前的操作路径为:", pwd) //当前的操作路径为: E:\Codes\GO\week5
	//文件路径拼接
	f1 := filepath.Join(pwd, "test", "1.txt")
	fmt.Println("文件的路径为:", f1) //文件的路径为: E:\Codes\GO\week5\test\1.txt
	//文件的目录名
	fmt.Println("文件的目录名:", filepath.Dir(f1)) //文件的目录名: E:\Codes\GO\week5\test
	//文件的文件名
	fmt.Println("文件的文件名:", filepath.Base(f1)) //文件的文件名: 1.txt
	//文件的绝对路径
	adspath, _ := filepath.Abs("evn/3.txt")
	fmt.Println("文件的绝对路径为:", adspath) //文件的绝对路径为: E:\Codes\GO\week5\evn\3.txt
	//拆分路径
	dirname, filename := filepath.Split(f1)
	fmt.Println("目录名为:", dirname, "文件名为", filename) //目录名为: E:\Codes\GO\week5\test\ 文件名为 1.txt
	//扩展名相关
	fmt.Println("f1的扩展名为:", filepath.Ext(f1)) //f1的扩展名为: .txt

	//通过os.Stat()函数返回的文件状态,如果有错误则根据错误状态来判断文件或者文件夹是否存在
	fileinfo, err := os.Stat(f1)
	if err != nil {
		fmt.Println(err.Error()) //如果文件不存在 CreateFile E:\Codes\GO\week5\test\1.txt: The system cannot find the path specified.
		if os.IsNotExist(err) {
			fmt.Println("file:", f1, " not exist!")
		}
	} else {
		fmt.Println(fileinfo.IsDir())   //判断是否为文件夹 //false
		fmt.Println(!fileinfo.IsDir())  //true
		fmt.Println(fileinfo.Name())    //获取文件的名字 //1.txt
		fmt.Println(fileinfo.ModTime()) //获取修改时间 返回的是个时间对象  //2022-09-30 16:30:59.0343772 +0800 CST
		fmt.Println(fileinfo.Mode())    //获取权限 //-rw-rw-rw-
		fmt.Println(fileinfo.Size())    //获取文件的字节长度  //61
	}
}

遍历目录

  • ioutil.ReadDir(dirname string) ([]fs.FileInfo, error) 获取文件信息的切片

从 Go 1.16 开始,现在由包 io 或包 os 提供相同的功能,并且在新代码中应该首选这些实现。

用法也是一样的

package main

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

func walk(path string) error {
	pathinfos, err := ioutil.ReadDir(path)
    if err != nil { //获取目录中的文件信息
		return fmt.Errorf("读取目录不成功")
	}
	for _, pathinfo := range pathinfos {
		path1 := filepath.Join(path, pathinfo.Name()) //通过filepath.Join连接父目录和当前目录
		if pathinfo.IsDir() {  //如果是目录,就递归子遍历
			if err := walk(path1); err != nil { 
				return err
			}
		} else {
			fmt.Println(path1)
		}
	}
	return nil
}

func main() {
	//获取当前执行命令的目录
	path, err := os.Getwd()
	if err != nil {
		panic(err)
	}
	walk(path)
}

默认的log输出到控制台。

log.Printf("%d+%d=%d\n", 3, 4, 3+4)
log.Println("Hello Golang")
log.Fatalln("Bye, the world") //日志输出后会执行os.Exit(1)

指定日志输出到文件

格式

log.New(out io.Writer, prefix string, flag int) *Logger

  • out 是打开的文件,用os.OpenFile()打开的一个可写文件
  • prefix 每条日志的前缀
  • flag 设置日志的格式
    • log.Ldate 日期格式的 2009/01/23
    • log.Lmicroseconds 精确到微秒的时间 01:23:23.123123
    • log.Ltime 精确到秒 01:23:23
    • log.Llongfile 完整文件名和行号 /a/b/c/d.go:23
    • log.Lshortfile 文件名和行号 d.go:23
    • log.LUTC 如果设置了Ldate或Ltime,则使用UTC而不是本地时区
    • log.Lmsgprefix 将“前缀”从行首移到消息之前
    • log.LstdFlags 相当于 Ldate | Ltime
logWriter := log.New(fout, "[BIZ_PREFIX]", log.Ldate|log.Lmicroseconds) //通过flag参数定义日志的格式
logWriter.Println("Hello Golang")

例子

package main

import (
	"fmt"
	"log"
	"os"
	"time"
)

var file, err = os.OpenFile("1.log", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0755)

func logs() {
	logger := log.New(file, "[abc]", log.Ldate|log.Lmicroseconds)
	logger.Println("第一行")
	logger.Println("第二行")
	logger.Println("第三行")
}

//关闭文件
func onexit() {
	if file != nil {
		fmt.Println("退出了")
		file.Close()
	}
}
func main() {
	defer func() {
        //异常退出的时候关闭文件
		if err := recover(); err != nil {
			onexit()
		}
	}()
	n := 0
    //循环打印日志
	for i := 0; i < 10; i++ {
		logs()
		time.Sleep(3 * time.Second)
        //模拟故障
		if i == 5 {
			_ = 1 / n
		}
	}
    //正常退出的时候关闭文件
	onexit()
}

调用系统命令

cmd_path, err := exec.LookPath(“df”) //查看系统命令所在的目录,确保命令已安装
cmd := exec.Command("df", "-h") //相当于命令df -h,注意Command的每一个参数都不能包含空格
output, err := cmd.Output() //cmd.Output()运行命令并获得其输出结果
cmd = exec.Command("rm", "./data/test.log")
cmd.Run() //如果不需要获得命令的输出,直接调用cmd.Run()即可
posted @ 2023-10-13 10:12  厚礼蝎  阅读(76)  评论(0编辑  收藏  举报