首页 > 其他分享 >04_go语言io流

04_go语言io流

时间:2023-10-08 13:25:09浏览次数:36  
标签:func 04 err fmt io Reader go os

1. 基本IO接口

1.1Reader 接口

// Reader 接口定义
type Reader interface {
    Read(p []byte) (n int, err error)
}

Read 将 len(p) 个字节读取到 p 中。它返回读取的字节数 n (0 <= n <= len(p)) 以及遇到的任何错误。即使 Read 返回的 n < len(p),它也会在调用过程中占用 len(p) 个字节作为暂存空间。如果读取的数据不到 len(p) 个字节,Read 会返回可用数据,而不是等待更多的数据。

当Read 在成功读取 n > 0 个字节后如果遇到一个错误或者 EOF(end of file), 它会返回读取的字节数。

// Read 接口实例
package io

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

func ReaderExample() {
FOREND:
	for {
		readerMenu()
		var ch string
		var (
			data []byte
			err  error
		)
		fmt.Scanln(&ch)
		switch strings.ToLower(ch) {
		case "1":
			fmt.Println("请输入不多于9个字符,回车结束")
			data, err = ReadFrom(os.Stdin, 11)
		case "2":
			dir, _ := os.Getwd()
			file, err := os.Open(dir + "/01.txt")
			if err != nil {
				fmt.Println("文件打开错误:", err)
				continue
			}
			data, err = ReadFrom(file, 9)
			file.Close()
		case "3":
			data, err = ReadFrom(strings.NewReader("from string"), 12)
		case "4":
			fmt.Println("暂未实现")
		case "b":
			fmt.Println("返回上级菜单")
			break FOREND
		case "q":
			fmt.Println("退出")
			os.Exit(0)
		default:
			fmt.Println("输入错误")
			continue
		}
		if err != nil {
			fmt.Println("数据读取失败,可以试试从其他输入源读取!")
		} else {
			fmt.Printf("读取到的数据是:%s\n", data)
		}
	}
}

// 从Reader输入流中读数据
func ReadFrom(reader io.Reader, num int) ([]byte, error) {
	p := make([]byte, num)
	n, err := reader.Read(p)
	if n > 0 {
		return p[:n], nil
	}
	return p, err
}

func readerMenu() {
	fmt.Println("")
	fmt.Println("*******从不同来源读取数据*********")
	fmt.Println("*******请选择数据源,请输入:*********")
	fmt.Println("1 表示 标准输入")
	fmt.Println("2 表示 普通文件")
	fmt.Println("3 表示 从字符串")
	fmt.Println("4 表示 从网络")
	fmt.Println("b 返回上级菜单")
	fmt.Println("q 退出")
	fmt.Println("***********************************")
}

1.2 Writer接口

// Writer 接口定义
type Writer interface {
    Write(p []byte) (n int, err error)
}

Write 将 len(p) 个字节从 p 中写入到基本数据流中。它返回从 p 中被写入的字节数 n(0 <= n <= len(p))以及任何遇到的引起写入提前停止的错误。若 Write 返回的 n < len(p),它就必须返回一个 非nil 的错误。

Writer接口一般结合标准库函数 Fprinf/Fprintf/Fprintln 来将数据格式化输出到 io.Writer中,这些函数的第一个参数是 io.Writer 类型的。fmt.Fprintf/.../... 会将内容输出到指定输出中。

实现了 io.Reader 接口或者 io.Writer 的接口类型:

// 写文件
func WriteToFile(path string) (string, error) {
	file, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_APPEND, os.ModeTemporary)
	if err != nil {
		return "文件打开错误", err
	}
	inputReader := bufio.NewReader(os.Stdin)
	input, err := inputReader.ReadString('\n')
	num, err := fmt.Fprintln(file, input)
	if err != nil {
		return "文件写入失败", err
	}
	return string(num), nil
}

1.3 实现了io.Reader 和 io.Writer 接口的类型

  • Stdin(Reader接口);Stdout(Writer接口);
  • os.File 同时实现了 io.Reader和 io.Writer接口;
  • strings.Reader 实现了 io.Reader接口;
  • bufio.Reader/Writer 分别实现了 Reader和Writer接口;
  • bytes.Buffer 同时实现了Reader和Writer接口;
  • bytes.Reader 实现了Reader接口;

1.4 ReadAt 和 WriteAt 接口

//定义如下
type ReaderAt interface {
    ReadAt(p []byte, off int64) (n int, err error)
}

type WriterAt interface {
    WriteAt(p []byte, off int64) (n int, err error)
}

ReadAt 和 WriteAt 可以将数据按照指定的偏移量进行 读取/写入,数据读写后存储到字节切片 p 中,其余内容和 Reader/Writer 接口类似。

1.5 ReaderFrom 和 WriterTo 接口

//定义如下
type ReaderFrom interface {
    ReadFrom(r Reader) (n int64, err error)
}

type WriterTo interface {
    WriteTo(w Writer) (n int64, err error)
}

ReadFrom 会从 r 中读取数据直到发生 EOF或者错误。

file, err := os.Open("xxx.txt")
if err != nil {
    panic(err)
}
defer file.Close()
writer := bufio.NewWriter(os.Stdout)
writer.ReadFrom(file)   // 读取file文件的全部内容,并将内容打印到控制台上
wrtier.Flush()  
// 将文件A内容复制到文件B中
func CopyFile(pathA string, pathB string) (string, error) {
	fileSrc, err := os.Open(pathA)
	if err != nil {
		fileSrc.Close()
		return "来源文件打开失败,请检查!", err
	}
	fileTo, err := os.OpenFile(pathB, os.O_CREATE|os.O_APPEND, os.ModeTemporary)
	if err != nil {
		fileTo.Close()
		return "目的文件打开失败,请检查!", err
	}
	writer := bufio.NewWriter(fileTo)
	writer.ReadFrom(fileSrc)
	writer.Flush()
	return "文件复制完毕!", nil
}

1.6 Seeker接口

type Seeker interface {
    Seek(offset int64, whence int) (ret int64, err error)
}

const (
  SeekStart   = 0 // seek relative to the origin of the file
  SeekCurrent = 1 // seek relative to the current offset
  SeekEnd     = 2 // seek relative to the end
)

// 使用示例
reader := strings.NewReader("Go语言学习的")
reader.Seek(-6, io.SeekEnd)
r, _, _ := reader.ReadRune()

seek 用于设置下一次 Read 或者 Write 的偏移量,其中 0表示相对于文件的起始处、1表示相对于当前的偏移、2表示相对于结尾处。Seek返回新的偏移量和一个错误。

1.7 ByteReader 和 ByteWriter接口

这两个接口的类分别用来实现往内存中读取一个字节或者写入一个字节,有如下实现了它的类型:

  • bufio.Reader/Writer 分别实现了 io.ByteReader 和 io.ByteWriter;
  • bufio.Buffer 同时实现了 io.ByteReader 和 io.ByteWriter;
  • bytes.Reader 实现了 io.ByteReader;
  • strings.Reader 实现了 io.ByteReader;
func TestBytes() {
	var ch byte
	fmt.Scanf("%c\n", &ch)
	buffer := new(bytes.Buffer)
	err := buffer.WriteByte(ch)
	if err == nil {
		fmt.Println("成功写入一个字节,准备读取该字节!")
		newCh, _ := buffer.ReadByte()
		fmt.Printf("读取到的字节:%c\n", newCh)
	} else {
		fmt.Println("写入错误!")
	}
}

1.7 LimitedReader 类型

LimitedReader 实现了从某个 Reader(R)读取但将返回数据量限制为 N 字节。达到只允许读取一定长度数据 的目的。

// 结构定义
type LimitedReader struct {
    R Reader // underlying reader,最终的读取操作通过 R.Read 完成
    N int64  // max bytes remaining
}
func TestLimitedReader() {
	content := "This Is LimitReader Example"
	reader := strings.NewReader(content)
	limitReader := &io.LimitedReader{R: reader, N: 8}         // 生成 LimitedReader
	for limitReader.N > 0 {                                   // 每次读取后都会更新 N 
		tmp := make([]byte, 8)
		limitReader.Read(tmp)
		fmt.Println(string(tmp))
	}
}

1.8 PipeWriter 和 PipeReader 类型

PipeReader 是管道的读取端,它从管道中读取数据,该方法会阻塞直到管道写入端开始写入数据或者写入端被关闭。如果写入端关闭时还带有 error,该 Reader 返回的 err 就是写入端传递的 error;否则 err 为 EOF;

同理PipeWriter 是管道的写入端,它向管道中写入数据,该方法会阻塞直到读取端读完所有数据或者读取端被关闭

通过 io.pipe() 可以分别生成 PipeWriter 对象和 PipeReader对象,它通过将 io.Reader 连接到 io.Writer ,一端的读取匹配另一端的写入,直接在这两端之间复制数据。它没有内部缓存,对于并行调用 Read 和 Write 以及其他函数来说都是安全的,一旦等待的 I/O 结束,Close 就会完成。

由于 Writer 和 Reader是同步的,因此不能在一个 goroutine 中进行读和写。

func TestPipe() {
	pipeReader, pipeWriter := io.Pipe()
	go PipeWrite(pipeWriter)
	go PipeRead(pipeReader)
	time.Sleep(30 * time.Second)
}
func PipeWrite(writer *io.PipeWriter) {
	data := []byte("Go语言中文网")
	for i := 0; i < 3; i++ {
		n, err := writer.Write(data)
		if err != nil {
			fmt.Println(err)
			return
		}
		fmt.Printf("写入字节 %d\n", n)
	}
	writer.CloseWithError(errors.New("写入端关闭..."))
}

func PipeRead(reader *io.PipeReader) {
	buf := make([]byte, 128)
	for {
		fmt.Println("接受端开始阻塞5秒")
		time.Sleep(5 * time.Second)
		fmt.Println("接收端开始接收")
		n, err := reader.Read(buf)
		if err != nil {
			fmt.Println(err)
			return
		}
		fmt.Printf("收到字节:%d\n buf 内容:%s\n", n, buf)
	}
}

1.9 Copy 和 CopyN 函数

// 函数定义
func Copy(dst Writer, src Reader) (written int64, err error)
func CopyN(dst Writer, src Reader, n int64) (written int64, err error)

Copy 将 src 复制到 dst,直到在 src 上到达 EOF 或者发生了错误。它返回复制的字节数,如果有错误的话,还会返回在复制时遇到的第一个错误。

CopyN 类似于 Copy,区别在于设置复制字段的长度。

如果 dst 实现了 ReaderFrom 接口,复制操作可以通过 dst.ReadFrom(xxx) 来实现;此外如果 src 实现了 WriterTo 接口,复制操作可以通过 dst.WriteTo(xxx) 来实现。

// 用Copy 实现文件复制操作
func TestCopy() {
	srcFile, err := os.Open("D:\\a.txt")
	dstFile, _ := os.OpenFile("D:\\b.txt", os.O_CREATE|os.O_APPEND, os.ModeTemporary)
	if err == nil {
		srcReader := bufio.NewReader(srcFile)
		dstWriter := bufio.NewWriter(dstFile)
		n, err := io.Copy(dstWriter, srcReader)
		if err != nil {
			fmt.Println("复制文件时出现了错误!")
		}
		fmt.Printf("一共复制了 %d 个字节", n)
	}
}

1.10 ReadAtLeast 和 ReadFull 函数

// 函数定义
func ReadAtLeast(r Reader, buf []byte, min int) (n int, err error)
func ReadFull(r Reader, buf []byte) (n int, err error)

ReadAtLeast 规定至少从r 中读取 min 个字节为止,如果字数不到 min 个字节,会返回一个错误;如果没有读取到字节,返回EOF。

ReadFull 规定从r 中读满 buf 长度个字节为止。

1.11 MultiReader 和 MultiWriter

// 函数定义
func MultiReader(readers ...Reader) Reader
func MultiWriter(writers ...Writer) Writer

这两个函数接收多个 writer/reader,返回一个用来操作的 Reader/Writer,注意只是逻辑上将多个 writer/reader 组合了起来,并不能通过调用一次方法获得所有内容。

func TestMultiReader() {
	readers := []io.Reader{
		strings.NewReader("from strings reader"),
		bytes.NewBufferString("from bytes buffer"),
	}
	reader := io.MultiReader(readers...)
	data := make([]byte, 0, 128)
	buf := make([]byte, 10)
	for n, err := reader.Read(buf); err != io.EOF; n, err = reader.Read(buf) {
		if err != nil {
			panic(err)
		}
		data = append(data, buf[:n]...)
	}
	fmt.Printf("%s\n", data)
}

func TestMultiWriter() {
	file, err := os.Create("02.txt")
	if err != nil {
		panic(err)
	}
	defer file.Close()
	writers := []io.Writer{
		file,
		os.Stdout,
	}
	writer := io.MultiWriter(writers...)
	writer.Write([]byte("好好学习Golang!"))
}

2. 方便的 IO 操作函数集 ioutil

2.1 NopCloser 函数

用来包装一个 io.Reader 成 io.ReadCloser 的实例。比如在 net/http 包中的 NewRequest,接收了一个 io.Reader 的 body,而实际上 Request 的 Body的类型是 io.ReadCloser,因此在代码内部进行了判断,如果传递的 io.Reader 同时也实现了 io.ReadCloser 接口,则转换;否则通过 ioutil.NopCloser 进行包装转换一下。

    rc, ok := body.(io.ReadCloser)
    if !ok && body != nil {
        rc = ioutil.NopCloser(body)
    }

2.2 ReadAll 函数

func ReadAll(r io.Reader) ([]byte, error)

读取 io.Reader 内的所有内容,底层是通过 bytes.Buffer 的 ReadFrom 来读取所有数据的,函数调用成功后会返回 err == nil,而不是 err == EOF

2.3 ReadDir 函数

作用:获取指定路径及子目录下的所有 文件/文件夹 的切片对象

面试题:编写程序获取某个目录下的所有文件(包括子目录)

func main() {
    dir := os.Args[1]
    listAll(dir,0)
}

func listAll(path string, curHier int){
    fileInfos, err := ioutil.ReadDir(path)
    if err != nil{fmt.Println(err); return}

    for _, info := range fileInfos{
        if info.IsDir(){
            for tmpHier := curHier; tmpHier > 0; tmpHier--{
                fmt.Printf("|\t")
            }
            fmt.Println(info.Name(),"\\")
            listAll(path + "/" + info.Name(),curHier + 1)
        }else{
            for tmpHier := curHier; tmpHier > 0; tmpHier--{
                fmt.Printf("|\t")
            }
            fmt.Println(info.Name())
        }
    }
}

2.4 ReadFile 和 WriteFile 函数

ReadFile 的实现和 ReadAll类似,不过 ReadFile 会首先判断文件的大小,给 bytes.Buffer 一个预定容量,避免额外的内存分配。

    func ReadFile(filename string) ([]byte, error)
    func WriteFile(filename string, data []byte, perm os.FileMode) error

ReadFile 从 filename 指定的文件中读取数据,成功会返回 nil 而不是 EOF;

WriteFile 将 data 写入到 filename 文件中,当文件不存在时会根据 perm 指定的权限进行创建一个,文件存在时会先清空文件内容,对于perm 参数,一般指定为 0666.

3. 格式化io fmt包

func main() {
    u := user{"tang"}
    //Printf 格式化输出
    fmt.Printf("% + v\n", u)     //格式化输出结构
    fmt.Printf("%#v\n", u)       //输出值的 Go 语言表示方法
    fmt.Printf("%T\n", u)        //输出值的类型的 Go 语言表示
    fmt.Printf("%t\n", true)     //输出值的 true 或 false
    fmt.Printf("%b\n", 1024)     //二进制表示
    fmt.Printf("%c\n", 11111111) //数值对应的 Unicode 编码字符
    fmt.Printf("%d\n", 10)       //十进制表示
    fmt.Printf("%o\n", 8)        //八进制表示
    fmt.Printf("%q\n", 22)       //转化为十六进制并附上单引号
    fmt.Printf("%x\n", 1223)     //十六进制表示,用a-f表示
    fmt.Printf("%X\n", 1223)     //十六进制表示,用A-F表示
    fmt.Printf("%U\n", 1233)     //Unicode表示
    fmt.Printf("%b\n", 12.34)    //无小数部分,两位指数的科学计数法6946802425218990p-49
    fmt.Printf("%e\n", 12.345)   //科学计数法,e表示
    fmt.Printf("%E\n", 12.34455) //科学计数法,E表示
    fmt.Printf("%f\n", 12.3456)  //有小数部分,无指数部分
    fmt.Printf("%g\n", 12.3456)  //根据实际情况采用%e或%f输出
    fmt.Printf("%G\n", 12.3456)  //根据实际情况采用%E或%f输出
    fmt.Printf("%s\n", "wqdew")  //直接输出字符串或者[]byte
    fmt.Printf("%q\n", "dedede") //双引号括起来的字符串
    fmt.Printf("%x\n", "abczxc") //每个字节用两字节十六进制表示,a-f表示
    fmt.Printf("%X\n", "asdzxc") //每个字节用两字节十六进制表示,A-F表示
    fmt.Printf("%p\n", 0x123)    //0x开头的十六进制数表示
}

3.1 占位符

参考:Go语言占位符

3.2 Scanning

参考:Printf、Scanf格式化输入输出

有待补充....

3.3 Scan序列函数

Scan序列函数包括 Fscan/Fscanf/Fscanln/Sscan/Sscanf/Sscanln/Scan/Scanf/Scanln

(1)区别

这三个函数的第一个参数接受一个 io.Reader 类型,从其中读取内容并赋值给对应实参;而 Scan/Scanf/Scanln则是从标准输入获取内容;Sscan/Sscanf/Sscanln 是从字符串获取内容。

(2)Scan、Scanf、Scanln的一些坑

func main() {
    var name string
    var age int8
    fmt.Scanf("%s",&name)
    fmt.Scanf("%d",&age)
}

Scanf 不能像c语言那样多行读取,如果输入了回车就读取不了回车后的内容,它会返回 unexpected newline 的错误。

func main() {
    var name string
    var age int8
    n, err := fmt.Scan(&name, &age)   // n,err 分别接受扫描成功个数和错误返回值
}

Scan是可以多行多个数据读取的,输入时可以多个数据写到一行

func main() {
    var name string
    var age int8
    fmt.Scanln(&name)
    fmt.Scanln(&age)
}

Scanln 在换行时会把缓冲区的回车也收走,但是 Scan和 Scanf不会,所以 Scanf不能读取多行数据;Scan虽然不能收走回车,但也不会把回车读进去,遇到回车它会读取下一个数据。所以如果想要每次输入一个数据后回车然后输入下一个数据,只能使用 scan.

Scanln 扫描来自标准输入的文本,将空格分隔的值依次存放到后续的参数内,直到碰到换行。Scanf 与其类似,除了 Scanf 的第一个参数用作格式字符串,用来决定如何读取。

4. 缓存 IO bufio

4.1 Reader 类型和方法

怎样实例化

可见 bufio.Reader 包装了一个 io.Reader 对象,底层同时维护一个 []byte 数组充当缓冲池。

    type Reader struct {
        buf          []byte        // 缓存
        rd           io.Reader    // 底层的io.Reader
        // r:从buf中读走的字节(偏移);w:buf中填充内容的偏移;
        // w - r 是buf中可被读的长度(缓存数据的大小),也是Buffered()方法的返回值
        r, w         int
        err          error        // 读过程中遇到的错误
        lastByte     int        // 最后一次读到的字节(ReadByte/UnreadByte)
        lastRuneSize int        // 最后一次读到的Rune的大小 (ReadRune/UnreadRune)
    }

怎么去实例化一个 bufio.Reader 对象呢?有 2 种方法,包括 NewReader和 NewReaderSize,其中 NewReader 是通过调用 NewReaderSize 来实现的,它设置了缓冲池大小为 4096。

    func NewReader(rd io.Reader) *Reader {
        // 默认缓存大小:defaultBufSize=4096
        return NewReaderSize(rd, defaultBufSize)
    }

    func NewReaderSize(rd io.Reader, size int) *Reader {
        // 已经是bufio.Reader类型,且缓存大小不小于 size,则直接返回
        b, ok := rd.(*Reader)
        if ok && len(b.buf) >= size {
            return b
        }
        // 缓存大小不会小于 minReadBufferSize (16字节)
        if size < minReadBufferSize {
            size = minReadBufferSize
        }
        // 构造一个bufio.Reader实例
        return &Reader{
            buf:          make([]byte, size),
            rd:           rd,
            lastByte:     -1,
            lastRuneSize: -1,
        }
    }

ReadSlice、ReadBytes、ReadString 和 ReadLine方法

这几个方法的底层都是调用的 ReadSlice 来实现,所以先看看 ReadSlice。

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

ReadSlice 从输入中读取,直到遇到第一个界定符 delim 为止,返回一个指向缓存中字节的 slice,在下次调用读操作(read)时,这些字节会无效。

// 测试ReadSlice
func TestReadSlice() {
	reader := bufio.NewReader(strings.NewReader("http://studygolang.com. \nIt is the home of gophers"))
	line, _ := reader.ReadSlice('\n')
	fmt.Printf("the line:%s\n", line)
	// 这里可以换上任意的 bufio 的 Read/Write 操作
	n, _ := reader.ReadSlice('\n')
	fmt.Printf("the line:%s\n", line)
	fmt.Println(string(n))
}

// 输出
the line:http://studygolang.com. 

the line:It is the home of gophers
It is the home of gophers

可以发现两次调用 ReadSlice 后,第一次读取的 line 发生了变化,说明 ReadSlice 返回的 []byte 指向的是 Reader 中的buffer,而不是复制的一个副本,所以 ReadSlice 返回的数据会被下次的 I/O 操作重写

另外 ReadSlice 如果在找到界定符之前遇到了 error,它就会返回缓存中所有的数据和错误本身;如果在找到界定符之前缓存已经满了,ReadSlice 就会返回 bufio.ErrBufferFull;当它返回的结果line没有以界定符delim 结尾时,err 不为空(可能是 bufio.ErrBufferFull或者io.EOF)。

ReadBytes

    func (b *Reader) ReadBytes(delim byte) (line []byte, err error)

ReadBytes 方法返回的 []byte 则不会指向 Reader 中的buffer,它相当于每次读取后复制了一份数据副本。

// 测试
    reader := bufio.NewReader(strings.NewReader("http://studygolang.com. \nIt is the home of gophers"))
    line, _ := reader.ReadBytes('\n')
    fmt.Printf("the line:%s\n", line)
    // 这里可以换上任意的 bufio 的 Read/Write 操作
    n, _ := reader.ReadBytes('\n')
    fmt.Printf("the line:%s\n", line)
    fmt.Println(string(n))

//输出
    the line:http://studygolang.com. 

    the line:http://studygolang.com. 

    It is the home of gophers

ReadString

func (b *Reader) ReadString(delim byte) (line string, err error)

ReadString 方法将读取到的字节数组转换成字符串返回。

ReadLine

func (b *Reader) ReadLine() (line []byte, isPrefix bool, err error)

ReadLine 方法是一个底层的原始行读取命令,一般读取一行会使用 ReadBytes('\n') 或者 ReadString('\n') 来代替,但是这样会保留了行尾的标识符,所以还需要通过 bytes.TrimRight(xxx, "\r\n") 来清理掉行尾标识符。

为什么一般不适用 ReadLine 来读取一行呢?ReadLine 类似于 ReadSlice,如果待读取的一行大于缓存,就会返回该行的开始部分(等于缓存大小的部分),该行剩余的部分会在下次调用时返回,而且它和ReadSlice 一样,返回的只是 buffer 的引用,下次再执行 io 操作时,已经读取的数据会无效。

Peek 及其它方法

    func (b *Reader) Peek(n int) ([]byte, error)

peek只是窥探一下Reader 中没有读取的 n 个字节,它返回的 []byte 也是对 buffer的引用。所以在并发情况下使用它是不安全的,需要注意。

// 测试Peek
func TestPeek() {
	reader := bufio.NewReaderSize(strings.NewReader("http://studygolang.com.\t It is the home of gophers"), 14)
	go Peek(reader)
	go reader.ReadBytes('\t')
	time.Sleep(1e8)
}

func Peek(reader *bufio.Reader) {
	line, _ := reader.Peek(14)
	fmt.Printf("%s\n", line)
	//time.Sleep(1)
	fmt.Printf("%s\n", line)
}

// sleep注释后输出
    http://studygo
    http://studygo

// sleep注释前输出
    http://studygo
    ng.com.     It is
    func (b *Reader) Read(p []byte) (n int, err error)
    func (b *Reader) ReadByte() (c byte, err error)
    func (b *Reader) ReadRune() (r rune, size int, err error)  // 读取一个字符
    func (b *Reader) UnreadByte() error
    func (b *Reader) UnreadRune() error
    func (b *Reader) WriteTo(w io.Writer) (n int64, err error)

4.2 SplitFunc 逐字节/字符/单词/行 读取

 type SplitFunc func(data []byte, atEOF bool) (advance int, token []byte, err error)

SplitFunc 定义了对输入进行分词的函数。其中 data是未处理的数据、atEOF标识是否还有更多的数据(是否到了EOF)、advance 标识从输入中读取的字节数、token表示下一个结果数据、err表示可能的错误。

有数据 "studygolang\tpolaris\tgolangchina",通过"\t"进行分词,那么会得到三个token,它们的内容分别是:studygolang、polaris 和 golangchina。而 SplitFunc 的功能是:进行分词,并返回未处理的数据中第一个 token。对于这个数据,就是返回 studygolang。

如果 data中没有一个完整的token,例如扫描时没有换行符,SplitFunc 会返回 (0,nil,nil) 通知 Scanner 读取更多的数据到 slice中。

在 bufio 包中预定义了一些 split 函数,这些 split 函数都是 SplitFunc 类型的实例,这些函数包括 ScanBytes、ScanRunes、ScanWords、ScanLines

ScanBytes:返回单个字节作为一个 token;

ScanRunes:返回单个 UTF-8 编码的 rune 作为一个 token;

ScanWords:返回通过空格分词的单词。 比如 study golang,会返回 study。注意这里的空格是指 '\t'、'\n'、'\v'、'\f'、'\r' ;

ScanLines:返回一行文本,不包括行尾的换行符;

一般使用时,我们通过 bufio 包来实例化 Scanner对象,然后通过 Scanner对象的 split方法来进行分词。其中 Scanner 默认的split 是ScanLines

实例1:统计一段英文有多少个单词?

func TestScannerSplit1() {
	const input = "This is The Golang Standard Library.\nWelcome you!"
	scanner := bufio.NewScanner(strings.NewReader(input))
	scanner.Split(bufio.ScanWords)
	count := 0
	for scanner.Scan() {
		count++
	}
	if err := scanner.Err(); err != nil {
		fmt.Println(os.Stderr, "reading input:", err)
	}
	fmt.Println(count)
}

// 输出
8

我们在实例化 Scanner 后,通过 scanner.split(bufio.ScanWords) 来改变 split 函数。注意我们应当在调用 scan.Scan() 方法前调用 split方法。

Scan 方法:该方法类似于 iterator 中的 Next方法,它用于将 Scanner 获取下一个 token。他返回一个 bool ,如果返回 false,此时要么是扫瞄到了输入末尾、要么是遇到了一个错误。注意当 scanner.Scan() 返回 false 时,通过 Err 方法可以获取遇到的第一个错误,如果错误是 io.EOF,Err 方法会返回 nil。

Bytes 方法和 Text 方法:两个方法都是返回最近的 token,Bytes返回 []byte,Text返回 string。这两个方法应该在 Scan() 方法后调用,而且下次 Scan 会覆盖这次的 token。

实例2:读取文件中的数据,一次读取一行

func TestScannerSplit2() {
	file, err := os.Create("scanner.txt")
	if err != nil {
		panic(err)
	}
	defer file.Close()
	file.WriteString("http://studygolang.com.\nIt is the home of gophers.\nIf you are studying golang, welcome you!")
	// 将文件指针设置到文件开头
	file.Seek(0, os.SEEK_SET)
	scanner := bufio.NewScanner(file)
	for scanner.Scan() {
		fmt.Println(scanner.Text())
	}
}

// 输出
http://studygolang.com.
It is the home of gophers.
If you are studying golang, welcome you!

4.3 Writer 类型方法

实例化Writer

类似于Reader,也提供了 NewWriter和 NewWriterSize 两种方法。

一些方法

Available 方法用于获取缓存中还未使用的字节数(缓存大小 - 字段 n 的值);

Buffered 方法用于获取写入当前缓存中的字节数(字段n 的值);

Flush 方法将缓存中的所有数据写入到底层的 io.Writer 对象中。

    // 实现了 io.ReaderFrom 接口
    func (b *Writer) ReadFrom(r io.Reader) (n int64, err error)

    // 实现了 io.Writer 接口
    func (b *Writer) Write(p []byte) (nn int, err error)

    // 实现了 io.ByteWriter 接口
    func (b *Writer) WriteByte(c byte) error

    // io 中没有该方法的接口,它用于写入单个 Unicode 码点,返回写入的字节数(码点占用的字节),内部实现会根据当前 rune 的范围调用 WriteByte 或 WriteString
    func (b *Writer) WriteRune(r rune) (size int, err error)

    // 写入字符串,如果返回写入的字节数比 len(s) 小,返回的error会解释原因
    func (b *Writer) WriteString(s string) (int, error)
// 测试---写文件
func TestBufferWriter() {
	file, err := os.Create("buffer_writer.txt")
	if err != nil {
		panic(err)
	}
	defer file.Close()
	writer := bufio.NewWriter(file)
	writer.WriteString("aaaabbbbcccsssddd")
	writer.Flush()
}

5. 文件读写总结

1. 基础写文件

通过open 打开文件,如果文件打开成功,就使用 defer xxx.close() 确保程序退出前关闭该文件,然后无限循环中使用 ReadString() 将文件内容读取出来。

func main() {
    inputFile, inputError := os.Open("input.dat")
    if inputError != nil {
        fmt.Printf("An error occurred on opening the inputfile\n" +
            "Does the file exist?\n" +
            "Have you got acces to it?\n")
        return // exit the function on error
    }
    defer inputFile.Close()

    inputReader := bufio.NewReader(inputFile)
    for {
        inputString, readerError := inputReader.ReadString('\n')
        if readerError == io.EOF {
            return
        }
        fmt.Printf("The input was: %s", inputString)
    }
}

2. 将整个文件内容读取到一个字符串里

可以通过 ioutil 下的 ioutil.ReadFile() 方法,第一个参数是返回值类型[]byte,第二个参数是返回的错误,如果没有错误发生,第二个返回 nil。

func main() {
    inputFile := "products.txt"
    outputFile := "products_copy.txt"
    buf, err := ioutil.ReadFile(inputFile)
    if err != nil {
        fmt.Fprintf(os.Stderr, "File Error: %s\n", err)
        // panic(err.Error())
        }
    fmt.Printf("%s\n", string(buf))
    err = ioutil.WriteFile(outputFile, buf, 0x644)
    if err != nil {
        panic(err. Error())
    }
}

3. 带缓冲读取

通过 bufio.ReaderRead(buf) 读取数据存储到 buf 字节数组中。

buf := make([]byte, 1024)
...
n, err := inputReader.Read(buf)
if (n == 0) { break}

4. 按列读取

通过 fmt 包的 FScan 开头的一系列函数读取他们。

func Fscanln(r io.Reader, a ...interface{}) (n int, err error)

func Test02() {
	file, err := os.Open("01.txt")
	if err != nil {
		panic(err)
	}
	defer file.Close()

	var col1, col2, col3 []string
	for {
		var v1, v2, v3 string
		_, err := fmt.Fscanln(file, &v1, &v2, &v3)
		if err != nil {
			break
		}
		col1 = append(col1, v1)
		col2 = append(col2, v2)
		col3 = append(col3, v3)
	}
	fmt.Println(col1)
	fmt.Println(col2)
	fmt.Println(col3)
}

/*  01.txt 文件的内容
ABC 49 11.2
FUNC 11 43.3
GO 66 24.9  */

5. 读取压缩文件内容

通过 compress 包提供的压缩文件的功能。

func main() {
    fName := "MyFile.gz"
    var r *bufio.Reader
    fi, err := os.Open(fName)
    if err != nil {
        fmt.Fprintf(os.Stderr, "%v, Can't open %s: error: %s\n", os.Args[0], fName,
            err)
        os.Exit(1)
    }
    fz, err := gzip.NewReader(fi)
    if err != nil {
        r = bufio.NewReader(fi)
    } else {
        r = bufio.NewReader(fz)
    }

    for {
        line, err := r.ReadString('\n')
        if err != nil {
            fmt.Println("Done reading file")
            os.Exit(0)
        }
        fmt.Println(line)
    }
}

6. 基础写文件

通过缓冲区写文件 —— bufio.NewWriter

func main () {
    outputFile, outputError := os.OpenFile("output.dat", os.O_WRONLY|os.O_CREATE, 0666)
    if outputError != nil {
        fmt.Printf("An error occurred with file opening or creation\n")
        return  
    }
    defer outputFile.Close()

    outputWriter := bufio.NewWriter(outputFile)
    outputString := "hello world!\n"

    for i:=0; i<10; i++ {
        outputWriter.WriteString(outputString)
    }
    outputWriter.Flush()
}

如果需要写入的内容比较简单,还可以通过 Fprintf 来写入:

func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error)

fmt.Fprintf(outputFile, “Some test data.\n”)

7. 读写 json 文件

通过 encoding/json 包进行 json 文件读写操作。

  • json.Marshal(v interface{}) ([]byte, error) 将任意类型数据转换为 json 格式的数据
  • func Unmarshal(data []byte, v interface{}) error 把 JSON 解码为数据结构 v,注意使用时需要传递v 的地址
type Address struct {
    Type    string
    City    string
    Country string
}

type VCard struct {
    FirstName string
    LastName  string
    Addresses []*Address
    Remark    string
}

func main() {
    pa := &Address{"private", "Aartselaar", "Belgium"}
    wa := &Address{"work", "Boom", "Belgium"}
    vc := VCard{"Jan", "Kersschot", []*Address{pa, wa}, "none"}
    // fmt.Printf("%v: \n", vc) // {Jan Kersschot [0x126d2b80 0x126d2be0] none}:
    // JSON format:
    js, _ := json.Marshal(vc)
    fmt.Printf("JSON format: %s", js)
    // using an encoder:
    file, _ := os.OpenFile("vcard.json", os.O_CREATE|os.O_WRONLY, 0)
    defer file.Close()
    enc := json.NewEncoder(file)
    err := enc.Encode(vc)
    if err != nil {
        log.Println("Error in encoding json")
    }
}
b == []byte({"Name": "Wednesday", "Age": 6, "Parents": ["Gomez", "Morticia"]})
var f interface{}
err := json.Unmarshal(b, &f)

编码和解码数据流

func NewDecoder(r io.Reader) *Decoder    // 解码,通常用于读取 .json 格式文件
func NewEncoder(w io.Writer) *Encoder    // 编码,通常用于写入 .json 格式文件
func Test03() {
	pa := &Address{"private", "Aartselaar", "Belgium"}
	wa := &Address{"work", "Boom", "Belgium"}
	vc := VCard{"Jan", "Kersschot", []*Address{pa, wa}, "none"}
	js, _ := json.Marshal(vc)
	fmt.Printf("JSON format:%s", js)

	file, _ := os.OpenFile("04.json", os.O_CREATE|os.O_WRONLY, 0)
	defer file.Close()
	enc := json.NewEncoder(file)       // 编码数据流
	err := enc.Encode(vc)
	if err != nil {
		log.Println("Error !")
	}
}
func Test04() {
	file, _ := os.OpenFile("04.json", os.O_RDONLY, 0)
	dec := json.NewDecoder(file)
	var f VCard
	dec.Decode(&f)   // 解码数据流,这里确定了输出数据流的格式,所以能够直接对应过去
	fmt.Println(f)
}

如果无法确认输出数据流的格式,一般通过 interface{} 来接收,然后使用类型断言为 m := f.(map[string]interface{})

接着通过 for-rangetype-switch 来访问其实际类型:

for k, v := range m {
    switch vv := v.(type) {
    case string:
        fmt.Println(k, "is string", vv)
    case int:
        fmt.Println(k, "is int", vv)

    case []interface{}:
        fmt.Println(k, "is an array:")
        for i, u := range vv {
            fmt.Println(i, u)
        }
    default:
        fmt.Println(k, "is of a type I don’t know how to handle")
    }
}

8. 读写 xml 文件

和 json 包一样,go 中也存在一个 xml包可以从 XML 中编码和解码数据。

处理 xml 字符串,通过switch-type 模式来识别 xml 字符串中的头标签、尾标签、属性值。

func TestXmlParser() {
	input := "<Person><FirstName>Laura</FirstName><LastName>Lynn</LastName></Person>"
	inputer := strings.NewReader(input)
	p := xml.NewDecoder(inputer)

	for t, err := p.Token(); err == nil; t, err = p.Token() {
		switch token := t.(type) {
		case xml.StartElement:
			name := token.Name.Local
			fmt.Printf("Token name: %s\n", name)
			for _, attr := range token.Attr {
				attrName := attr.Name.Local
				attrValue := attr.Value
				fmt.Printf("An attribute is: %s %s\n", attrName, attrValue)
			}
		case xml.EndElement:
			fmt.Printf("End of token\n")
		case xml.CharData:
			content := string([]byte(token))
			fmt.Printf("This is the content: %v\n", content)
		default:
		}
	}
}

序列化 xml 字符串

func TestXmlInputer() {
	input := "<Person><FirstName>Laura</FirstName><LastName>Lynn</LastName></Person>"
	file, err := os.OpenFile("04.xml", os.O_CREATE|os.O_WRONLY, 0666)
	if err != nil {
		panic(err)
	}
	res, _ := xml.Marshal(input)
	fmt.Printf("xml content is %s\n", res)
	defer file.Close()
	writer := bufio.NewWriter(file)
	p := xml.NewEncoder(writer)
	p.Encode(input)
}

9. 用 Gob 传输数据

Gob 是 Go 自己的以二进制形式序列化和反序列化程序数据的格式,可以在 encoding 包中找到。

Gob 通常用于远程方法调用(RPCs)参数和结果的传输,以及应用程序和机器之间的数据传输。它和 json、xml 有一些不同,主要体现在 Gob 特定用于纯 Go 环境中,例如两个用 Go 写的服务之间的通信,这样服务之间的通信可以变得更加高效和优化。

Gob 文件或流是完全自描述的,里面包含的所有类型都有一个对应的描述,并且总是可以用 Go 解码,而不需要了解文件的内容。只有可导出的字段会被编码,零值会被忽略。在解码结构体时,只有同时匹配名称和兼容类型的字段才会被解码,当源数据类型增加新字段后,Gob 解码客户端仍然可以以这种方式正常工作:解码客户端会继续识别以前存在的字段。并且还提供了很大的灵活性,比如在发送者看来,整数被编码成没有固定长度的可变长度,而忽略具体的 Go 类型

假如在发送者这边有一个结构T:

type T struct { X, Y, Z int }
var t = T{X: 7, Y: 0, Z: 8}

而在接收者这边可以用一个结构体 U 来接收这个值:

type U struct { X, Y *int8 }
var u U

字节缓冲模拟网络传输:

type P struct {
	X, Y, Z int
	Name    string
}

type Q struct {
	X, Y *int32
	Name string
}

func TestGob01() {
	var network bytes.Buffer
	dec := gob.NewDecoder(&network)               // 数据从network中读出
	enc := gob.NewEncoder(&network)               // 数据往network中写入
	err := enc.Encode(P{3, 4, 5, "Pythagoras"})
	if err != nil {
		log.Fatal("encoder error:", err)
	}
	var q Q
	err = dec.Decode(&q)
	if err != nil {
		log.Fatal("decoder error:", err)
	}
	fmt.Printf("%q: {%d,%d}\n", q.Name, *q.X, *q.Y)
}

编码到Gob文件:

func TestGob02() {
	pa := &Address{"private", "Aartselaar", "Belgium"}
	wa := &Address{"work", "Boom", "Belgium"}
	vc := VCard{"Jan", "Kersschot", []*Address{pa, wa}, "none"}
	file, _ := os.OpenFile("vcard.gob", os.O_CREATE|os.O_WRONLY, 0)
	defer file.Close()
	enc := gob.NewEncoder(file)
	err := enc.Encode(vc)
	if err != nil {
		log.Println("Error in encoding gob")
	}
}

解码并输出Gob文件:

func TestGob03() {
	file, _ := os.OpenFile("vcard.gob", os.O_RDONLY, 0)
	defer file.Close()
	dec := gob.NewDecoder(file)
	var res VCard
	dec.Decode(&res)
	fmt.Println(res)
}

10. Go中的密码学加密

Go 通常对网络传输中的数据进行加密,Go提供了超过30个包用于数据加密:

  • hash包:实现了 adler32crc32crc64fnv 校验
  • crypto包:实现了其它的hash算法,比如 md4、md5、sha1等。

测试SHA1加密并输出加密结果:

func TestHashCrypt() {
	hasher := sha1.New()
	io.WriteString(hasher, "test")
	b := []byte{}
	fmt.Printf("Result: %x\n", hasher.Sum(b))
	fmt.Printf("Result: %d\n", hasher.Sum(b))
	hasher.Reset()
	data := []byte("We shall welcome")
	hasher.Write(data)
	checksum := hasher.Sum(b)
	fmt.Printf("Result: %x\n", checksum)
}

加密数据并写入文件:

func TestHashPwd() {
	hasher := md5.New()
	file, _ := os.OpenFile("hash.txt", os.O_CREATE, 0)
	defer file.Close()
	var input string
	buffer := []byte{}
	fmt.Scan(&input)
	hasher.Write([]byte(input))
	res := hasher.Sum(buffer)
	fmt.Printf("%s\n", string(res))
	writer := bufio.NewWriter(file)
	_, err := writer.WriteString(string(res))
	if err != nil {
		panic(err)
	}
	writer.Flush()
}

总结:什么时候选择对应的IO库

1、io.Reader/Writer 常用的几种实现

  • net.Conn:标识网络连接;
  • os.Stdin、os.Stdout、os.Stderr:对应标准输入输出错误;
  • os.File:网络、标准输入输出文件的流读取;
  • strings.Reader:字符串抽象为io.Reader来实现;
  • bytes.Reader:[]byte抽象为io.Reader来实现;
  • bytes.Buffer:[]byte抽象为io.Reader和io.Writer的实现;
  • bufio.Reader/Writer:带缓冲的流读取和写入(如按行读取);

2、常见的几种库

  • io库属于底层接口定义库。实际使用主要用来调用它的常量和接口定义,比如 io.EOF 判断是否已经读取完,用 io.Reader 做变量的类型声明。
// 字节流读取完后,会返回io.EOF这个error
for {
	n, err := r.Read(buf)
	fmt.Println(n, err, buf[:n])
	if err == io.EOF {
		break
	}
}
  • os库

主要用来处理操作系统操作的,它作为Go程序和操作系统交互的桥梁。创建文件、打开或者关闭文件、Socket等等这些都是和操作系统挂钩的,所以都通过 os 库来执行。

  • ioutil库

ioutil库是一个有工具包,它提供了很多实用的 IO 工具函数,例如 ReadAll、ReadFile、WriteFile、ReadDir。唯一需要注意的是它们都是一次性读取和一次性写入,所以使用时,尤其是把数据从文件里一次性读到内存中时需要注意文件的大小。

//读出文件所有内容
func readByFile() {
	data, err := ioutil.ReadFile("./file/test.txt")
	if err != nil {
		log.Fatal("err:",err)
		return
	}
	fmt.Println("data",string(data))
}


//将数据一次性写入文件
func writeFile() {
  err := ioutil.WriteFile("./file/write_test.txt", []byte("hello world!"), 0644)
  if err != nil {
    panic(err)
    return
  }
}
  • bufio库

bufio,可以理解为在io库的基础上额外封装加了一个缓存层,它提供了很多按行进行读写的函数,从io库的按字节读写变为按行读写对写代码来说还是方便了不少。bufio库和 ioutil库相比较,只是bufio库有一个额外的缓存层。

func readBigFile(filePath string) error {
  f, err := os.Open(filePath)
  defer f.Close()

  if err != nil {
    log.Fatal(err)
    return err
  }

  buf := bufio.NewReader(f)
  count := 0
  // 循环中打印前100行内容
  for {
    count += 1
    line, err := buf.ReadString('\n')
    line = strings.TrimSpace(line)
    if err != nil {
      return err
    }
    fmt.Println("line", line)

    if count > 100 {
      break
    }
  }
  return nil
}
  • bytes 和 string库:

bytes 和 strings 库里的 bytes.Reader 和string.Reader,它们都实现了io.Reader接口,也都提供了NewReader方法用来从[]byte或者string类型的变量直接构建出相应的Reader实现。但是区别在于 bytes 库有 Buffer 的功能,而 strings 库没有。

r := strings.NewReader("abcde")
// 或者是 bytes.NewReader([]byte("abcde"))
buf := make([]byte, 4)
for {
	n, err := r.Read(buf)
	fmt.Println(n, err, buf[:n])
	if err == io.EOF {
		break
	}
}

参考:

https://www.cnblogs.com/golove/p/3276678.html

标签:func,04,err,fmt,io,Reader,go,os
From: https://www.cnblogs.com/istitches/p/17748639.html

相关文章

  • GO数组解密:从基础到高阶全解
    在本文中,我们深入探讨了Go语言中数组的各个方面。从基础概念、常规操作,到高级技巧和特殊操作,我们通过清晰的解释和具体的Go代码示例为读者提供了全面的指南。无论您是初学者还是经验丰富的开发者,这篇文章都将助您更深入地理解和掌握Go数组的实际应用。关注公众号【TechLeadClo......
  • UNIQUE VISION Programming Contest 2023 Autumn(AtCoder Beginner Contest 323)
    UNIQUEVISIONProgrammingContest2023Autumn(AtCoderBeginnerContest323)A.WeakBeats解题思路:按题意模拟即可。代码:#include<bits/stdc++.h>usingnamespacestd;usingll=longlong;voidsolve(){strings;cin>>s;intn=s.size();......
  • Solution-AGC018F
    对于全幺模阵刻画限制的一般方法。先写出限制:\(\sum_{v\in\text{sub}(u)}a_v=\{1,-1\}\)。嘛虽然你可以通过奇偶性(大概)把限制改成\(|\sum_{v\insub(u)}a_u|\leq1\),但是我们还是别这么做吧。考虑转化一下限制。设\(a_u\)表示\(u\)在第一棵树上的子树和,\(b_u\)表示\(u......
  • C# Dx截图初始化报错“SharpDX.SharpDXException: HRESULT: [0x80070057], Module: [G
    最近发现Dx截图创建输出设备时output.QueryInterface<Output1>().DuplicateOutput报错:“SharpDX.SharpDXException:HRESULT:[0x80070057],Module:[General],ApiCode:[E_INVALIDARG/InvalidArguments],Message:参数错误。经过验证,如果一个进程多次创建输出设备(多次调用o......
  • spring学习三:IoC概述
    IOC:控制反转,它并不是一种技术而是一种设计思想,是一个重要的面向对象编程法则,能够知道我们如何设计出松耦合,更优良的程序。 Spring通过IOC容器来管理所有java对象的实例化和初始化,控制对象与对象之间的依赖关系,我们将由IOC容器管理的java对象称为SpringBean,它与使用关键字new创......
  • npm install 报 cb.apply is not a function 错误
    npminstall报cb.applyisnotafunction错误1、问题来源.当我执行npminstall命令时,出现cb.applyisnotafunction错误!.由此可知,可能是npm和node版本不匹配。解决方案更换版本.node与npm版本对应表.参考资料node版本对应的npm版本表.解决npm......
  • Go - Sorting Arrays or Slices
    Problem: Youwanttosortelementsinanarrayorslice.Solution: Forint,float64,andstringarraysorslicesyoucanusesort.Ints,sort.Float64s,andsort.Strings.Youcanalsouseacustomcomparatorbyusingsort.Slice.Forstructs,youcan......
  • Go - Making Arrays and Slices Safe for Concurrent Use
    Problem: Youwanttomakearraysandslicessafeforconcurrentusebymultiplegoroutines.Solution: Useamutexfromthesynclibrarytosafeguardthearrayorslice.Lockthearrayorslicebeforemodifyingit,andunlockitaftermodificationsarema......
  • CSS,position: relative用法
    展示图如下:使左边区域固定展示,不受子域名的影响,所以要使用相对定位position:relative相对定位一般使用父级菜单绝对定位使用如下:绝对定位一般使用子级菜单position:absolute悬浮遮盖其余位置  ......
  • VS2019 创建Integration Service
    最近工作中需要用到Integrationservice,使用VS2022如何都打不开,查阅文档发现vs2022目前不支持,所以需要下载VS2019,安装步骤如下1、下载vs20192、在此窗口中,我们单击“扩展”>“管理扩展”: 3、在打开的窗口的搜索栏中,搜索“IntegrationServices”扩展名。从出现的列表中,我们......