当前位置: 首页 > 工具软件 > Bible Reader > 使用案例 >

io.Reader: 《Go语言圣经》练习7.5实现一个LimitReader

冯鸿光
2023-12-01

题目解答见:练习题答案
其余部分为相关只是补充

题目:
io包里面的LimitReader函数

  • 接收一个io.Reader接口类型的r和字节数n,
  • 并且返回另一个从r中读取字节但是当读完n个字节后就表示读到文件结束的Reader
  • 实现这个LimitReader函数:

io.Reader

io.Reader是一个接口,任何实现了Read()函数的对象,都可以作为Reader来使用

type Reader interface{
    Read(p []byte) (n int, err error)
}

strings.NewReader

strings.NewReader实现了将一个普通的字符串创建一个Reader

下面是创建的源码:

Reader的结构体

type Reader struct {
	s        string //资源 : 要读取的字符串
    i        int64 // current reading index
	prevRune int   // index of previous rune; or < 0
}
  • i : 一个对象并不是一次性读完,i用来记录每次读到了哪里

实现Read()方法

  • 执行一次读取 len(b) 个字符
  • r.i 移动len(b) 个字符
  • 返回本次读取的字符数量
  • 如果遇到读取完毕,返回一个io.EOF
func (r *Reader) Read(b []byte) (n int, err error) {
	if r.i >= int64(len(r.s)) { //如果当前index大于s的长度则说明读到了结尾
		return 0, io.EOF
	}
	r.prevRune = -1 //?
	n = copy(b, r.s[r.i:]) // 将剩余字符串赋值给b []byte ; n = 复制了多少个字符
	r.i += int64(n) //r 读走了b的长度这么多个字符
	return 
}
  • n = copy(b, r.s[r.i:]) : 将从最初位子r.i 到末尾位子len(b)+ r.i复制过去,并返回复制了多少个字符

    这里copy(b,...) 可以从外面获取到b, 引用传递

  • r.i 读取位子后移

string.NewReader的使用

使用NewReader() 读取一个字符串为Reader,

Reader使用Read()方法每次读取p个字符

// 根据原始的io.Reader创建一个字符读取器
reader := strings.NewReader("123456789")

p := make([]byte, 2) // 选择每次读取多少个字符

// 每次读取两个字符,直到读取完毕为止
for {
    n, err := reader.Read(p)
    if err != nil { // 读取出错or到了结尾
        if err == io.EOF {
            fmt.Println("读到结尾啦")
            break
        }
        fmt.Println("出错")
        os.Exit(1)
    }
    fmt.Printf("本次读取内容 %s , %d\n", p[:], n) // 获取到p的内容
}

输出

本次读取内容 12 , 2
本次读取内容 34 , 2
本次读取内容 56 , 2
本次读取内容 78 , 2
本次读取内容 98 , 1
读到结尾啦

练习题答案

io包里面的LimitReader函数

  • 接收一个io.Reader接口类型的r和字节数n,
  • 并且返回另一个从r中读取字节但是当读完n个字节后就表示读到文件结束的Reader
  • 实现这个LimitReader函数:
func LimitReader(r io.Reader, n int64) io.Reader
func main() {
	r := strings.NewReader("1234567890")
	reader := LimitReader(r, 8) // 限制最多一共读取8个字符

	p := make([]byte, 2) // 每次读取两个字符
	for {
		n, err := reader.Read(p)
		if err != nil {
			if err == io.EOF {
				fmt.Println("已经读到结尾!")
				break
			}
			// 读取出错
			log.Fatalf("读取出错 %v", err) // equivalent print and call exit()
		}
		fmt.Printf("本次读取%d个字符 : %s\n", n, p[:])
	}
}

type LimitedReader struct {
	io.Reader
	N int // 最多读取N个字符
}

// 实现Reader里面的Read() : 功能是可以读取n个字符就表示出结束符
func (l *LimitedReader) Read(p []byte) (n int, err error) {
	// 结束读取
	if l.N <= 0 { // 什么时候会< 0 : 传入的数字本身就<0
		err = io.EOF
		return
	}
	if len(p) > l.N { // 可以一次读到结尾
		p = p[:l.N] // 减小p的大小为l.N
	}
	n, err = l.Reader.Read(p)
	l.N -= n
	return
}
func LimitReader(r io.Reader, n int) io.Reader {
	return &LimitedReader{r, n}
}

输出

本次读取2个字符 : 12
本次读取2个字符 : 34
本次读取2个字符 : 56
本次读取2个字符 : 78
已经读到结尾!

官方的LimintReader

func LimitReader(r Reader, n int64) Reader { return &LimitedReader{r, n} }

// A LimitedReader reads from R but limits the amount of
// data returned to just N bytes. Each call to Read
// updates N to reflect the new amount remaining.
// Read returns EOF when N <= 0 or when the underlying R returns EOF.
type LimitedReader struct {
	R Reader // underlying reader
	N int64  // max bytes remaining
}

func (l *LimitedReader) Read(p []byte) (n int, err error) {
	// 判断结尾的标志 : 相当于strings.Reader里面的 i : index 记录当前读到的位子
	if l.N <= 0 {
		return 0, EOF
	}
	// p的容量大于N : 一次可以读完
	if int64(len(p)) > l.N {
		p = p[0:l.N]
	}
	// 一次不能读完
	n, err = l.R.Read(p)
	// 当前还能再次读取的字符 : 
	l.N -= int64(n)
	return
}
 类似资料: