Go 1.19.4 文件读写操作-Day 14

news2024/9/21 22:42:16

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,会改变文件指针位置,实际使用时需要注意。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2092430.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

书生浦语-MindSearch

1.目的 利用SiliconCloud提供的免费Intern2.5-7B-Chat的API部署MindSearch。 2.过程 2.1 在GitHub上打开codespace主页 我们首先在GitHub上打开codespace&#xff0c;选择blank template。 然后打开一个Web端的vscode&#xff0c;将MindSearch进行clone mkdir -p /workspa…

二叉树详解(进阶)

目录 1. 二叉搜索树 1.1 基本概念 1.2 基本操作 1.3 性能分析 1.4 键值对 2. AVL树和红黑树 2.1 AVL树 2.2 红黑树 3. 红黑树模拟实现STL中的map与set 1. 二叉搜索树 1.1 基本概念 二叉搜索树&#xff08;BST&#xff0c;Binary Search Tree&#xff09;&#xff1a…

记录一次安装Studio卸载后再次安装反复打不开的问题

先说问题表现&#xff0c;低版本的安装后点击没反应&#xff0c;高版本的报错&#xff0c;如下图&#xff0c;反复卸载安装都没有用&#xff0c;网上也找了各种彻底卸载安卓Studio的方法也不行 Error occurred during initialization of VMagent library failed Agent OnLoad:…

【C++】unordered_set 容器的最全解析(什么是unordered_set?unordered_set的常用接口有那些?)

目录 一、前言 二、预备知识 &#x1f4a2;关联式容器&#x1f4a2; &#x1f4a2;键值对&#x1f4a2; &#x1f4a2;哈希结构的关联式容器&#x1f4a2; 三、unordered_set 详解 &#x1f525;unordered_set 的介绍 &#x1f525;unordered_set 的构造 &am…

解除 Excel 表格的文档保护全攻略

在日常工作和学习中&#xff0c;我们可能会遇到 Excel 表格被保护无法编辑的情况。别担心&#xff0c;今天就为大家分享几种解除 Excel 表格文档保护的方法。 一、导入腾讯文档 可以将受保护的 Excel 表格上传到腾讯文档。在部分情况下&#xff0c;腾讯文档会尝试自动解除表…

零基础国产GD32单片机编程入门(九)低功耗模式实战含源码

文章目录 一.概要二.GD32单片机低功耗基本介绍三.GD32单片机待机模式介绍四.待机低功耗例程实验五.工程源代码下载六.小结 一.概要 在生活中通过关掉用电器可以实现省电节能的目的&#xff0c;同样的道理单片机也可以通过这种方法实现降低功耗。单片机是由许多部件组成&#x…

ruoyi-vue-plus服务端打包报错的问题

对idea不熟&#xff0c;不知道在哪里输入打包命令&#xff0c;只会用手点击进行打包&#xff0c;然后就报错了 官方文档给的打包命令是 mvn clean package -D maven.test.skiptrue -P prod 从命令中可以看到跳过了maven测试&#xff0c;那么就要设置idea打包时跳过测试&…

k8s单master多node环境搭建-k8s版本低于1.24,容器运行时为docker

k8s 1.20.6单master多node环境搭建 1.环境规划2.初始化服务器1&#xff09;配置主机名2&#xff09;设置IP为静态IP3&#xff09;关闭selinux4&#xff09;配置主机hosts文件5&#xff09;配置三台主机之间免密登录6&#xff09;关闭交换分区swap&#xff0c;提升性能7&#xf…

【Python基础】字符串类型

本文收录于 《Python编程入门》专栏&#xff0c;从零基础开始&#xff0c;分享一些Python编程基础知识&#xff0c;欢迎关注&#xff0c;谢谢&#xff01; 文章目录 一、前言二、Python 字符串类型2.1 Python访问字符串中的值2.2 Python 转义字符2.3 Python 字符串运算符2.4 Py…

Bluetooth: gatt profile

Gatt 主要是描述了attribute的排列方式&#xff1b; Attribute caching 这个机制允许client只搜索一次server即可&#xff0c;当重连后不需要再搜索直接使用之前的。如果server的服务发生了变化&#xff0c;需要通过 service change indication 告诉client&#xff1b; client…

网优学习干货:2.6G仿真操作(2)

导入仿真区域图层 建立仿真站点组 设置仿真任务-结果图层和楼宇仿真高度 仿真结果统计-结果图层渲染 仿真结果统计-结果导出 目录 导入天线文件-导入方法与覆盖仿真相同&#xff0c;但天线文件需要包含PDSCH波束文件 将Beamforming天线添加到基站 如果在步骤④中没有找到Beamfo…

Web自动化测试实战--博客系统

&#x1f3a5; 个人主页&#xff1a;Dikz12&#x1f525;个人专栏&#xff1a;测试&#x1f4d5;格言&#xff1a;吾愚多不敏&#xff0c;而愿加学欢迎大家&#x1f44d;点赞✍评论⭐收藏 目录 1.项目效果展示 2.编写web测试用例 3.自动化测试脚本开发 3.1创建空项目 引…

构建大师:深入理解Linux下的Make和Makefile

引言 在软件开发的世界里&#xff0c;构建过程是一项繁琐而重要的任务。无论是简单的脚本还是复杂的软件项目&#xff0c;都需要一种方式来自动化编译、链接以及测试等过程。在Linux环境下&#xff0c;Make工具和它的配置文件——Makefile&#xff0c;成为了许多开发者构建项目…

计算机硬件的组成

目录 前言 计算机系统组成 计算机硬件的组成 1、控制器 2、运算器 3、主存储器 4、辅助存储器 5、输入设备 6、输出设备 最后 前言 计算机已成为不可或缺的工具。无论是个人电脑还是服务器集群&#xff0c;其背后都是由一系列硬件组件协同工作的结果。 本文讲介绍计…

ssrf攻击本地fastcgi漏洞复现

目录 环境&#xff1a;UbuntuNginxphp 代码 开始测试 查看 环境搭建 环境&#xff1a;UbuntuNginxphp 代码 <?php highlight_file(__FILE__); $url $_GET[url]; $curl curl_init($url);curl_setopt($ch,CURLOPT_URL,$url); curl_setopt($curl, CURLOPT_HEADER, 0…

滚雪球学MyBatis-Plus(02):环境准备

环境准备 本地开发环境参考如下&#xff1a; 开发工具&#xff1a;IntelliJ IDEA 2021.3.2JDK版本&#xff1a; JDK 1.8Spring Boot版本&#xff1a;2.3.1.RELEASEMaven版本&#xff1a;Apache Maven 3.8.2MySQL&#xff1a;5.6 前言 在上期内容中&#xff0c;我们系统地介绍了…

【多线程】设计模式之单例模式

&#x1f490;个人主页&#xff1a;初晴~ &#x1f4da;相关专栏&#xff1a;多线程 / javaEE初阶 一、什么是设计模式 设计模式好⽐象棋中的 "棋谱". 红⽅当头炮, ⿊⽅⻢来跳. 针对红⽅的⼀些⾛法, ⿊⽅应招的时候有⼀些固定的套路. 按照套路来⾛局势就不会吃亏. …

【微服务】接口的幂等性怎么设计?

一、什么是幂等&#xff1f; 幂等性&#xff1a;短时间内&#xff0c;对于相同输入的请求&#xff0c;无论进行多少次重复操作&#xff0c;都应该和单次调用的结果一致。 二、幂等问题产生的原因是什么&#xff1f;(或者说为什么需要实现幂等性?) 1、前端重复提交 在用户注…

高频Postman接口测试面试题

一、Postman在工作中使用流程是什么样的&#xff1f; 新建集合管理根据接口所属的模块&#xff0c;在集合中不同模块下编写接口测试用例处理接口之间的数据关联操作添加环境变量在tests tab下中增加断言调试接口&#xff0c;确保接口能被正常调用批量运行用例或者导出通过Newm…

STM32H750VBT6烧录源码无反应的问题

当烧录后出现这种情况下&#xff0c;点击魔术棒里面 Linker,勾选第一个方框后再次烧录即可。