看似合理的io.Reader 接口

GO语言基础教程(173)Go IO操作之bufio库:Go语言bufio库详解:给IO加上缓冲,速度直接起飞! 在编程世界中,IO操作往往是性能瓶颈之一。想象一下,如果你每次想吃一颗葡萄都要跑去厨房,大部分时间都会浪费在路上了。更聪明的方法是拿一个碗装满葡萄,然后带回房间慢慢享用。Go语言的bufio包就是这个"碗",它通过缓冲机制大幅减少系统调用次数,让IO操作效率飙升。 阅读详情

在项目中,需要实现了一个流式的 io.Reader 接口,每次 Read 返回一个 rune 的字节切片,相当于实现流式的效果。后面对其测试,确保有效且不会出现乱码。正常使用 Read() 方法是符合预期的,但是,当我使用非流式的方法,也就是 io.ReadAll(r) 时,却出现了乱码。

// text_reader.go
type textReader struct {
	buf bytes.Buffer
}

func NewTextReader(s string) io.Reader {
	return &textReader{
		buf: *bytes.NewBufferString(s),
	}
}

func (r *textReader) Read(p []byte) (n int, err error) {
	if len(p) == 0 {
		return 0, nil
	}
	text, _, err := r.buf.ReadRune()
	if err != nil {
		return 0, err
	}
	b := make([]byte, 4)
	n = utf8.EncodeRune(b, text)
	return copy(p, b[:n]), err
}
// text_reader_test.go
var text string
func Test_textReader_Read(t *testing.T) {
	t.Run("", func(t *testing.T) {
		r := NewTextReader(text)
		tl := 0
		for {
			buf := make([]byte, 64)
			l, err := r.Read(buf)
			t.Logf("got data len: %d, body: %s, err: %v", l, string(buf[:l]), err)
			tl += l
			if err == io.EOF {
				break
			}
			if err != nil {
				t.Errorf("got error: %v", err)
			}
		}
		t.Log("got total len: ", tl)
	})
	t.Run("", func(t *testing.T) {
		r := NewTextReader(text)
		buf, err := io.ReadAll(r)
		t.Logf("got data body: %s, len: %d", string(buf), len(buf))
		if err != nil && err != io.EOF {
			t.Errorf("got error: %v", err)
		}
	})
}

后面深入到 io.ReadAll 源码中,发现 io.ReadAll 的实现并不长,并且会在一开始初始化一个长度为 512 的字节切片。

func ReadAll(r Reader) ([]byte, error) {
	b := make([]byte, 0, 512)
	for {
		if len(b) == cap(b) {
			// Add more capacity (let append pick how much).
			b = append(b, 0)[:len(b)]
		}
		n, err := r.Read(b[len(b):cap(b)])
		b = b[:len(b)+n]
		if err != nil {
			if err == EOF {
				err = nil
			}
			return b, err
		}
	}
}

遇到这种必现的问题,一般首先是将 Case 尽可能简化。后面的现象就是当初始化 textReader 的字符串长度足够小时, io.ReadAll 就不会出现乱码。然后不免让人想到长度是否和 io.ReadAll 中初始化长度的 512 相关,后面使用 171 个相同的汉字组成的字符串,作为输入,字节总长度为 513。果然在最后就出现了乱码:

在这里插入图片描述

并且返回的长度为 512,从日志可以看出来,io.ReadAll() 最后一次调用 textReader.Read() 时,p 的长度只有 2 ,因此一个 3 字节的汉字被截断成乱码了。因此问题定位出来了:

textReader.Read() 在 copy 之前并没有去检查 p 的大小,导致一个3字节的汉字被截断

那么是我实现的 io.Reader 这个接口不符合规范吗?我在 copy(p, b[:n]) 之前应该要对 p 进行扩容操作吗?OK,看下 io.Reader 的接口定义

// Reader is the interface that wraps the basic Read method.
//
// Read reads up to len(p)len(p)len(p) bytes into p. It returns the number of bytes
// read (0<=n<=len(p))(0 <= n <= len(p))(0<=n<=len(p)) and any error encountered. Even if Read
// returns n < len§, it may use all of p as scratch space during the call.
// If some data is available but not len(p)len(p)len(p) bytes, Read conventionally
// returns what is available instead of waiting for more.

io.Reader 接口要求最多读取 len(p) 个字节,这么看来,确实是 textReader.Read() 实现的不合理,另外 io.Reader 还规定了其他的行为,详情可以看 io.Reader 的注视。因此正确的 textReader.Read() 应该是这样:

type textReader struct {
	buf []byte
	src bytes.Buffer
}

func NewTextReader(s string) io.Reader {
	return &textReader{
		src: *bytes.NewBufferString(s),
		buf: make([]byte, 0, 4),
	}
}

func (r *textReader) Read(p []byte) (n int, err error) {
	if len(p) == 0 {
		return 0, nil
	}
	if len(r.buf) > 0 {
		copy(p, r.buf[:1])
		r.buf = r.buf[1:]
		return 1, nil
	}
	text, _, err := r.src.ReadRune()
	if err != nil {
		return 0, err
	}
	b := make([]byte, 4)
	n = utf8.EncodeRune(b, text)
	if n > len(p) {
		r.buf = append(r.buf, b[len(p):n]...)
		n = len(p)
	}
	return copy(p, b[:n]), err
}

上面使用了 []byte 来存储被截断的字符,并且在 copy 的时候检查 len(p),并将被截断的剩余字节放入 buf 中,等待下一次 Read() 的调用。当然这个地方 buf 只是一个 FIFO (先进先出)的队列,并且可以保证的是长度不会超过 4 ,因此完全可以使用一个循环数组来优化:

package main

import (
	"bytes"
	"io"
	"unicode/utf8"
)

type textReader struct {
	buf circleQueue
	src bytes.Buffer
}

func NewTextReader(s string) io.Reader {
	return &textReader{
		src: *bytes.NewBufferString(s),
	}
}

func (r *textReader) Read(p []byte) (n int, err error) {
	if len(p) == 0 {
		return 0, nil
	}
	if b, err := r.buf.ReadByte(); err == nil {
		p[0] = b
		return 1, nil
	}
	text, _, err := r.src.ReadRune()
	if err != nil {
		return 0, err
	}
	b := make([]byte, 4)
	n = utf8.EncodeRune(b, text)
	if n > len(p) {
		for i := len(p); i < n; i++ {
			r.buf.WriteByte(b[i])
		}
		n = len(p)
	}
	return copy(p, b[:n]), err
}

type circleQueue struct {
	buf  [8]byte
	head int
	tail int
}

func (q *circleQueue) ReadByte() (byte, error) {
	if q.head == q.tail {
		return 0, io.EOF
	}
	b := q.buf[q.head]
	q.head++
	if q.head >= 8 {
		q.head = 0
	}
	return b, nil
}

func (q *circleQueue) WriteByte(b byte) {
	q.buf[q.tail] = b
	q.tail++
	if q.tail >= 8 {
		q.tail = 0
	}
}

至此,我们就解决了这个看似合理的 io.Reader 接口在使用 io.ReadAll() 时出现乱码的 BUG,原因在于 io.Reader 并没有按照规定去实现 Read(io.Reader)(int, error) 方法。

【论文笔记】AS Reader vs Stanford Attentive Reader Attention Sum Reader Network 数据集 CNN&DailyMail 每篇文章作为一个文档(document),在文档的summary中剔除一个实体类单词,并作为问题(question),剔除的实体类单词即作为答案(answer),该文档中所有的实体类单词均可为候选答案(candidate answers)。其中每个样本将文本中所有的命名实体用类似“... 阅读详情

相关推荐

无源低通滤波器

无源RC滤波器“滤除”不需要的信号,因为它们分离并允许仅通过其频率的正弦输入信号,最简单的是无源低通滤波器网络。在低频应用(高达100kHz)中,无源滤波器通常使用简单的RC(电阻-电容)网络构建,而高频滤波器(高于100kHz)通常由RLC(电阻-电感-电容)元件制成。无源滤波器由电阻器、电容器和电感器等无源元件组成,没有放大元件(晶体管、运算放大器等),因此没有信号增益,因此它们的输出电平始终小于输入。滤波器是根据它们允许通过它们的信号的频率范围来命名的,同时阻挡或“衰减”其余部分。

weixin_44448586的博客 1万+

io.Reader 解析

简介 io.Reader 是一个 Interface 类型,功能非常强大,在任何需要读的地方我们都尽量使用它。先来看下它的原型: type Reader interface { Read(p []byte) (n int, err error) } 可见,任何实现了 Read() 函数的对象都可以作为 Reader 来使用。 Reader 类型 标准库中有许多不同的 Reader...

cbmljs的博客 5467

海康/大华设备如何接入GB28181平台?最新2022版互联避坑指南

本文详细解析了海康/大华设备接入GB28181平台的最新2022版互联避坑指南,涵盖参数配置、实时视音频点播协议交互及典型故障排查。重点介绍了GB/T28181-2022标准下的设备编码规范、SIP服务器配置及媒体流保活机制,帮助工程团队高效解决设备离线和媒体流中断等问题。

weixin_30733003的博客 686

golang之bufio包的使用

原文地址:http://www.niu12.com/article/38 github地址:https://github.com/ZQCard/go_api_practice // 参考:https://www.cnblogs.com/golove/p/3282667.html// bufio 包实现了带缓存的 I/O 操作// 它封装一个 io.Reader 或 io.Writer...

diaoliu7156的博客 514

DDR核心机制解析:从Burst到Prefetch的性能跃迁

本文深入解析了DDR内存性能跃迁的核心机制,重点阐述了Burst(突发传输)与Prefetch(预取)技术的协同工作原理。Burst机制通过连续传输数据减少访问延迟,而Prefetch则通过内部预取数据提升吞吐量,两者结合完美匹配现代CPU缓存行需求,是DDR3等内存实现高效带宽的关键。

weixin_29232121的博客 135

Context Capture10.20 安装教程(附安装包)

ContextCapture 是一款由Bentley公司推出的实景建模软件,可以快速为各种类型的基础设施项目生成最具挑战性的反应现实环境的三维模型。

一起来交流学习吧 2万+

Go编程技巧--io.Reader/Writer

Reader 接口仅有一个 Read 方法,所以只要实现了Read(p []byte) (n int, errerror)方法的类型,我们就认为其类型就是 Reader。Read 方法会把读取的数据复制到 p 中 ,一次只能读取最高不超过len(p)的数据,并不保证一定会读满整个 p,读取的数据量返回到n,并在读完的时候返回 io.EOF 到 err。Read 方法可以从不同来源读取数据,这个来源可能是文件、网络传输的数据或者普通的字符串,io.Reader。

赵凯月的博客 1186

ArcGIS中地理坐标系与投影坐标系以及投影转换

简单介绍arcgis软件中坐标系定义、设置和常见问题。

weixin_41399650的博客 8万+

go语言中io操作中的 io.Reader 和 io.Writer的获取方法 总结

我们在对文件进行io操作的时候,经常看到需要我们传递一个 io.Reader 或者 io.Writer 对象作为读写的入参, 那么我们该如何或者这些个RW对象呢? 其实很简单,你只需要查找一下哪些对象实现了 Read或者 Writer方法,那么你只需要创建一个实现了这2个方法之一的对象 , 那他就可以是一个 io.Reader 或者 io.Writer 。当然最常见的应该就是我们的 os.File对象了, 另外还有 bufio.Reader, bytes.Buffer 等对象都可以作为io的RW入参。

Tekin 是深耕技术 20 年的全栈实战派专家,精通 Go/Python/Java 等多语言开发。博客专注技术原理与实战结合,深度解析 Python 高阶编程、Go 语言架构、数据库优化等硬核内容。涵盖并发编程、机器学习、云原生等前沿领域,通过真实案例拆 1283

秋叶ComfyUI整合包使用FRP实现公网访问教程 ComfyUI内网穿透 秋叶整合包

最近在做 ComfyUI 的实验时,我发现很多小伙伴都遇到一个问题:本地启动的 ComfyUI 服务只能在本地访问,想要通过公网远程访问,就必须借助来实现。作为一名前端开发工程师,我花了点时间把 ComfyUI 启动器和 FRP 配置整合了一下,今天就分享给大家。本文适合已经了解。需要注意的是 穿透到公网的话任何人都能访问 为了安全建议看我另一篇帖子加上密码验证 防止恶意攻击。

代码简单说 Vue、JAVA、PHP、Node.js 熟练运用,接口、架构、性能全搞定。 1347

S7-1500——(一)西门子PLC编程从入门到精通4、SCL间接寻址

学习目标是在掌握博途平台SCL编程语言的基本使用基础之上,进一步了解SCL语言环境下的间接寻址过程,初步具备编写复杂控制程序的能力;在编写plc程序过程中, 需要对plc中的数据进行访问,这就是寻址;直接寻址间接寻址;直接寻址直接寻址就是直接对数据的存储单元进行访问;根据表达方式的不同,可分为绝对寻址和符号寻址;对于plc中的标准数据存储区包括IQMTCL以及标准数据块,既可以使用绝对寻址,也可以使用符号寻址;绝对寻址在使用绝对寻址时,可以寻址的宽度包括位,字,节字,双字;

大小曲奇的博客 2121

一文掌握 Go 语言 I/O 操作中的 io.Reader 和 io.Writer

io.Reader:用于从数据源读取数据,Read方法将数据读入给定的字节切片。io.Writer:用于将数据写入目标,Write方法将数据写入指定的目标。通过 `

每日一贴 1624

基于simulink的BMS系统中电池单体故障隔离与恢复建模

本文介绍了使用Simulink构建BMS电池单体故障诊断与容错控制模型的方法。主要内容包括:1)搭建12串锂电池组模型;2)实现单体故障注入(过压、断线等);3)设计故障诊断与隔离模块(采用Stateflow状态机);4)开发恢复判断逻辑(需满足电压、温度等条件);5)系统监控与仿真设置。文章还提供了工程实践建议,如防环流设计、通信冗余等,并指出进阶方向(多故障处理、AI预警等)。该模型适用于新能源汽车、储能系统的BMS设计,可提高系统可靠性和安全性。

xiaoheshang_123的博客 140

java reader怎么输出字符串_Java基础:Java IO流学习总结

IO是指对数据流的输入和输出,也称为IO流,IO流主要分为两大类,字节流和字符流。字节流可以处理任何类型的数据,如图片,视频等,字符流只能处理字符类型的数据。 IO流的本质是数据传输,并且流是单向的。Java流操作有关的类或接口:Java流类图结构IO流的分类字符流和字节流字符流的由来: 因为数据编码的不同,而有了对字符进行高效操作的流对象。本质其实就是基于字节流读取时,去查了指定的码表。 字节流...

weixin_39715460的博客 128

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

Go-Plugin复杂数据类型传输终极指南:接口、Reader/Writer的完整解决方案

🚀 想要在Go插件系统中优雅地处理复杂数据类型?go-plugin项目提供了完整的解决方案!作为HashiCorp旗下多个知名工具(如Terraform、Vault、Packer)的核心插件系统,go-plugin已经经过数百万台机器的实战检验,为复杂参数和返回值提供了强大的传输能力。 ## 🔍 为什么需要复杂数据类型传输? 在传统的RPC系统中,简单的基本类型传输相对容易,但当涉及到**

gitblog_00102的博客 926

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

Java的IO

了解Java IO流 Java的IO流是实现输入/输出的基础,它可以方便地实现数据的输入/输出操作,在Java中把不同的输入/输出源(键盘、文件、网络连接等)抽象表述 为“流”(stream),通过流的方式允许Java程序使用相同的方式来访问不同的输入输出源。 stream是从起源(source)到接收(sink)的有序数据。影响IO性能的无非就是两大因素:数据的格式及存储的方式,前两类主要是数据...

qq_43053196的博客 225

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

java io流常用接口_javaIO流接口總結

Java流操作有關的類或接口: Java流類圖結構: 流的概念和作用流是一組有順序的,有起點和終點的字節集合,是對數據傳輸的總稱或抽象。即數據在兩設備間的傳輸稱為流,流的本質是數據傳輸,根據數據傳輸特性將流抽象為各種類,方便更直觀的進行數據操作。IO流的分類根據處理數據類型的不同分為:字符流和字節流根據數據流向不同分為:輸入流和輸出流字符流和字節流字符流的由來: 因為數據編碼的不同,而有了對字符進...

weixin_42528534的博客 717

java util io_Java IO详解(转)

IO是Java及众多编程语言很重要的一块,同时很多程序的瓶颈和耗时操作也都在IO这块。一、简介IO操作面临很多问题,信息量的巨大,网络的环境等等,因为IO不仅仅是对本地文件、目录的操作,有时对二进制流、还有一部分是网络方面的资源,所以多种原因直接造成IO操作无疑是耗时且复杂多变的。Java对IO的支持是个不断的演变过程,经过了很多的优化,直到JDK1.4以后,才趋于稳定,在JDK1.4中,加入了n...

weixin_33820059的博客 315

Go接口 - interface 实践

interface是GO语言中非常重要的类型,它是用来定义一类方法集,只表示对象的行为(Behavior),GO语言的接口和实现不需要显示关联(也就是常说的duck类型),只要实现了接口所有方法,就可以当做该接口的一个实现,赋值给所有引用该接口的变量,从而满足面向对象编程(OOP)中的两个非常重要原则:依赖倒置、里氏替换。 也正由于这个特点,所以GO接口最佳的实践是:接口尽量的小,根据实际的需求定义的接口大小。 例如:io包体的Reader/Writer type Reader interface {

编程是艺术,极致是信仰,分享是连接世界的方式。 489

Java IO

java.io 1、基于字节操作的I/O接口:InputStream和OutputStream 2、基于字符操作的I/O接口:Writer和Reader 3、基于磁盘操作的I/O接口:File 4、基于网络操作的I/O接口:Socket(不在java.io包下) 影响IO性能的无非就是两大因素:数据的格式及存储的方式,前两类主要是数据格式方面的,后两个类是存储方式...

dazhenhao6183的博客 93

Apache Pulsar Go Client Reader API实战:从指定位置读取消息的完整教程

Apache Pulsar Go Client Library提供了强大的Reader API,使开发者能够灵活地从指定位置读取Pulsar主题中的消息。本教程将详细介绍如何使用Reader API实现从特定位置读取消息的功能,帮助新手快速掌握这一实用技能。 ## 什么是Pulsar Reader API? Pulsar Reader API是Apache Pulsar Go客户端库中的一个核

gitblog_00970的博客 949

IO流-简介

一:概念 IO流:input和output的简称,用来处理设备之间数据的传输。 java对数据的操作是通过流的方式 Java用于操作流的对象都在IO包中 流按照操作数据分为两种:字符流和字节流 流按流向分为:输入流和输出流 二:字符流的由来ascii 由于计算机只能识别二进制数据 ,故早期的流都是二进制的,像媒体文件等所有的文件都是可以通过字节流来处理的,后来人们将文本类的文件单独分离出来,这些文本文件经常使用,就弄了个字符流,以美国为例,将abcd等26个字母和一些其他...

qq_32521381的博客 225

3步解锁AI阅读能力:Jina Reader实战指南

在构建AI应用时,你是否经常遇到这样的困境?LLM无法直接访问最新的网络信息,网页内容提取复杂且容易出错,PDF和Office文档处理更是让人头疼。传统爬虫需要处理反爬机制、JavaScript渲染、动态内容加载等一系列技术难题,而Jina Reader正是为解决这些问题而生的智能解决方案。这个开源工具能够将任何URL转换为AI友好的输入格式,让你的LLM应用获得前所未有的信息获取能力。 ##

gitblog_00477的博客 1075

Java基础教程之IO操作·上

本文详细介绍了Java中的文件操作类和IO流技术。主要内容包括:1. File类的基本用法,涵盖文件创建、删除、路径处理等操作;2. 字节流(InputStream/OutputStream)和字符流(Reader/Writer)的体系结构和使用方法;3. 两类流的区别:字节流直接操作文件,字符流需经缓冲区处理;4. 实际开发中的注意事项,如资源关闭、中文处理等。文章通过丰富示例展示了各类API的使用场景,并强调了根据需求选择合适流类型的重要性,为Java文件IO编程提供了全面指导。

小二丶的博客 603

java基础:IO流学习总结

Java IO流学习总结 Java流操作有关的类或接口: Java流类图结构: 流的概念和作用 流是一组有顺序的,有起点和终点的字节集合,是对数据传输的总称或抽象。即数据在两设备间的传输称为流,流的本质是数据传输,根据数据传输特性将流抽象为各种类,方便更直观的进行数据操作。 IO流的分类 根据处理数据类型的不同分为:字符流和字节流- 根据数据流向不同分为:输入流和输出流 字符流和字节流 字...

人在码途,逐日拾光 202

Go语言必知必会100问题-05 接口污染

对于软件开发人员来说,根据当前情况猜测以后可能有什么需求,来构建完美的抽象,过度设计代码是很常见的,应该避免这样做,因为在大多数情况下,会用不必要的抽象污染当前的代码。这句话想表达的意思是如果没有直接的原因,我们不应该首先在代码中创建抽象,不应该使用接口进行设计,而是等待具体的需求。假设我们需要实现将一个文件内容复制到另一个文件中的函数,我们可以创建一个特定的函数,将两个 *os.File作为输入, 或者可以选择使用io.Reader和io.Writer接口创建一个更通用的函数。答案是它使代码流更加复杂。

数据小冰的博客 931
上一篇: 服务压测的心路历程——解决数据竞争与空闲连接问题
下一篇: golang的HTTP流式客户端实现方法与对比
Corleo
博客等级 码龄11年 120粉丝 32原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值