Go语言学习23-文件操作——写文件

关于文件句柄的极简解释

1.只有windows中才有句柄,windows中的句柄是指针的指针,因为windows中对象的经常会在内存中移动(如进行垃圾回收后),所以地址值经常会变,所以就对外提供一个指针的指针即句柄给用户,句柄的地址是不会变的。

2.linux中是没有文件句柄的,只有文件描述符,只是大家习惯把它说成句柄。

3,linux中, 每当进程打开一个文件时,系统就为其分配一个唯一对应的整型文件描述符(从0开始),用来标识这个文件。linux 操作系统通常对每个进程能打开的文件数量有一个限制。默认是1024。

4、可以简单理解文件句柄就是一个对象。

写文件

之前读文件的时候,我们使用的是os.Open()这个方法,这个函数只能用来读文件,不能用来写文件。

如果要写文件,就需要用到os.OpenFile()这个方法

func OpenFile(name string,flag int,perm FileMode)(*File,error){
    ...
}

name:文件名称

flag:打开文件的模式。有以下几种 perm:文件权限,一个八进制数。r(读)04,w(写)02,x(执行)01。

模式 含义
os.O_WRONLY 只写
os.O_CREATE 创建文件
os.O_RDONLY 只读
os.O_RDWR 读写
os.O_TRUNC 清空
os.O_APPEND 追加

再写案例之前,我们先来理解一个问题,即二进制运算,位运算符——或

假设我想要输出吃饭睡觉打豆豆,都输出,如何操作?

package main

import "fmt"

const (
	eat   int = 4
	sleep int = 2
	dadd  int = 1
)

//左边为1 表示 吃饭	100	4
//中间为1 表示 睡觉	010	2
//右边为1 表示 打豆豆	001 1
func f(arg int) {
	fmt.Printf("%b\n", arg)
}
func main() {
	f(eat | dadd)         //101	或计算就是,二进制不同时,位数为1
	f(eat | sleep | dadd) //111	同理同上
}

文件写入操作案例

还记得读取文件吧,有三种写法,那么写入文件,同理也有三种写法。

第一种方式——

//打开文件写入内容
func main() {
	fileobj, err := os.OpenFile("./xx.txt", os.O_APPEND|os.O_CREATE, 0644)
    //file, err := os.OpenFile("xx.txt", os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666) 这里就是创建+清除+只写入
	if err != nil {
		fmt.Println("err", err)
		return
	}
//这里有两种小的写入方法,一个是Write,一个是WriteString
	fileobj.Write([]byte("虽然失败了,但是对后来的刘邦项羽起义打下了关键的基础。明修栈道,暗度陈仓。陈仓就是现在陕西宝鸡那里。项羽本来不想打了,划分界限,我做我的西楚霸王,给你封个汉王,你去做你的汉王吧!"))
	fileobj.WriteString("天若有情天亦老,人间正道是沧桑!")
	fileobj.Close()
}


//或者输出一个n也是可以的,看你的最终参数接收了
func main() {
	fileobj, err := os.OpenFile("./xx.txt", os.O_APPEND|os.O_CREATE, 0644)
	if err != nil {
		fmt.Println("err", err)
		return
	}
	n, err := fileobj.Write([]byte("虽然失败了,但是对后来的刘邦项羽起义打下了关键的基础。明修栈道,暗度陈仓。陈仓就是现在陕西宝鸡那里。项羽本来不想打了,划分界限,我做我的西楚霸王,你做你的沛公吧!"))
	if err != nil {
		fmt.Println("err:", err)
		return
	}
	fmt.Printf("已写入%d个字节!", n)
	// fileobj.WriteString("天若有情天亦老,人间正道是沧桑!")
	fileobj.Close()

}

第二种方式——bufio.NewWriter

os.OpenFile()打开的文件所生成的对象,传入bufio.NewWriter(fileobj)

image-20220304111703581

注意一点,就是bufio均为先创建一个缓冲区,即一个缓存,然后将内容先写入缓存中,最后将缓存放入硬盘中。所以这里就很容易出错,在后面一定要加上个

wr.Flush()!!!不然会无法写入。这就类似于PHP了,PHP的文件上传就是通过move_uploaded_file这个函数将缓存中的内容拷贝到文件中。

image-20220304112152458

func writedemo2() {
	fileobj, err := os.OpenFile("xx.txt", os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666)
	if err != nil {
		fmt.Println("err", err)
		return
	}
	defer fileobj.Close()
	wr := bufio.NewWriter(fileobj)
	wr.WriteString("holyshit!!!\n") //注意这里是写在缓存中的,而不是直接落盘的
	wr.Flush()                      //将缓存的内容写入文件
}
func main() {
	writedemo2()
}

image-20220304114445697

第三种方式——ioutil.WriteFile

func writedemo3() {
	str := "hellow bitch!"
	err := ioutil.WriteFile("./xxx.txt", []byte(str), 0666)
	if err != nil {
		fmt.Println("err:", err)
		return
	}
}

利用bufio获取用户输入

普通获取输入的方法

可以看到这里我们输入的内容一旦有空格,它就会输出一个,是因为Scanln它读到空白符(空格或者空行)就会停止。

image-20220304154648809

利用bufio

package main

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

//获取用户输入时有空格
func useScan() {
	var s string
	fmt.Println("请输入内容!")
	fmt.Scanln(&s)
	fmt.Printf("你输入的内容为:%s", s)
}
func usebufio() {
	var s string
	reader := bufio.NewReader(os.Stdin) //将标准输入创建成一个对象reader
	fmt.Println("请输入内容!")
	s, _ = reader.ReadString('\n') //对象reader的ReadString方法,一直读到换行符
	fmt.Printf("你输入的内容为:%s", s)
}
func main() {
	// useScan()
	usebufio()
}

image-20220304155659506

那么问题来了,我们既然可以将标准输入放入NewReader,那么是不是可以将我们的标准输入放入标准输出等,或者 将标准输出放入标准输入之类的。。

image-20220304160436363

总结

三种写方式代码总结

package main

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

//打开文件写入内容
func writedemo1() {
	fileobj, err := os.OpenFile("./xx.txt", os.O_APPEND|os.O_CREATE, 0644)
	if err != nil {
		fmt.Println("err", err)
		return
	}
	n, err := fileobj.Write([]byte("虽然失败了,但是对后来的刘邦项羽起义打下了关键的基础。明修栈道,暗度陈仓。陈仓就是现在陕西宝鸡那里。项羽本来不想打了,划分界限,我做我的西楚霸王,你做你的沛公吧!"))
	if err != nil {
		fmt.Println("err:", err)
		return
	}
	fmt.Printf("已写入%d个字节!", n)
	// fileobj.WriteString("天若有情天亦老,人间正道是沧桑!")
	fileobj.Close()
}
func writedemo2() {
	fileobj, err := os.OpenFile("xx.txt", os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666)
	if err != nil {
		fmt.Println("err", err)
		return
	}
	defer fileobj.Close()
	wr := bufio.NewWriter(fileobj)
	wr.WriteString("holyshit!!!\n") //注意这里是写在缓存中的,而不是直接落盘的
	wr.Flush()                      //将缓存的内容写入文件
}
func writedemo3() {
	str := "hellow bitch!"
	err := ioutil.WriteFile("./xxx.txt", []byte(str), 0666)
	if err != nil {
		fmt.Println("err:", err)
		return
	}
}
func main() {
	// writedemo1()
	// writedemo2()
	writedemo3()

}

练习

copyFile

借助io.Copy()实现一个拷贝文件函数。

// CopyFile 拷贝文件函数
func CopyFile(dstName, srcName string) (written int64, err error) {
	// 以读方式打开源文件
	src, err := os.Open(srcName)
	if err != nil {
		fmt.Printf("open %s failed, err:%v.\n", srcName, err)
		return
	}
	defer src.Close()
	// 以写|创建的方式打开目标文件
	dst, err := os.OpenFile(dstName, os.O_WRONLY|os.O_CREATE, 0644)
	if err != nil {
		fmt.Printf("open %s failed, err:%v.\n", dstName, err)
		return
	}
	defer dst.Close()
	return io.Copy(dst, src) //调用io.Copy()拷贝内容
}
func main() {
	_, err := CopyFile("dst.txt", "src.txt")
	if err != nil {
		fmt.Println("copy file failed, err:", err)
		return
	}
	fmt.Println("copy done!")
}

实现一个cat命令

使用文件操作相关知识,模拟实现linux平台cat命令的功能。

package main

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

// cat命令实现
func cat(r *bufio.Reader) {
	for {
		buf, err := r.ReadBytes('\n') //注意是字符
		if err == io.EOF {
			// 退出之前将已读到的内容输出
			fmt.Fprintf(os.Stdout, "%s", buf)
			break
		}
		fmt.Fprintf(os.Stdout, "%s", buf)
	}
}

func main() {
	flag.Parse() // 解析命令行参数
	if flag.NArg() == 0 {
		// 如果没有参数默认从标准输入读取内容
		cat(bufio.NewReader(os.Stdin))
	}
	// 依次读取每个指定文件的内容并打印到终端
	for i := 0; i < flag.NArg(); i++ {
		f, err := os.Open(flag.Arg(i))
		if err != nil {
			fmt.Fprintf(os.Stdout, "reading from %s failed, err:%v\n", flag.Arg(i), err)
			continue
		}
		cat(bufio.NewReader(f))
	}
}
posted @ 2022-03-04 11:38  谨言慎行啊  阅读(163)  评论(0)    收藏  举报