Go 1.19.4 文件读写操作-Day 14

1. 文件读写操作

在我们对一个文件进行读写操作前,有一个必做步骤,那就是要先打开文件。

打开文件主要使用os模块的 Open 和 OpenFile 。

  • Open:适合读。
  • OpenFile:适合读写。

2. 打开文件

2.1 Open

作用:

        以只读方式打开文件,权限使用文件默认权限。

下面是Open的源码:

func Open(name string) (*File, error) {
    // O_RDONLY:只读方式打开文件,文件必须存在。
    // 0:在 os.OpenFile 函数中,传入0作为权限参数意味着使用默认的文件权限。
	return OpenFile(name, O_RDONLY, 0)
}

语法:

        func os.Open(name string) (*os.File, error)

参数含义:

        name string:文件路径变量。

        *os.File:表示打开的文件对应的指针。

        error:可能发生的错误,只要有异常,error类型就会被赋值。

package main

import (
	"fmt"
	"os"
)

func main() {
	filename := "D:/个人/学习/Go/文件与目录操作/test.txt" // 文件内容:abc
	f, err := os.Open(filename)
	if err != nil {
		panic(err)
	}
	// 用完后一定要关闭文件,释放文件描述符
	defer f.Close() // 可结合defer,让文件关闭操作,最后一定会执行
	fmt.Printf("f的类型=%T\nf的值=%[1]v\nf的值=%#[1]v", f)}
==========调试结果==========
f的类型=*os.File
f的值=&{0xc000004a00}
f的值=&os.File{file:(*os.file)(0xc000004a00)}

上述代码打开文件后得到一个文件操作句柄(&{0xc000004a00}),这是os.File类型的指针,使用它就可以操作文件了。

2.2 Read

作用:

        Read 方法是 os.File 结构体的一个方法,用于从文件中读取数据。

        Read是按字节读取的文件,在文件结束时,Read返回0,io.EOF。

      

语法:

        func (r *File) Read(b []byte) (n int, err error)

参数:

        b []byte:一个字节切片,用来存储读取的数据。

        n int:实际读取到的字节数。

        err error:如果有错误发生,返回错误信息。

注意事项:使用Read方法的前提,一定是已经用Open方法打开文件,并获得了文件指针。

package main

import (
	"fmt"
	"os"
)

func main() {
	filename := "D:/个人/学习/Go/文件与目录操作/test.txt"
	f, err := os.Open(filename)
	if err != nil {
		panic(err)
	}
	// 用完后一定要关闭文件,释放文件描述符
	defer fmt.Println("文件关闭完成")
	defer f.Close() // 可结合defer,确保文件最后一定会关闭。
	defer fmt.Println("开始关闭文件")

	fmt.Println("返回的文件指针(文件句柄):", f)

	// 读取文件
    // 因为Read方法,是需要一个字节切片来作为参数的,所以必须要先定义一个字节切片。
    // 正常读取,都是k的整数倍,如1024、2048等。这里写3,只是为了测试
	buffer := make([]byte, 3) // 创建一个大小为 2 字节的缓冲区(字节切片)
	n, err := f.Read(buffer)  // 从文件中读取数据到缓冲区,n表示成功读取了n个字节
	if err != nil {
		panic(err)
	} else {
		fmt.Printf("成功读取的字节数:%d\n读取缓冲区:%d\n字节转换字符串:%s\n", n, buffer, string(buffer))
	}
}
==========调试结果==========
返回的文件指针(文件句柄): &{0xc000148780}
成功读取的字节数:3
读取缓冲区:[97 98 99]
字节转换字符串:abc
开始关闭文件
文件关闭完成

2.2.1 循环读取文件

package main

import (
	"fmt"
	"os"
)

func main() {
	filename := "D:/个人/学习/Go/文件与目录操作/test.txt"
	f, err := os.Open(filename)
	if err != nil {
		panic(err)
	}

	defer fmt.Println("文件关闭完成")
	defer f.Close()
	defer fmt.Println("开始关闭文件")

	fmt.Println("返回的文件指针(文件句柄):", f)

	// 读取文件
	buffer := make([]byte, 1)

    // 无限循环读取,每次读取1个字节
	for {
		n, err := f.Read(buffer)
		if err != nil {
			// panic(err)
			fmt.Println(err)
		} else {
			fmt.Printf("成功读取的字节数:%d\n读取缓冲区:%d\n字节转换字符串:%s\n", n, buffer, string(buffer))
			fmt.Println("==================")
		}
	}

}

通过断点执行发现,文件内容读取完毕后,会返回一个EOF,表示已无内容可读取,字节数也为0。

可以在判断中加上painc或break,捕获到异常就停止运行。

package main

import (
	"fmt"
	"os"
)

func main() {
	filename := "D:/个人/学习/Go/文件与目录操作/test.txt"
	f, err := os.Open(filename)
	if err != nil {
		panic(err)
	}

	defer fmt.Println("文件关闭完成")
	defer f.Close()
	defer fmt.Println("开始关闭文件")

	fmt.Println("返回的文件指针(文件句柄):", f)

	// 读取文件
	buffer := make([]byte, 1)
	for {
		n, err := f.Read(buffer)
		if err != nil {
			fmt.Println(err)
			break
			// panic(err)
		} else {
			fmt.Printf("成功读取的字节数:%d\n读取缓冲区:%d\n字节转换字符串:%s\n", n, buffer, string(buffer))
			fmt.Println("==================")
		}
	}
}
==========调试结果==========
返回的文件指针(文件句柄): &{0xc0000cc780}
成功读取的字节数:1
读取缓冲区:[97]
字节转换字符串:a
==================
成功读取的字节数:1
读取缓冲区:[98]
字节转换字符串:b
==================
成功读取的字节数:1
读取缓冲区:[99]
字节转换字符串:c
==================
EOF
开始关闭文件
文件关闭完成

2.2.2 buffer越界

上述代码还有一个问题,如果读取的字节数大于buffer中剩余可读的字节数,会出现下面这种问题:

这个buffer越界是这样的,如上述代码,每次读取2字节,第一次读取ab放到buferr中,第二次读取到c和没有存储数据的空字节,但是由于第二次读取的结果,是覆盖上一次的结果的,所以我们看到的是cb。

解决办法:buffer[:n],从0开始到n-1,就能避免越界。

2.2.3 文件读取总结

  1. 读取完毕后,一定要关闭文件释放文件描述符,如:f.Close()或defer f.Close()。
  2. 读取文件异常时,一定要panic或break。
  3. 读取文件时,正确的做法应该是buffer[:n],防止buffer越界,特别是string(buffer[:n])。
  4. 建议每次读取大小为KB的整数倍,特别是大文件,如1024字节、2048字节等。

 3. 定位(Seek)

文件是什么?是二进制有序序列,逻辑上理解为字节数组。
也就是说,文件读写都会有一个针,指向二进制序列的索引,正常都是从前向后移动。

那么如何定位到某一个指定的字节呢?这里就要介绍下Seek(定位or寻找)。

3.1 Seek函数介绍

作用:

        在Go语言中,Seek 函数是用来设置文件读写操作中的偏移量的。Seek 函数定义在 os 包的 File 结构体中,其原型如下:

语法:

        func (f *File) Seek(offset int64, whence int) (ret int64, err error)

参数:

offset:相对偏移量。

whence:决定这个偏移量是相对于文件的哪个位置。并且whence还有3个选项:

  • 0 表示相对文件开头 (io.SeekStart),offset 只能正,负报错。
  • 1 表示相对当前位置 (io.SeekCurrent),offset 可正可负,但是负指针不能超左边界。
  • 2 表示相对文件结尾 (io.SeekEnd),offset 可正可负,但是负指针不能超左边界,正数的话,会读不到内容,但不会报错。

常用:

  • Seek(0, 0),指针拉到文件开头读写。
  • Seek(0, 2),指针拉到文件结尾读写。

3.2 示例

3.2.1 二次读取文件

注意这里调整了文件内容为:abcdef

package main

import (
	"fmt"
	"os"
)

func main() {
	filename := "D:/个人/学习/Go/文件与目录操作/test.txt" // 文件内容:abcdef
	f, err := os.Open(filename)
	if err != nil {
		panic(err)
	}

	defer fmt.Println("文件关闭完成")
	defer f.Close()
	defer fmt.Println("开始关闭文件")

	fmt.Println("返回的文件指针(文件句柄):", f)

	// 读取文件
	buffer := make([]byte, 2)// len=2, cap=2
	for {
		n, err := f.Read(buffer)
		if err != nil {
			fmt.Println(err)
			break
			// panic(err)
		} else {
			fmt.Printf("成功读取的字节数:%d\n读取缓冲区:%d\n字节转换字符串:%s\n", n, buffer[:n], string(buffer[:n]))
			fmt.Println("==================")
		}
	}
	fmt.Println("==================") // 新增内容
	n, err := f.Read(buffer)
	fmt.Println(n, err)
}
==========调试结果==========
省略部分输出
==================
0 EOF
开始关闭文件
文件关闭完成

从上面的输出结果可以看到,当我们二次读取一个已经被读取完毕的文件时,只能读取到0字节和EOF,这是因为此时的指针已经指向了文件结尾,已经没有字节可以读取了。

如果想继续读取有效内容,可以调整指针,也就是调整偏移量。

3.2.1.1 调整offset为负数,二次读取文件

错误的方式

package main

import (
	"fmt"
	"os"
)

func main() {
	filename := "D:/个人/学习/Go/文件与目录操作/test.txt"
	f, err := os.Open(filename)
	if err != nil {
		panic(err)
	}

	defer fmt.Println("文件关闭完成")
	defer f.Close()
	defer fmt.Println("开始关闭文件")

	fmt.Println("返回的文件指针(文件句柄):", f)

	// 读取文件
	buffer := make([]byte, 2)
	for {
		n, err := f.Read(buffer)
		if err != nil {
			fmt.Println(err)
			break
			// panic(err)
		} else {
			fmt.Printf("成功读取的字节数:%d\n读取缓冲区:%d\n字节转换字符串:%s\n", n, buffer[:n], string(buffer[:n]))
			fmt.Println("==================")
		}
	}
	fmt.Println("=======Seek=======")

	ret, err := f.Seek(-2, 0) // 基于文件起始位置,往左移动2位。错误的方式
	fmt.Println(ret, err)
}

==========调试结果==========
省略部分输出
=======Seek=======
0 seek D:/个人/学习/Go/文件与目录操作/test.txt: An attempt was made to move the file pointer before the beginning of the file.
开始关闭文件
文件关闭完成

可以看到,上述代码执行报错了,为什么?

首先for循环中,每次读取2个字节(buffer切片,长度容量都是指定为2了),后续每次读取的内容,都会覆盖上一次的内容,所以之前最终读取出来了cb,此时的指针是指在c这个位置的。

那么f.Seek(-2, 0),是说基于c这个位置(相当于就是文件内容起始位置),往左再偏移2位,从文件内容开头往左偏移,那肯定不行啊。

3.2.1.2 调整offset为正数,二次读取文件

这里把offset调整为了20,依然能够给正常执行,说明正向超界不会报错,虽然buffer切片的长度和容量都为2。

那这样做有啥意义呢?请继续往下看。

从上述结果来看,从文件末尾,正向读取的话,不会超界,最多没有内容。

3.2.1.3 从文件末尾往前读

3.3 Seek使用注意事项

 在文件读写的过程中,不要随意使用Seek,建议文件内容EOF后,再使用Seek调整指针。

3.4 ReadAt

之前如果读取文件时,想调整偏移量,必须结合Seek和Read才行。

而ReadAt允许指定一个偏移量,然后从这个位置开始读取一定数量的字节到一个给定的缓冲区。

ReadAt相当于是从文件开头,开始偏移,但它不会影响当前文件指针。

但是Read和Write都会影响文件指针。

4. 自带缓冲读取(bufio)

文件使用Read读取,非常底层,操作起来很不方便,每次还要先创建一个buffer,来存储从磁盘中读取出来的字节序列,且bufio会自带一个指针,所以使用seek调整指针位置,是没用的。

Go语言提供了bufio包实现了对文件的二进制或文本处理的方法。

bufio 是 Go 语言标准库中用于缓冲输入输出的包。它提供了数据读写的缓冲机制,可以减少系统调用的次数,从而提高程序的效率。

4.1 bufio.NewReader

bufio.NewReader 是 Go 语言标准库中 bufio 包的一个函数,它接收一个 io.Reader 接口类型的参数,并返回一个 *bufio.Reader 类型的值。这个返回的 *bufio.Reader 包含了一个缓冲区,可以减少对底层输入流的直接调用次数。

这里的io.Reader 接口类型的参数,就是我们上边的f.Read(buffer),任何类型只要实现了Read(p []byte) 方法,就可以说它实现了 io.Reader 接口。

4.1.1 Read

package main

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

func main() {
	// 定义文件路径
	filename := "D:/个人/学习/Go/文件与目录操作/test.txt" // 文件内容:abcdef

	// 打开文件
	f, err := os.Open(filename)
	if err != nil {
		panic(err)
	}

	defer fmt.Println("文件关闭完成")
	defer f.Close()
	defer fmt.Println("开始关闭文件")

	// 读取文件
	fmt.Println("=========NewReader=========")
	reader := bufio.NewReader(f) // 这样就把f包装成了bufio的Reader
	b1 := make([]byte, 3)        // 新建用于存储文件内容的字节切片
	n, err := reader.Read(b1)
	fmt.Println(n, err, b1, string(b1[:n]))
}
=========NewReader=========
3 <nil> [97 98 99] abc
开始关闭文件
文件关闭完成

4.1.2 ReadByte(按单字节读取)

ReadByte读取并返回单个字节。如果没有可用的字节,则返回错误。

package main

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

func main() {
	// 定义文件路径
	filename := "D:/个人/学习/Go/文件与目录操作/test.txt" // 文件内容:abcdef

	// 打开文件
	f, err := os.Open(filename)
	if err != nil {
		panic(err)
	}

	defer fmt.Println("文件关闭完成")
	defer f.Close()
	defer fmt.Println("开始关闭文件")

	// 读取文件
	reader := bufio.NewReader(f) // 这样就把f包装成了bufio的Reader
	fmt.Println("=========ReadByte=========")
	b, err2 := reader.ReadByte()
	fmt.Println(b, err2, string(b))
}
=========ReadByte=========
97 <nil> a
开始关闭文件
文件关闭完成

4.1.3 ReadBytes(指定分隔符读取)

func (*bufio.Reader).ReadBytes(delim byte) ([]byte, error)

ReadBytes 函数读取输入直到遇到 delim(分隔符 ,返回一个包含数据直到(包括)分隔符的切片。

如果 ReadBytes 在找到分隔符之前遇到错误,它将返回读取的数据和错误本身(通常是 io.EOF)。

如果返回的数据不以 delim(分隔符 结尾,ReadBytes 将返回 err != nil。对于简单用途,使用 Scanner 可能更方便。

package main

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

func main() {
	// 定义文件路径
	filename := "D:/个人/学习/Go/文件与目录操作/test.txt" // 文件内容:abcdef

	// 打开文件
	f, err := os.Open(filename)
	if err != nil {
		panic(err)
	}

	defer fmt.Println("文件关闭完成")
	defer f.Close()
	defer fmt.Println("开始关闭文件")

	// 读取文件
	reader := bufio.NewReader(f) // 这样就把f包装成了bufio的Reader
	fmt.Println("=========ReadBytes=========")
	b, err2 := reader.ReadBytes('d') // 我文件中的内容:abcdef测试,这里我用d作为分隔符
	fmt.Printf("返回的字节切片=%v\n切片转字符串=%s\nerr=%v\n", b, string(b), err2)

}
=========ReadBytes=========
返回的字节切片=[97 98 99 100]
切片转字符串=abcd
err=<nil>
开始关闭文件
文件关闭完成

4.1.4 ReadRune(读取rune字符)

作用:

        从输入流中读取单个 UTF-8 编码的 Unicode 字符。

         注意:使用rune读取,一定要保证文件的编码是UTF-8。

语法:

        func (*bufio.Reader).ReadRune() (r rune, size int, err error)

  • r rune:返回一个rune类型的字符。
  • size int:返回值的字节大小。
package main

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

func main() {
	// 定义文件路径
	filename := "D:/个人/学习/Go/文件与目录操作/test.txt" // 文件内容:abcdef

	// 打开文件
	f, err := os.Open(filename)
	if err != nil {
		panic(err)
	}

	defer fmt.Println("文件关闭完成")
	defer f.Close()
	defer fmt.Println("开始关闭文件")

	// 读取文件
	reader := bufio.NewReader(f) // 这样就把f包装成了bufio的Reader
	fmt.Println("=========ReadRune=========")
	r, size, err2 := reader.ReadRune()
	fmt.Printf("rune字符=%v\nrune字符大小=%d\nrune转str=%s\nerr=%v\n", r, size, string(r), err2)
}
=========ReadRune=========
rune字符=97
rune字符大小=1
rune转str=a
err=<nil>
开始关闭文件
文件关闭完成

4.1.5 ReadSlice(指定分隔符读取)

作用:

        简单来说,就是指定一个byte分隔符,然后ReadSlice读取到第一个分隔符为止,返回读取到的切片,包含了分隔符和它之前的数据。

语法:

        func (*bufio.Reader).ReadSlice(delim byte) (line []byte, err error)

  • 参数 delim byte:分隔符
  • 返回值 line []byte:读取到的数据切片
  • 返回值 err error:错误
package main

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

func main() {
	// 定义文件路径
	filename := "D:/个人/学习/Go/文件与目录操作/test.txt"

	// 打开文件
	f, err := os.Open(filename)
	if err != nil {
		panic(err)
	}

	defer fmt.Println("文件关闭完成")
	defer f.Close()
	defer fmt.Println("开始关闭文件")

	// 读取文件
	reader := bufio.NewReader(f)
	fmt.Println("=========ReadSlice=========")
	line, err := reader.ReadSlice('\n') // 文件内容是:abcdef\n测试
	fmt.Println(line, string(line), err)
}
=========ReadSlice=========
[97 98 99 100 101 102 13 10] abcdef
 <nil>
开始关闭文件
文件关闭完成

上述代码指定了分隔符为'\n',然后把截止到\n(包含\n)的所有数据都读出来了。

4.1.6 ReadString(指定分隔符读取,返回string)

作用:

        ReadString读取,直到输入中第一次出现分隔符,返回一个字符串,其中包含分隔符之前的数据并包括分隔符。

        如果ReadString在找到分隔符之前遇到错误,它将返回在错误之前读取的数据和错误本身(通常是io.EOF)。

        ReadString返回err != nil当且仅当返回的数据不以delim结尾时。

语法:

        func (*bufio.Reader).ReadString(delim byte) (string, error)

package main

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

func main() {
	// 定义文件路径
	filename := "D:/个人/学习/Go/文件与目录操作/test.txt"

	// 打开文件
	f, err := os.Open(filename)
	if err != nil {
		panic(err)
	}

	defer fmt.Println("文件关闭完成")
	defer f.Close()
	defer fmt.Println("开始关闭文件")

	// 读取文件
	reader := bufio.NewReader(f)
	fmt.Println("=========ReadString=========")
	s, err := reader.ReadString('\n') // 文件内容是:abcdef\n测试
	fmt.Println(s, err)
}
=========ReadString=========
abcdef
 <nil> // 注意这里是包含了\n的
开始关闭文件
文件关闭完成

5. flag

5.1 基本介绍

        flag 是一个非常有用的包,它允许你定义命令行参数,这样用户可以通过命令行来控制程序的行为。

        flag包提供了一组函数和类型,让你可以定义命令行参数,比如开关选项、字符串、整数等。这些参数可以在程序启动时通过命令行指定。

5.2 可使用的模式

O_RDONLY:只读打开文件,用的较少,因为Open方法用的就是它。

O_WRONLY:只写

O_RDWR:读写

注意上面这三个,是互斥的,不可以同时使用。

O_APPEND:追加写入

O_CREATE:文件不存在则创建,存在就不管。

O_EXCL:文件存在则报错。配合O_CREATE使用,如果创建的文件已存在就报错。
O_SYNC:同步IO,等待上一次IO完成。
O_TRUNC:对于可写的文件,打开时清空内容。

6. 常用文件操作

这里结合上面的flag,就可以进行文件读写操作了。

// 只写打开文件,文件必须存在。
flag := os.O_WRONLY

// 这里有2层含义:
// (1)只写权限打开文件,指针在文件头部,写入的内容会覆盖后面存在的内容。
// (2)如果文件不存在,先创建,如果文件存在,不作操作。
flag = os.O_WRONLY | os.O_CREATE

// 2层含义:
// (1)只写权限打开文件,指针在文件头部,写入的内容会覆盖后面存在的内容。
// (2)如果文件不存在,先创建。如果文件存在,先把原有内容清空,再从头写入内容。
flag = os.O_WRONLY | os.O_CREATE | os.O_TRUNC

// 这里有2层含义:
// (1)只写权限打开文件,文件不存在就先创建,指针在文件头部。
// (2)APPEND调整文件指针到文件末尾,新写入的内容在文件尾部追加进去。
flag = os.O_WRONLY | os.O_APPEND | os.O_CREATE

// 文件存在就报错
// 注意:不能单独使用,必须配合os.O_CREATE
flag = os.O_EXCL

// 只写权限打开文件,如果文件存在,则报错。文件不存在就创建文件,并从头开始写入。
flag = os.O_WRONLY | os.O_EXCL | os.O_CREATE

// 文件可读写(从文件头部开始),但要求文件存在
flag = os.O_RDWR

6.1 写入文件

6.1.1 WriteString

package main

import (
	"fmt"
	"os"
)

func main() {
	filename := "D:/个人/学习/Go/文件与目录操作/test.txt"

	// 读写方式打开文件
	f, err := os.OpenFile(filename, os.O_RDWR, 0) // 0表示使用系统默认权限
	if err != nil {
		panic(err)
	}
	defer fmt.Println("文件关闭完成")
	defer f.Close()
	defer fmt.Println("开始关闭文件")

	// flag操作
	// 读取文件
	buffer := make([]byte, 3)
	n2, err2 := f.Read(buffer)
	fmt.Println("读取文件:", n2, string(buffer[:n2]), err2)

	// 写入文件
	f.WriteString("rst") // 写入缓冲区
    f.Sync() // 刷新缓冲中的内容到磁盘
}
==========调试结果==========
读取文件: 3 abc <nil>
开始关闭文件
文件关闭完成

上述代码,使用writestring写入了一个rst,如下图:

这里可以看到,WriteString写入的rst居然在abc的后面,为什么?

由于没有指定写入的位置,WriteString 方法将会把字符串写入到文件的当前读取/写入位置。

并且如果在读取操作之后没有移动文件的读写指针,那么写入的内容将会覆盖掉原来的位置,而不是追加到文件的末尾。

使用WriteString写入前,一定要注意指针的位置。

默认读、写打开,指针位置一定是在文件的开头,也就是索引0的位置。

6.1.2 flag使用总结

  1. O_WRONLY:只读,从头读,文件要存在
  2. O_WRONLY:只写,从头写,文件要存在。如果文件已存在有内容,从头覆盖
  3. O_CREATE | O_TRUNC:没有文件创建新文件,从头写;有文件清空内容从头写
  4. O_APPEND:追加写,文件要存在
  5. O_CREATE:文件存在,从头写;文件不存在创建新文件,从头写(单纯的create不含指针操作)
  6. O_EXCL | O_CREATE:文件不存在创建新文件,从头写;文件存在报错
  7. O_RDWR:既能读又能写,从头开始

6.2 带缓冲读写

package main

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

func main() {
	// 文件内容:abcdef测试
	filename := "D:/个人/学习/Go/文件与目录操作/test.txt"

	// 提前配置flag
	// 读写方式打开文件,如文件不存在,就创建,如果存在,就先清空内容。指针在文件头部。
	flag := os.O_RDWR | os.O_CREATE | os.O_TRUNC

	// os.ModePerm默认权限=0777,只影响新建文件
	f, err := os.OpenFile(filename, flag, os.ModePerm)
    //上面的openfile,还可以用create替代,create内部的flag设置和我们的flag配置一摸一样。
    //f, err := os.Create(filename)


	if err == nil {
		defer f.Close()

		// 定义读写打开文件
		r := bufio.NewReader(f)
		w := bufio.NewWriter(f)

		// 写入内容
		w.WriteString("0123456789\n")
		w.WriteString("abc\n")

		// 刷新缓冲区中的内容到磁盘
		w.Flush()

		fmt.Println("==========分割线==========")

		// 经过上面的写入操作,此时的指针已经到了文件末尾,所以需要调整指针到文件开头
		f.Seek(0, 0)

		// 读取刚刚写入的内容,用换行符做分隔符。
		fmt.Println(r.ReadString('\n'))
		fmt.Println(r.ReadString('\n'))
	} else {
		fmt.Println("文件打开异常:", err)
	}

}
==========分割线==========
0123456789
 <nil>
abc
 <nil>

6.3 文件写入总结

更加推荐bufio这种自带缓冲的方式,更加简单。

注意:追加和清空这俩flag,会改变文件指针位置,实际使用时需要注意。

  • 7
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值