golang 标准库 - fmt/scan.go 解读

本文深入解读了Go语言标准库fmt中的scan.go源码,探讨其在输入扫描和格式化方面的实现原理。
摘要由CSDN通过智能技术生成

**

golang 标准库 - fmt/scan.go 解读

**

// go/src/fmt/scan.go
// version 1.7


package fmt

import (
	"errors"
	"io"
	"math"
	"os"
	"reflect"
	"strconv"
	"sync"
	"unicode/utf8"
)

// ScanState 将扫描器的状态报告给自定义类型的 Scan 方法。
type ScanState interface {
	// ReadRune 从输入端读取一个字符,如果用在 Scanln 类的扫描器中,
	// 则该方法会在读到第一个换行符之后或读到指定宽度之后返回 EOF。
	// r   :读取的字符
	// size:字符所占用的字节数
	// err :遇到的错误信息
	ReadRune() (r rune, size int, err error)
	// UnreadRune 撤消最后一次的 ReadRune 操作,
	// 使下次的 ReadRune 操作得到与前一次 ReadRune 相同的结果。
	// 返回:遇到的错误信息
	UnreadRune() error
	// SkipSpace 为自定义的 Scan 方法提供跳过开头空白的能力。
	// 根据扫描器的不同(Scan 或 Scanln)决定是否跳过换行符。
	SkipSpace()
	// Token 用于从输入端读取符合要求的字符串,准备解析。
	// Token 从输入端读取连续的符合 f(c) 的字符 c。如果 f 为 nil,则使用
	// !unicode.IsSpace(c) 代替 f(c)。
	// skipSpace:是否跳过输入端开头的连续空白(通过 SkipSpace 方法)。
	// token    :存放读取到的数据。
	// err      :遇到的错误信息。
	// 注意:token 指向共享的数据,下次的 Token 操作可能会覆盖本次的结果。
	Token(skipSpace bool, f func(rune) bool) (token []byte, err error)
	// Width 返回占位符中指定的宽度值(宽度值是字符个数,不是字节个数)。
	// wid:获取到的宽度值
	// ok :是否指定了宽度值
	Width() (wid int, ok bool)
	// 因为上面实现了 ReadRune 方法,所以 Read 方法永远不应该被 Scan 方法调用。
	// 一个好的 ScanState 应该让 Read 直接返回相应的错误信息。
	Read(buf []byte) (n int, err error)
}

// Scanner 用于让自定义类型实现自己的扫描过程。
// Scan 方法会从输入端读取数据并将处理结果存入接收端,接收端必须是有效的指针。
// Scan 方法会被扫描器调用,只要对应的 arg 实现了该方法。
type Scanner interface {
	Scan(state ScanState, verb rune) error
}

// Scan 从标准输入中读取字符串(以空白分隔的值的序列)并解析为具体的值,
// 存入参数 a 所提供的变量中(参数 a 必须提供变量的地址)。换行视为空白。
// 当读到 EOF 或所有变量都填写完毕则停止扫描。
// n  :成功解析的参数数量
// err:解析过程中遇到的错误信息
func Scan(a ...interface{}) (n int, err error) {
	return Fscan(os.Stdin, a...)
}

// Scanln 和 Scan 类似,只不过遇到换行符就停止扫描。
func Scanln(a ...interface{}) (n int, err error) {
	return Fscanln(os.Stdin, a...)
}

// Scanf 从标准输入中读取字符串,并根据格式字符串 format 对读取的数据进行解析,
// 存入参数 a 所提供的变量中(参数 a 必须提供变量的地址)。
// 输入端的换行符必须和格式字符串中的换行符相对应(如果格式字符串中有换行符,则
// 输入端必须输入相应的换行符)。
// 占位符 %c 总是匹配下一个字符,包括空白,比如空格符、制表符、换行符。
// n  :成功解析的参数数量
// err:解析过程中遇到的错误信息
func Scanf(format string, a ...interface{}) (n int, err error) {
	return Fscanf(os.Stdin, format, a...)
}

// 实现了 Reader 接口的字符串类型
type stringReader string

func (r *stringReader) Read(b []byte) (n int, err error) {
	n = copy(b, *r)
	*r = (*r)[n:]
	if n == 0 {
		err = io.EOF
	}
	return
}

// Sscan 和 Scan 类似,只不过从 str 中读取数据。
func Sscan(str string, a ...interface{}) (n int, err error) {
	return Fscan((*stringReader)(&str), a...)
}

// Sscanln 和 Scanln 类似,只不过从 str 中读取数据。
func Sscanln(str string, a ...interface{}) (n int, err error) {
	return Fscanln((*stringReader)(&str), a...)
}

// Sscanf 和 Scanf 类似,只不过从 str 中读取数据。
func Sscanf(str string, format string, a ...interface{}) (n int, err error) {
	return Fscanf((*stringReader)(&str), format, a...)
}

// Fscan 和 Scan 类似,只不过从 r 中读取数据。
func Fscan(r io.Reader, a ...interface{}) (n int, err error) {
	s, old := newScanState(r, true, false) // 创建扫描器
	n, err = s.doScan(a)                   // 开始扫描
	s.free(old)                            // 回收扫描器
	return
}

// Fscanln 和 Fcanln 类似,只不过从 r 中读取数据。
func Fscanln(r io.Reader, a ...interface{}) (n int, err error) {
	s, old := newScanState(r, false, true) // 创建扫描器
	n, err = s.doScan(a)                   // 开始扫描
	s.free(old)                            // 回收扫描器
	return
}

// Fscanf 和 Scanf 类似,只不过从 r 中读取数据。
func Fscanf(r io.Reader, format string, a ...interface{}) (n int, err error) {
	s, old := newScanState(r, false, false) // 创建扫描器
	n, err = s.doScanf(format, a)           // 开始扫描
	s.free(old)                             // 回收扫描器
	return
}

// scanError 声明本地错误类型,用于 recover 时辨别 panic 是否由本地代码产生的。
type scanError struct {
	err error
}

// 本地代码用 -1 表示遇到 EOF
const eof = -1

// ss 是扫描器,整个扫描过程都是由它完成的。
// 它从 rs 中读取数据并进行解析。
type ss struct {
	rs    io.RuneScanner // 输入端
	buf   buffer         // 缓冲区
	count int            // 已读取的字符数
	atEOF bool           // 是否读到 EOF
	ssave                // 一些需要经常复位的字段
}

// ssave 是 ss 的一部分,存储一些需要经常复位的字段
type ssave struct {
	validSave bool // 平时用不上,递归时使用
	nlIsEnd   bool // 是否在换行符之后停止读取
	nlIsSpace bool // 是否将换行符视为空白
	argLimit  int  // 已读的字符数不能超过 argLimit(argLimit <= limit)
	limit     int  // 已读的字符数不能超过 limit(好像就当做常量在使用,用于复位 argLimit)
	maxWid    int  // 存储占位符中指定的宽度值
}

// 实现 ScanState 接口
// Read 方法仅用于 ScanState 以满足 io.Reader 接口。
// 在内部永远不会调用它,所以没有必要让它有任何动作。
func (s *ss) Read(buf []byte) (n int, err error) {
	return 0, errors.New("ScanState's Read should not be called. Use ReadRune")
}

// 实现 ScanState 接口
func (s *ss) ReadRune() (r rune, size int, err error) {
	// 读到 EOF 或超出读取限制,则返回 0 0 nil
	if s.atEOF || s.count >= s.argLimit {
		err = io.EOF
		return
	}
	r, size, err = s.rs.ReadRune()
	if err == nil {
		s.count++ // 统计被读出的字符数
		if s.nlIsEnd && r == '\n' {
			s.atEOF = true // 拒绝再次 ReadRune
		}
	} else if err == io.EOF {
		s.atEOF = true // 拒绝再次 ReadRune
	}
	return
}

// 实现 ScanState 接口
func (s *ss) Width() (wid int, ok bool) {
	if s.maxWid == hugeWid { //	hugeWid 是常量 1 << 30
		return 0, false
	}
	return s.maxWid, true
}

// 读取一个字符,如果遇到 EOF 则返回 eof(即 -1)
// 如果遇到其它错误,则中止整个扫描过程,返回 err。
func (s *ss) getRune() (r rune) {
	r, _, err := s.ReadRune()
	if err != nil {
		if err == io.EOF {
			return eof
		}
		s.error(err)
	}
	return
}

// 功能同 getRune,只不过遇到 EOF 也中止整个扫描过程,返回 err。
func (s *ss) mustReadRune() (r rune) {
	r = s.getRune()
	if r == eof {
		s.error(io.ErrUnexpectedEOF)
	}
	return
}

// 实现 ScanState 接口
func (s *ss) UnreadRune() error {
	s.rs.UnreadRune()
	s.atEOF = false // 允许再次 ReadRune
	s.count--       // 统计被读出的字符数
	return nil      // UnreadRune 可以反复调用,不返回错误信息。
}

// 将错误信息转换为 panic。
// 用于配合 recover 快速结束函数调用链,避免过多的返回值判断。
// 类似于 break label 的用法。
func (s *ss) error(err error) {
	panic(scanError{err})
}

// 作用同上面的 error 方法
func (s *ss) errorString(err string) {
	panic(scanError{errors.New(err)})
}

// 实现 ScanState 接口
func (s *ss) Token(skipSpace bool, f func(rune) bool) (tok []byte, err error) {
	// 遇到本地错误则仅仅返回 err 信息。
	// 遇到其它错误则 panic。
	defer func() {
		if e := recover(); e != nil {
			if se, ok := e.(scanError); ok {
				err = se.err
			} else {
				panic(e)
			}
		}
	}(
在你提供的 Dockerfile 中,定义了一个基于 Alpine Linux 的 Golang 1.20 镜像。 以下是对 Dockerfile 中每个指令的解释: ```Dockerfile FROM golang:1.20-alpine3.17 ``` 该指令指定了基础镜像为 Golang 1.20 版本的 Alpine Linux 镜像。 ```Dockerfile RUN apk add --no-cache \ ffmpeg ``` 该指令使用 `apk` 包管理器安装了 `ffmpeg` 工具。 ```Dockerfile RUN wget -O /video.mkv http://jell.yfish.us/media/jellyfish-10-mbps-hd-h264.mkv ``` 该指令使用 `wget` 下载了一个名为 `jellyfish-10-mbps-hd-h264.mkv` 的测试视频文件,并将其保存为 `/video.mkv`。 ```Dockerfile WORKDIR /s ``` 该指令将工作目录设置为 `/s`。 ```Dockerfile COPY go.mod go.sum ./ RUN go mod download ``` 该指令将项目的 `go.mod` 和 `go.sum` 文件复制到容器中,并使用 `go mod download` 下载项目的依赖项。 ```Dockerfile COPY . ./ RUN go build -o /mediamtx . ``` 该指令将当前目录的所有文件复制到容器中,并使用 `go build` 构建了一个名为 `/mediamtx` 的可执行文件。 ```Dockerfile COPY bench/publish/start.sh / RUN chmod +x /start.sh ``` 该指令将 `bench/publish/start.sh` 脚本复制到容器的根目录,并使用 `chmod` 命令赋予其可执行权限。 ```Dockerfile ENTRYPOINT [ "/start.sh" ] ``` 该指令设置容器的入口点为 `/start.sh` 脚本。 通过这个 Dockerfile,可以构建一个包含了 Golang 程序、ffmpeg 工具和测试视频文件的镜像。镜像构建过程中会下载依赖并编译 Golang 程序,最后设置容器的入口点为 `start.sh` 脚本。 如果你有任何进一步的问题,请随时提问。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值