Go语言IO模式

Go语言IO模式

IO 操作是我们在编程中不可避免会遇到的,Go语言的 io 包中提供了相关的接口,定义了相应的规范,不同的数

据类型可以根据规范去实现相应的方法,提供更加丰富的功能。

本文主要介绍常见的 IO (输入和输出)模式,以及如何在应用程序中使用 Go I/O API。

IO 包官方文档:https://pkg.go.dev/io

1、IO包核心接口

1.1 Reader

io.Reader接口定义了 Read 方法,用于读取数据到字节数组中:

  • 入参:字节数组 p,会将数据读入到 p 中
  • 返回值:本次读取的字节数 n,以及遇到的错误 err
type Reader interface {
	Read(p []byte) (n int, err error)
}

1.2 Writer

io.Writer接口定义了 Write 方法,用于写数据到文件中

  • 入参:字节数组 p,会将 p 中的数据写入到文件中
  • 返回值:成功写入完成的字节数 n,以及遇到的错误 err
type Writer interface {
	Write(p []byte) (n int, err error)
}

1.3 Closer

io.Closer接口定义了 Close 方法,该方法用于关闭连接。

type Closer interface {
	Close() error
}
Se

第一次调用该方法后,再次调用该方法应该产生什么行为,该接口没有定义,依赖实现方法自定义。

1.4 Seeker

io.Seeker接口定义了 Seek 方法,该方法用于指定下次读取或者写入时的偏移量

  • 入参:计算新偏移量的起始值 whence, 基于whence的偏移量offset
  • 返回值:基于 whence 和 offset 计算后新的偏移量值,以及可能产生的错误
type Seeker interface {
	Seek(offset int64, whence int) (int64, error)
}

io包中定义了如下三种 whence:

const (
	SeekStart   = 0 // 基于文件开始位置
	SeekCurrent = 1 // 基于当前偏移量 
	SeekEnd     = 2 // 基于文件结束位置
)

2、IO包类型分类

2.1 基础类型

例如 Reader、Writer、Closer、ReaderAt、WriterAt、Seeker、ByteReader、ByteWriter、RuneReader、

StringWriter 等。

2.2 组合类型

在go语言中,可以利用接口的组合,来囊括其他接口中的方法,类似于定义了一个父接口,可以包含多个子接

口。如果一个 struct 实现了所有子接口的方法,也就相当于实现了父接口。小接口 + 接口组合的方式,很大程度

上增加了程序的灵活性,在我们自己业务开发过程中,可以借鉴这种做法。

使用的语法糖则是 Go 的匿名字段的用法。

针对上面四个最小粒度的接口,io包定义了如下几种组合接口:

// ReadWriter 是 Read 和 Write 方法的组合
type ReadWriter interface {
	Reader
	Writer
}

// ReadCloser 是 Read 和 Close 方法的组合
type ReadCloser interface {
	Reader
	Closer
}

// WriteCloser 是 Write 和 Close 方法的组合
type WriteCloser interface {
	Writer
	Closer
}

// ReadWriteCloser 是 Read、Write 和 Close 方法的组合
type ReadWriteCloser interface {
	Reader
	Writer
	Closer
}

// ReadSeeker 是 Read 和 Seek 方法的组合
type ReadSeeker interface {
	Reader
	Seeker
}

// WriteSeeker 是 Write 和 Seek 方法的组合
type WriteSeeker interface {
	Writer
	Seeker
}

// ReadWriteSeeker 是 Read、Write 和 Seek 方法的组合
type ReadWriteSeeker interface {
	Reader
	Writer
	Seeker
}

2.3 进阶类型

一般是在基础接口之上,增加了一些额外的实现。

例如 TeeReader、LimitReader、SectionReader、MultiReader、MultiWriter、PipeReader、PipeWriter 等。

3、IO包通用的函数

3.1 Cpoy

把一个 Reader 读出来,写到 Writer 里去,直到其中一方出错为止 (比如最常见的,读端出现 EOF )。

3.2 CopyN

这个和 Copy 一样,读 Reader,写 Writer,直到出错为止。但是 CopyN 比 Copy 多了一个结束条件:数据的拷

贝绝对不会超过 N 个;

3.3 CopyBuffer

这个也是个拷贝实现,和 Copy,CopyN 本质无差异。这个能让用户指定使用多大的 Buffer 内存,这个可以让用

户能根据实际情况优化性能,比如大文件拷贝的话,可以考虑使用大一点的 buffer,提高效率 (1G 的文件拷贝,

它也是分了无数次的读写完成的,比如用 1M 的内存 buffer,不停的搬运,搬运 1024 次,才算完)。

4、io.Reader及其进阶的应用

4.1 读取文件

读取文件是最常见的使用 io.reader 的方式之一,以下的代码示例演示了如何利用 io.reader 读取本地的文件数

据。

package main

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

func readFromLocalFile(filePath string) {
	file, err := os.Open(filePath)
	if err != nil {
		log.Fatal(err)
	}
	defer file.Close()
	buffer := make([]byte, 1024)
	for {
		n, err := file.Read(buffer)
		if err != nil && err != io.EOF {
			log.Fatal(err)
		}
		if n == 0 {
			break
		}
		fmt.Print(string(buffer[:n]))
	}
}

func main(){
	readFromLocalFile("./files/a.txt")
}
# 程序输出
a1
a2
a3
a4
a5

4.2 读取网络数据

另外一个常见的使用 io.reader 的场景就是读取网络数据。比如,我们可以通过 HttpClient 从一个 http 链接上面

读取数据。以下的代码演示了如何利用 io.reader 进行网络读取。

package main

import (
	"fmt"
	"io"
	"log"
	"net/http"
)

func readFromWeb(url string) {
	resp, err := http.Get(url)
	if err != nil {
		log.Fatal(err)
	}
	defer resp.Body.Close()
	buffer := make([]byte, 1024)
	for {
		n, err := resp.Body.Read(buffer)
		if err != nil && err != io.EOF {
			log.Fatal(err)
		}
		if n == 0 {
			break
		}
		fmt.Print(string(buffer[:n]))
	}
}

func main(){
	readFromWeb("http://www.baidu.com")
}

4.3 读取进程输出

有时候我们需要使用 Go启动一个进程,并且读取该进程的输出。这时候,我们可以使用 io.reader 接口去读取进

程的输出流。以下的代码演示了如何使用 io.reader 读取进程的输出流。

package main

import (
	"fmt"
	"io"
	"log"
	"os/exec"
)

func readFromCommand(cmd *exec.Cmd) {
	stdout, err := cmd.StdoutPipe()
	if err != nil {
		log.Fatal(err)
	}
	if err := cmd.Start(); err != nil {
		log.Fatal(err)
	}
	buffer := make([]byte, 1024)
	for {
		n, err := stdout.Read(buffer)
		if err != nil && err != io.EOF {
			log.Fatal(err)
		}
		if n == 0 {
			break
		}
		fmt.Print(string(buffer[:n]))
	}
}

func main() {
	readFromCommand(exec.Command("ls"))
}

4.4 io.MultiReader

有时候我们需要多个 io.reader 进行串联使用,比如在网络爬虫、多文件拼接等情况中,就需要从多个来源读取数

据并将其合并成一个完整的数据流。这时候,可以使用 io.MultiReader。以下的代码示例演示了如何使用

io.MultiReader 去单独读取两个文件然后进行合并。

package main

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

func readMultipleFiles(file1, file2 *os.File) {
	combinedReader := io.MultiReader(file1, file2)

	// create a buffer to store the read content
	buffer := make([]byte, 1024)

	for {
		n, err := combinedReader.Read(buffer)
		if err != nil && err != io.EOF {
			log.Fatal(err)
		}

		if n == 0 {
			break
		}
		fmt.Println(string(buffer[:n]))
	}
}

func main() {
	file1, err := os.Open("./files/a.txt")
	if err != nil {
		log.Fatal(err)
	}
	defer file1.Close()
	file2, err := os.Open("./files/b.txt")
	if err != nil {
		log.Fatal(err)
	}
	defer file2.Close()
	readMultipleFiles(file1, file2)
}
# 程序输出
a1
a2
a3
a4
a5
b1
b2
b3
b4
b5

4.5 io.TeeReader

在某些情况下,我们需要将输入流的数据同时输出到多个不同的 io.writer 以满足不同的需求,比如在调试某些应

用时需要将输入流数据同时输出到不同的日志。这个时候就可以使用 io.TeeReader。以下的代码演示了如何使用

io.TeeReader 把输入流的数据同时输出到标准输出和标准错误输出。

package main

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

func printToStdAndErr(reader io.Reader) {
	teeReader := io.TeeReader(reader, os.Stderr)
	buffer := make([]byte, 1024)
	for {
		n, err := teeReader.Read(buffer)
		if err != nil && err != io.EOF {
			log.Fatal(err)
		}
		if n == 0 {
			break
		}
		fmt.Println(string(buffer[:n]))
	}
}

func main() {
	file1, err := os.Open("./files/a.txt")
	if err != nil {
		log.Fatal(err)
	}
	defer file1.Close()
	printToStdAndErr(file1)
}
# 输出
a1
a2
a3
a4
a5
a1
a2
a3
a4
a5

4.6 io.LimitedReader

有时候我们需要从一个 io.reader 中读取一定的数据量,这种需求可以通过 io.LimitedReader 完成。

io.LimitedReader 可以从某个数据流中按照指定的长度读取数据,读取长度上限达到之后,就停止读取数据。以

下的代码演示了如何在 Go 中使用 io.LimitedReader。

package main

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

func readByteSlice(reader io.Reader) {
	limitedReader := &io.LimitedReader{R: reader, N: 10}
	buffer := make([]byte, 1024)
	for {
		n, err := limitedReader.Read(buffer)
		if err != nil && err != io.EOF {
			log.Fatal(err)
		}
		if n == 0 {
			break
		}
		fmt.Print(string(buffer[:n]))
	}
}

func main() {
	file1, err := os.Open("./files/a.txt")
	if err != nil {
		log.Fatal(err)
	}
	defer file1.Close()
	readByteSlice(file1)
}
# 程序输出
a1
a2
a3

5、IO包的一些使用

5.1 写到标准输出

package main

import "fmt"

func main() {
	// Hello World
	fmt.Println("Hello World")
}

上面代码是下面这个例子的简化版:

package main

import (
	"fmt"
	"os"
)

func main() {
	fmt.Fprintln(os.Stdout, "Hello World")
}

这里导入了一个额外的包 os,并且使用了 fmt 包中一个叫做 Fprintln 的方法。Fprintln 方法接收一个 io.Writer 类

型和一个要写入的字符串。os.Stdout 满足 io.Writer 接口。

5.2 写到定制的writer

学会了如何向 os.stdout 写入数据,让我们创建一个自定义 writer 并在那里存储一些信息。我们可以通过初始化

一个空的缓冲区并向其写入内容来实现:

package main

import (
	"bytes"
	"fmt"
)

func main() {
	// 空buffer,实现了io.Writer
	var b bytes.Buffer
	// 这里需要传入地址&
	fmt.Fprintln(&b, "Hello World")
	// Hello World
	fmt.Println(b.String())
}

这个片段实例化了一个空缓冲区,并将其作为 Fprintln 方法的第一个参数 (io.Writer)。

5.3 同时写给多个writer

有时需要将一个字符串写入多个 writer 中。我们可以使用 io 包中的 MultiWriter 方法轻松做到这一点。

package main

import (
	"bytes"
	"fmt"
	"io"
)

func main() {
	// 两个空buffers
	var buf1, buf2 bytes.Buffer
	// 创建MultiWriter
	mw := io.MultiWriter(&buf1, &buf2)
	// 写入数据到MultiWriter
	fmt.Fprintln(mw, "Hello World")
	// Hello World
	fmt.Println(buf1.String())
	// Hello World
	fmt.Println(buf2.String())
}

在上面的片段中,我们创建了两个空的缓冲区,叫做 buf1 和 buf2。我们将这些 writer 传递给一个叫做

io.MultiWriter 的方法,以获得一个组合写入器。消息 Hello World 将在内部同时被写入 buf1 和 buf2 中。

5.4 创建一个简单的reader

Go提供了 io.Reader interface 来实现一个 IO reader。reader 不进行读取,但为他人提供数据。它是一个临时的

信息仓库,有许多方法,如 WriteTo, Seek 等。

让我们看看如何从一个字符串创建一个简单的 reader:

package main

import (
	"fmt"
	"io"
	"strings"
)

func main() {
	// 创建一个NewReader
	r := strings.NewReader("Hello World")
	// 从Reader读取所有数据
	// func ReadAll(r Reader) ([]byte, error) {}
	b, err := io.ReadAll(r)
	if err != nil {
		panic(err)
	}
	// Hello World
	fmt.Println(string(b))
}

这段代码使用 strings.NewReader 方法创建了一个新的 reader。该 reader 拥有 io.Reader 接口的所有方法。我

们使用 io.ReadAll 从 reader 中读取内容,它返回一个字节切片。最后,我们将其打印到控制台。

注意:os.Stdin 是一个常用的 reader,用于收集标准输入。

5.5 一次性从多个 reader 上读取数据

与 io.MultiWriter 类似,我们也可以创建一个 io.MultiReader 来从多个 reader 那里读取数据。数据会按照传递给

io.MultiReader 的读者的顺序依次收集。这就像一次从不同的数据存储中收集信息,但要按照给定的顺序。

package main

import (
	"fmt"
	"io"
	"strings"
)

func main() {
	// 创建两个Reader
	buf1 := strings.NewReader("Hello buf1\n")
	buf2 := strings.NewReader("Hello buf2")
	// 创建一个MultiReader
	mr := io.MultiReader(buf1, buf2)
	// 从MultiReader读取数据
	b, err := io.ReadAll(mr)
	if err != nil {
		panic(err)
	}
	// Hello buf1
	// Hello buf2
	fmt.Println(string(b))
}

代码很简单,创建两个名为 buf1 和 buf2 的 reader,并试图从它们中创建一个 MultiReader 。我们可以使用

io.Readall 来读取 MultiReader 的内容,就像一个普通的 reader。

现在我们已经了解了 reader 和 writer,让我们看看从 reader 复制数据到 writer 的例子。接下来我们看到复制数

据的技术。

注意:不要对大的缓冲区使用 io.ReadAll,因为它们会消耗尽内存。

5.6 将数据从 reader 复制到 writer

再次对定义的理解:

reader:我可以从谁那里复制数据

writer:我可以把数据写给谁?我可以向谁写数据

这些定义使我们很容易理解,我们需要从一个 reader 加载数据,并将其转储到一个 writer (如os.Stdout或一个缓

冲区)。这个复制过程可以通过两种方式发生:

  • reader 将数据推送给 writer
  • writer 从 reader 中拉出数据

5.6.1 reader 将数据推送给 writer

这一部分解释了第一种拷贝的变化,即 reader 将数据推送到 writer 那里。它使用 reader.WriteTo(writer) 的

API。

package main

import (
	"bytes"
	"fmt"
	"strings"
)

func main() {
	r := strings.NewReader("Hello World")
	var b bytes.Buffer
	r.WriteTo(&b)
	// Hello World
	fmt.Println(b.String())
}

在代码中,我们使用 WriteTo 方法从一个名为 r 的 reader 那里把内容写进写 writer b。在下面的例子中,我们看

到一个 writer 如何主动地从一个 reader 那里获取信息。

5.6.2 writer 从 reader 中拉出数据

方法 writer.ReadFrom(reader) 被一个 writer 用来从一个给定的 reader 中提取数据。让我们看一个例子:

package main

import (
	"bytes"
	"fmt"
	"strings"
)

func main() {
	r := strings.NewReader("Hello World")
	var b bytes.Buffer
	b.ReadFrom(r)
	// Hello World
	fmt.Println(b.String())
}

该代码看起来与前面的例子相似。无论你是作为 reader 还是 writer,你都可以选择变体1或2来复制数据。现在是

第三种变体,它比较干净。

5.6.3 使用 io.Copy

io.Copy 是一个实用的函数,它允许人们将数据从一个 reader 移到一个 writer。

让我们看看它是如何工作的:

package main

import (
	"bytes"
	"fmt"
	"io"
	"strings"
)

func main() {
	r := strings.NewReader("Hello World")
	var b bytes.Buffer
	_, err := io.Copy(&b, r)
	if err != nil {
		panic(err)
	}
	// Hello World
	fmt.Println(b.String())
}

io.Copy 的第一个参数是 Writer (目标),第二个参数是 Reader (源),用于复制数据。

每当有人将数据写入 writer 时,你希望有信息可以被相应的 reader 读取,这就出现了管道的概念。

5.7 用io.Pipe创建一个数据管道

io.Pipe 返回一个 reader 和一个 writer,向 writer 中写入数据会自动允许程序从 reader 中消费数据,它就像一个

Unix的管道。

你必须把写入逻辑放到一个单独的 goroutine 中,因为管道会阻塞 writer,直到从 reader 中读取数据,而且

reader 也会被阻塞,直到 writer 被关闭。

package main

import (
	"fmt"
	"io"
)

func main() {
	// func Pipe() (*PipeReader, *PipeWriter) {}
	pr, pw := io.Pipe()
	go func() {
		defer pw.Close()
		fmt.Fprintln(pw, "Hello World")
	}()
	b, err := io.ReadAll(pr)
	if err != nil {
		panic(err)
	}
	// Hello World
	fmt.Println(string(b))
}

该代码创建了一个管道 reader 和管道 writer。我们启动一个程序,将一些信息写入管道 writer 并关闭它。我们使

用 io.ReadAll 方法从管道 reader 中读取数据。如果你不启动一个单独的程序(写或读,都是一个操作),程序将陷

入死锁。

5.8 用io.Pipe、io.Copy和io.MultiWriter捕捉函数的stdout到一个变量中

假设我们正在构建一个CLI应用程序。作为这个过程的一部分,我们创建一个函数产生的标准输出(到控制台),并

将相同的信息赋值到一个变量中。我们怎样才能做到这一点呢?我们可以使用上面讨论的技术来创建一个解决方

案。

package main

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

func foo(w *io.PipeWriter) {
	defer w.Close()
	fmt.Fprintln(w, "Hello World")
}

func main() {
	pr, pw := io.Pipe()
	go foo(pw)
	var b bytes.Buffer
	mw := io.MultiWriter(os.Stdout, &b)
	_, err := io.Copy(mw, pr)
	if err != nil {
		panic(err)
	}
	fmt.Println("result: ",b.String())
}
# 输出
Hello World
result:  Hello World

上述程序是这样工作的:

  • 创建一个管道,给出一个 reader 和 writer。如果你向管道 writer 写了什么,Go会把它复制到管道的 reader

    那里。

  • 创建一个 io.MultiWriter 与 os.Stdout 和自定义缓冲区 b

  • foo(作为一个协程)将把一个字符串写到管道 writer 中

  • io.Copy将把内容从管道 reader 复制到 MultiWriter 中。

  • os.Stdout将接收输出以及你的自定义缓冲区b

  • 内容现在可以在b中使用

5.9 Reader读取文件内容

package main

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

// 读取值
func ReadAll(reader io.Reader) (int, []byte, error) {
	result := make([]byte, 0)
	size := 0
	var err error
	data := make([]byte, 2048)
	for {
		n, err := reader.Read(data)
		if err != nil {
			// error为io.EOF表示读取结束
			if err == io.EOF {
				if n > 0 {
					size += n
					result = append(result, data...)
				}
			}
			break
		}
		size += n
		result = append(result, data...)
	}
	return size, result, err
}

func main() {
	file := "./files/a.txt"
	f, err := os.Open(file)
	if err != nil {
		fmt.Printf("open file [%s] failed", file)
		return
	}
	defer f.Close()
	n, data, err := ReadAll(f)
	if err != nil && err != io.EOF {
		fmt.Printf("read from file failed with error : %s\n", err)
		return
	}
	fmt.Printf("content size : = %d \n", n)
	fmt.Println(string(data))
}
# 结果
content size : = 18
a1
a2
a3
a4
a5

5.10 Writer写入文件内容

package main

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

// 定义一个Screen对象
type Screen struct{}

func (s *Screen) Write(data []byte) (int, error) {
	fmt.Print(string(data))
	return len(data), nil
}

func WriteTo(writer io.Writer, data []byte) (int, error) {
	return writer.Write(data)
}

func main() {
	data := []byte("hello, 这是一段普通文本")
	// 输出到屏幕
	_, err := WriteTo(&Screen{}, data)
	if err != nil {
		panic(err)
	}
	// 输出到文件
	fo, err := os.OpenFile("fout.txt", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666)
	if err != nil {
		panic(err)
	}
	defer fo.Close()
	_, err = WriteTo(fo, data)
	if err != nil {
		panic(err)
	}
}

6、其它相关的IO库

基础的 IO 库,提供了 Reader 和 Writer 接口。其中的 os 包、net 包、string 包、bytes 包以及

bufio 包都实现了 io 中的 Reader 或 Writer 接口。

Go语言IO模式_第1张图片

  • ioutil:提供了一些方便的文件读写函数,如 ReadFile 和 WriteFile。

  • bytes:提供了对字节内容的读写。

  • os:提供了访问底层操作系统资源的能力,如文件读写、进程控制等。

  • net:提供了网络相关的 IO 功能,如 TCP、UDP 通信、HTTP 请求等。

  • strings:提供了string的读取。因为string不能写,所以只有Reader。

  • bufio:提供带缓存的 IO 操作,解决频繁、少量读取场景下的性能问题。

6.1 io/ioutil工具库

ioutil 是 io 库的辅助工具函数库,用于实现I/O实用程序功能。

io/ioutil 库官方文档:https://pkg.go.dev/io/ioutil

工具函数 返回值 描述
ReadAll []byte 读取数据返回读取到的字节切片
ReadDir []os.FileInfo 读取目录返回目录入口数组
ReadFile []byte 读取文件返回文件内容的字节切片
WriteFile error 根据文件路径写入字节切片
TempDir string 在指定目录中创建指定前缀的临时文件夹,返回临时目录路径。
TempFile os.File 在指定目录创建指定前缀的临时文件

下面演示 ReadAll() 用来一次性的读取数据:

package main

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

func main() {
	fp, err := os.Open("./files/a.txt")
	defer fp.Close()
	buf, err := ioutil.ReadAll(fp)
	if err != nil {
		fmt.Printf("%+v\n", err)
	} else {
		fmt.Printf("%s\n", buf)
	}
}
# 结果
a1
a2
a3
a4
a5

6.2 bytes库

处理字节数组的库,bytes.Reader 可以把 []byte 转换成 Reader,bytes.Buffer 可以把 []byte 转化成 Reader、

Writer ,换句话讲,内存块可以作为读写的数据流了。

标准库 bytes 是Go语言中用来操作字节串 (byte slice) 的包,以下是 bytes 包的一些重要知识点:

  • bytes.Buffer类型:这是bytes包中最常用的类型之一。Buffer类型表示一个缓冲区,可以用来动态地构建字节

    串,也可以用来读取字节串。

  • bytes.NewBuffer()函数:这是一个用来创建bytes.Buffer类型的函数,可以传入一个字节切片或字符串作为初

    始化内容。

  • bytes.NewReader()函数:这个函数可以将一个字节切片或字符串包装成一个io.Reader接口类型,方便对这

    些数据进行读取操作。

  • bytes.Contains()函数:这个函数用于检查一个字节串是否包含另一个子字节串。

  • bytes.Equal()函数:这个函数用于比较两个字节串是否相等。

  • bytes.Join()函数:这个函数可以将多个字节串连接起来,返回一个新的字节串。

  • bytes.Split()函数:这个函数可以将一个字节串按照指定的分隔符分割成多个子字节串。

  • bytes.Trim()函数:这个函数可以去掉字节串开头和结尾的指定字符集。

  • bytes.Replace()函数:这个函数可以将字节串中的指定子字节串替换成另一个字节串。

  • bytes.Fields()函数:这个函数可以将一个字节串按照空格分割成多个子字节串,并返回一个切片。

以上是 bytes 包的一些重要知识点,不过还有其他很多有用的函数和类型。如果需要更详细的了解,可以查看官

方文档。

bytes 库官方文档:https://pkg.go.dev/bytes

package main

import (
	"bytes"
	"fmt"
	"os"
	"strings"
)

func main() {

	// =================================bytes.Buffer=================================
	a := []byte("ab")
	b := []byte("abc")
	// [97 98] [97 98 99]
	fmt.Println(a, b)
	// ab abc
	fmt.Printf("%s %s\n", a, b)
	// 比较a和b是否相等,长度及字节都相同
	bytesEqual := bytes.Equal(a, b)
	// false
	fmt.Println(bytesEqual)
	// 判断a与b是否相同,忽略大小写
	bytesEqualFold := bytes.EqualFold(a, b)
	// false
	fmt.Println(bytesEqualFold)
	// 比较a和b,a等于b结果为0,a小于b结果为-1,a大于b结果为1
	bytesCompare := bytes.Compare(a, b)
	// -1
	fmt.Println(bytesCompare)

	// =================================bytes.NewBuffer=================================
	// 使用byte创建一个buf
	bytesNewBuffer := bytes.NewBuffer([]byte("abc"))
	// 使用string创建一个buf
	bytesNewBufferString := bytes.NewBufferString("abc")
	// 将buf转切片
	bytesBufferBytes := bytesNewBuffer.Bytes()
	// abc
	fmt.Println(string(bytesBufferBytes))
	// 将buf转string
	bytesBufferString := bytesNewBufferString.String()
	// abc
	fmt.Println(bytesBufferString)
	// 将缓冲区重置为空
	// bytesNewBufferString.Reset()
	// 读取缓冲区的数据到别的地方
	// 将缓冲区的内容bytesNewBuffer读取len(bufferByteRead)到bufferByteRead里面,返回读取的字节数
	bufferByteRead := []byte("women")
	bytesBufferByte, _ := bytesNewBuffer.Read(bufferByteRead)
	// 3
	fmt.Println(bytesBufferByte)
	// 验证bufferByteRead的数据
	// abcen,将abc替换wom
	fmt.Println(string(bufferByteRead))
	// 将别的数据读取到缓冲区
	// 将p写入缓冲区,返回p的长度
	bytesBufferWrite, _ := bytesNewBuffer.Write([]byte("def"))
	// 3
	fmt.Println(bytesBufferWrite)
	// 将c写入缓冲区
	bytesNewBuffer.WriteByte(byte('g'))
	bytesNewBuffer.WriteRune('h')
	// 将str写入缓冲区,返回str的长度
	bytesBufferWriteString, _ := bytesNewBuffer.WriteString("ijk")
	// 3
	fmt.Println(bytesBufferWriteString)
	// 测试缓冲区写入的内容
	// defghijk
	fmt.Println(bytesNewBuffer.String())
	// 将数据写入w(io.write),n为写入的字节数
	w := os.Stdout
	bytesBufferWriteTo, _ := bytesNewBuffer.WriteTo(w)
	// 8
	fmt.Println(bytesBufferWriteTo)
	// 从io.reader中读取数据,附加到缓冲区,返回读取的字节数n
	bytesBufferReadFrom, _ := bytesNewBuffer.ReadFrom(strings.NewReader("hello,world"))
	// 11
	fmt.Println(bytesBufferReadFrom)
	// 验证缓冲区数据
	// 会清空上面的缓冲区
	// hello,world
	fmt.Println(bytesNewBuffer.String())
	// 读取到delim字节,并返回到delim字节的切片
	bytesBufferReadBytes,_ := bytesNewBuffer.ReadBytes('l')
	// hel
	fmt.Println(string(bytesBufferReadBytes))
	// 读取到delim字节,并返回到delim字节的字符串
	bytesBufferReadStr,_ := bytesNewBuffer.ReadString('r')
	// lo,wor
	fmt.Println(bytesBufferReadStr)

	// =================================bytes.NewBuffer=================================
	// 创建一个从b读取的数据的 Reader
	bytesReader := bytes.NewReader([]byte("bbbbbbbbbbbbbbbbb"))
	// 返回Reader包含的切片中还没读取的字节数
	bytesReaderLen := bytesReader.Len()
	// 17
	fmt.Println(bytesReaderLen)
	// 返回Reader的原始字节数
	bytesReaderSize := bytesReader.Size()
	// 17
	fmt.Println(bytesReaderSize)
	// 从Reader中读取n字节放入b中,返回读取的字节数
	bytesRead := []byte("abc")
	bytesReaderRead,_ := bytesReader.Read(bytesRead)
	// bbb
	fmt.Println(string(bytesRead))
	// 3
	fmt.Println(bytesReaderRead)
	// 14
	fmt.Println(bytesReader.Len())
	//从Reader中读取索引到off放入b中,返回读取的字节数
	bytesReadAt := []byte("abcd")
	bytesReaderReadAt,_ := bytesReader.ReadAt(bytesReadAt,5)
	// bbbb
	fmt.Println(string(bytesReadAt))
	// 4
	fmt.Println(bytesReaderReadAt)
	// 14
	fmt.Println(bytesReader.Len())
	// 读取一个字节并返回该字节
	bytesReaderReadByte,_ := bytesReader.ReadByte()
	// b
	fmt.Println(string(bytesReaderReadByte))
	// 根据 whence 的值,修改并返回进度下标 i ,当 whence == 0 ,进度下标修改为 off,当 whence == 1 ,
	// 进度下标修改为 i+off,当 whence == 2 ,进度下标修改为 len[s]+off.
	// off 可以为负数,whence 的只能为 0,1,2,当 whence 为其他值或计算后的进度下标越界,则返回错误。
	bytesReaderSeek,_ := bytesReader.Seek(5,6)
	// 0
	fmt.Println(bytesReaderSeek)
	// 将数据写入 w(io.write) , n 为写入的字节数
	w1 := os.Stdout
	bytesReaderWriteTo,_ := bytesReader.WriteTo(w1)
	// bbbbbbbbbbbbb
	// 13
	fmt.Println(bytesReaderWriteTo)
	// 重置 Reader
	bytesReader.Reset([]byte("abc"))
}

6.3 os库

文件 IO,这个就是我们最常见的文件 IO 啦,文件可以作为读端,也可以作为写端。

os 库官方文档:https://pkg.go.dev/os

读取文件内容:

package main

import (
	"fmt"
	"os"
)

func main() {
	// 打开文件
	file, err := os.Open("./files/a.txt")
	if err != nil {
		fmt.Println(err)
		return
	}
	// 关闭连接
	defer file.Close()
	// ./files/a.txt
	fmt.Println(file.Name())
	// 读取文件的内容
	// Read到容器,返回读取数量n
	// 如果读到了文件的末尾 err=io.EOF
	bs := make([]byte, 1024)
	n, err := file.Read(bs)
	// 18
	fmt.Println(n)
	/*
		a1
		a2
		a3
		a4
		a5
	*/
	fmt.Println(string(bs))
}

向文件中写入内容:

package main

import (
	"fmt"
	"os"
)

func main() {
	// os.O_APPEND追加不改变数据
	file, err := os.OpenFile("./files/c.txt", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666)
	if err != nil {
		fmt.Println(err)
		return
	}
	defer file.Close()
	// 业务代码
	bs := []byte{65, 66, 67, 68, 69}
	n, err := file.Write(bs)
	if err != nil {
		fmt.Println(err)
		return
	}
	// 5
	fmt.Println(n)
	n1, err := file.WriteString("hello world!")
	if err != nil {
		fmt.Println(err)
		return
	}
	// 12
	fmt.Println(n1)
}

生成的文件的内容:

ABCDEhello world!

6.4 net 库

网络可以作为读写源,抽象成了 Reader、Writer 的形式。这个是以 net.Conn 封装出来的。

net 库官方文档:https://pkg.go.dev/net

server 端:

package main

import (
	"log"
	"net"
)

func handleConn(conn net.Conn) {
	defer conn.Close()
	buf := make([]byte, 4096)
	conn.Read(buf)
	conn.Write([]byte("pong: "))
	conn.Write(buf)
}

func main() {
	server, err := net.Listen("tcp", ":8888")
	if err != nil {
		log.Println(err)
	}
	for {
		c, err := server.Accept()
		if err != nil {
			log.Println(err)
		}
		go handleConn(c)
	}
}

client 端:

package main

import (
	"io"
	"net"
	"os"
)

func main() {
	conn, err := net.Dial("tcp", ":8888")
	if err != nil {
		panic(err)
	}
	conn.Write([]byte("hello world!"))
	io.Copy(os.Stdout, conn)
}

先启动服务端,再启动客户端,客户端会输出如下信息:

pong: hello world!

6.5 strings库

strgins包用于字符串相关处理。

strings 库官方文档:https://pkg.go.dev/strings

package main

import (
	"fmt"
	"strings"
)

func main() {
	var builder strings.Builder
	// 将s的内容附加到b的缓冲区
	stringsBuilderWriteStringInt, _ := builder.WriteString("hello,world")
	// 11
	fmt.Println(stringsBuilderWriteStringInt)
	// 返回累计的字符串
	stringsBuilderString := builder.String()
	// hello,world
	fmt.Println(stringsBuilderString)
	// 返回累计字节数
	stringsBuilderLen := builder.Len()
	// 11
	fmt.Println(stringsBuilderLen)
	// 返回构建器底层字节片的容量。它是为正在生成的字符串分配的总空间,包括任何已经写入的字节。
	stringsBuilderCap := builder.Cap()
	// 16
	fmt.Println(stringsBuilderCap)
	// 增加b的容量,再增加n字节的空间
	builder.Grow(10)
	// 将p的内容附加到b的缓冲区。 总是返回len(p)
	stringsBuilderWrite, _ := builder.Write([]byte("hello"))
	// 5
	fmt.Println(stringsBuilderWrite)
	// 将字节c追加到b的缓冲区
	builder.WriteByte('a')
	// 将Unicode代码点r的UTF-8编码附加到b的缓冲区
	stringsBuilderWriteRune, _ := builder.WriteRune('1')
	// 1
	fmt.Println(stringsBuilderWriteRune)
	// 重置将生成器重置为空。
	builder.Reset()
}

6.6 bufio库

Reader/Writer 可以是缓冲 IO 的数据流。

bufio 库的官方文档:https://pkg.go.dev/bufio

package main

import (
	"bufio"
	"fmt"
	"strings"
)

func main() {
	s := strings.NewReader("ABCDEFG")
	str := strings.NewReader("12345")
	br := bufio.NewReader(s)
	b, _ := br.ReadString('\n')
	// ABCDEFG
	fmt.Println(b)
	// Reset丢弃缓冲中的数据,清除任何错误,将br重设为其下层从str读取数据。
	br.Reset(str)
	b, _ = br.ReadString('\n')
	// 12345
	fmt.Println(b)
}
package main

import (
	"bufio"
	"bytes"
	"fmt"
)

func main() {
	b := bytes.NewBuffer(make([]byte, 0))
	bw := bufio.NewWriter(b)
	bw.WriteString("123456789")
	c := bytes.NewBuffer(make([]byte, 0))
	// Reset丢弃缓冲中的数据,清除任何错误,将bw重设为将其输出写入c
	bw.Reset(c)
	bw.WriteString("456")
	// Flush方法将缓冲中的数据写入下层的io.Writer接口
	bw.Flush()
	// b=
	fmt.Println("b=", b)
	// c= 456
	fmt.Println("c=", c)
}

这里只是简单的介绍各个库的使用,如果有需要可以查阅相关文档。

你可能感兴趣的:(golang,golang)