硬核,图解bufio包系列之读取原理

发表于 2年以前  | 总阅读数:692 次

今天跟大家分享一篇 bufio 的读取原理。

01 Go中普通的文件读写

首先我们来看看在Go中对文件的普通读取方式是怎么样的。下面是普通的读取文件内容的示例代码:

package main

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

func main() {
    filename := "./test.txt"
    //以读写模式打开文件
    fd, _ := os.OpenFile(filename, os.O_RDWR, 0666)
    b := make([]byte, 2)
    // 从文件中读取最多len(b)个字节的内容到b中
    n, err := fd.Read(b)
    fmt.Printf("n:=%d, b:=%s, err=%+v\n", n, b, err)
}

上面的读取方式是通过文件系统的IO进行读取的,每次都需要一次底层的系统调用,若需要连续多次读取,那么这种方式的效率就会大大降低。如下图:

在Go中,将读写文件的操作抽象成了接口io.Reader和io.Writer,只要实现对应接口的方法即可。如示例中os.OpenFile函数返回的File对象即实现了这两个接口。那有没有什么办法提高读写效率呢?那就是编程中常用的技术--缓存。

02 将文件内容预读取到缓存--bufio

这里的思想很简单,当用户从文件中读取数据的时候,先从文件中读取一大块内容到内存缓冲区,以供后面的读取操作直接从内存缓冲区进行读取,以降低从文件中读取的系统调用次数。如下图所示:

整体思想比较简单。但在bufio中的具体实现中针对不同的场景使用了不同的策略机制。下面我们先看下缓冲区的几种状态,然后再针对缓冲区的每一类读取操作来深入分析其具体的实现策略。

03 缓冲区的状态

缓冲区有三种状态,分别是缓冲区为空、缓冲区未满但有可读取的数据以及缓冲区满的状态。在bufio中,缓冲区本质上是一个字节切片,并通过两个整型变量r和w分别表示可读取以及可写入的索引位置。从文件中每加载一个字节的内容到缓冲区则w+1,从缓冲区每读走一个字节的内容,则r+1。下面我们分别看下三种状态。

1)缓冲区为空的状态

缓冲区为空的状态本质上是指没有内容可读。其判断标准如下:

r == w

r和w相等,意味着已经将写入到缓冲区的内容都读完了。其中最简单的就是r和w都等于0,缓冲区中没有任何内容,如下图所示:

缓冲区为空的状态还有一种情况是缓冲区中有内容,但已经都被读取走了,即r和w相等,如下图:

在这种状态下,当再需要读取内容时,会首先将r和w都置为0,然后从文件中加载新的数据填充到缓冲区中以供下次调用方读取。

2)缓冲区为非空的状态

这种状态是指在缓冲区中有可读的内容,其判断标准如下:

r != w && (w-r) < len(buf)

r != w 说明buf[r:w]这段内容还没被调用方读取。(w-r) < len(buf) 说明buf不是满的状态,还有空间可以继续填充内容。在这种状态下,当程序执行读操作时,会直接从缓冲区中读取。如下图:

上图中,虚线部分表示已经被读走的内容。buf[r:w]这段切片是可读的缓冲内容,在该示例中为buf[2:8]。

3)缓冲区满状态

还有最后一种缓冲区状态,即缓冲区满。其通用的判断标准如下:

(w-r) >= len(buf)

如果要满足上述公式,只有一种情况,即 r=0,表示还没有从缓冲区读走任何内容。w=len(buf),表示从文件中读取的内容已经填满了整个缓冲区。该示例中为w=10,即表示没有任何空闲的空间。如下图所示:

以上就是缓冲区的三种状态,下面我们来开始看看bufio的具体是如何从缓冲区读取数据的。

04 读取特定字节数的操作-- Read([]byte)

我们还是从最简单的读取操作开始。还是上面的例子,我们将其改写成使用bufio的读取操作:

package main

import (
    "bufio"
    "fmt"
    "os"
)

func main() {
    filename := "./test.txt"
    //以读写模式打开文件
    fd, _ := os.OpenFile(filename, os.O_RDWR, 0666)

    //将fd包装到buffer Reader中
    bufioReader := bufio.NewReader(fd)

    p := make([]byte, 2)
    n, _ := bufioReader.Read(p)

    fmt.Printf("n=%d,p=%s", n, p)
}

基本用法看起来和直接从文件中读取差不多,只不过是多包装了一层buffer Reader。下面我们看看其内部的具体实现。

上面提到bufio的基本思想是有一个缓冲区,调用方直接从缓冲区中读取。下图是其初始的状态:

为了方便演示,上图中是一块大小只有5个字节的缓冲区,当然在bufio中实际默认的缓冲区大小是4096字节,即4KB。下面我们看该读取函数在缓冲区的三种状态下各自的读取策略。

场景一:当缓冲区为空状态时的读取逻辑(即r等于w)

在缓冲区为空时,进行读取也有两种情况:

  • 若调用方要读取的字节数 小于 缓冲区的长度,则先填充缓冲区,再从缓冲区中读取。
  • 若调用方要读取的字节数 ≥ 缓冲区的长度,则直接从文件中读取,不填充缓冲区。

下面我们先来看第一种情况:要读取的字节数小于缓冲区的长度。这种情况的读取逻辑是从文件中将内容读取到缓冲区中,将缓冲区填满。然后再从缓冲区读取想要的字节内容。

我们看下下面语句的具体执行过程:

p := make([]byte, 2)
n, _ := bufioReader.Read(p)

这里是要从文件中读取2个字节。即要读取的字节数少于缓冲区的内容字节数。我们看下下图:

第一步是填充缓冲区。因为缓冲区是空的状态,所以先将文件的内容读取到缓冲区。示例中缓冲区的容量为5,所以,会从缓冲区的位置0开始,将缓冲区尽量填满。所谓尽量填满,有以下两种情况:

  • 若文件的可读取的内容字节数大于等于缓冲区容量,那就先将缓冲区填满。
  • 若文件的可读取的内容字节数小于缓冲区容量,则将可读取内容全部读取到缓冲区即可。

第二步,从缓冲区中读取数据。示例中,需要拷贝2个字节到调用者的变量 p 中。如下图:

第三步,移动 r 的位置,以便标记下次从缓存区读取时的开始位置。这次是从缓冲区索引0的位置开始读取的,并且只读取了2个字节,所以下次读取应该是从缓冲区的索引位置2处开始读取。

以上就是要读取的字节数比缓冲区的缓存内容字节数少时的实现逻辑。

现在,我们来看这样一种场景,如果调用者要读取的字节数和缓冲区的字节数相等,按照上面的逻辑,其读取过程如下:

  • 先从文件读取5个字节到缓冲区,这时 w=5,代表下次再写入缓冲区的位置。缓冲区从空的状态转换到满的状态。
  • 然后再将缓冲区的5个字节全部拷贝到 p 中,这时r = 5,代表下次再从缓冲区读取数据的位置。这时缓冲区中的内容都已经被读走了, r 和 w相等。这时缓冲区从满的状态又变成了空的状态。

我们看到,最终缓冲区的状态还是空,数据只不过是在缓冲区中中转了一下而已。因此,更有效的做法应该是从文件直接读取对应的字节数到 调用者的 p 中就行,而不再经过缓冲区。因为最终读取的效果是一样的,但直接读取效率还会更高,因为少了一次从缓冲区到 调用者 p 的拷贝操作。如下图所示:

同样,如果当读取的字节数长度大于缓冲区的长度,也是同样的原理。

场景二:当缓冲区为非空状态的读取逻辑

如果在缓冲区非空状态下进行读取操作时,唯一需要注意的点就是当缓冲区中可读取的内容字节数小于调用者要读取的字节数时,则只能读取缓冲区中的内容。如下是bufio中缓冲区的初始状态,目前只有1字节的内容可读,如下图:

调用者期望读取2个字节的内容,但缓冲区中只有1个字节可读,这时是不再从文件中继续读取的,而是只读取缓冲区的内容即可,如下图:

最后,将实际读取到的字节数返回给调用者,并将下次可读取的索引位置 r 进行更新,如下图:

这时缓冲区的状态实际上是变成了空的状态。如果再继续读取的话,r和w就会复位成0,并从文件中再读取一大块内容填充到缓冲区中。

另外还有一种就是缓冲区满的状态下的读取逻辑,这种场景下就结合场景二进行读取即可。以上实际上就是bufio包中的Read([]byte)函数的逻辑,按字节读取。其实在实际编程中,我们经常会遇到的是按行读取,更通用一点就是一直读取到指定的字符为止。下面我们来看看这种读取操作的实现。

05 从缓冲区中读取到指定位置

这种读取方式是从缓冲区中读取数据,直到遇到指定的字符为止(实际上是指定字符所在的切片索引位置)。按行读取是最常见的场景之一,即一直读取到换行符为止。这种读取方式中也分两种情况:

  • 情况1:当缓冲区中包含指定的字符,则从缓冲区中直接返回包含该字符及之前的有效内容。
  • 情况2:当缓冲区中没有指定的字符,又分两种情况:
  • 2.1 若缓冲区是满的状态,则返回整个缓冲的内容
  • 2.2 若缓冲区处于非空状态(也非满的状态),则将缓冲区填满内容,再从该缓冲区中查找是否存在指定的字符。若在缓冲区中能够查找到指定的字符,则返回该指定字符及以前的内容,否则,返回整个缓冲区的内容,即2.1的情况。

情况1比较简单,假设我们在缓冲区中读取内容,直到遇到指定字符 E为止。缓冲区中的状态如下:

缓冲区中的buf[1:5]这段内容中包含字符E,那么直接返回buf[1:2]的内容即可。

情况2稍微复杂下。下面我们稍微拆解下在缓冲区各种状态下第一次未找到对应的字符的情况。

首先我们看当缓冲区处于满的状态下,第一次未找到对应字符的逻辑。如下:

若在缓冲区中查找字符B,发现没有对应的字符,同时又发现缓冲区状态是满的状态,所以就直接将缓冲区中的所有内容都返回,同时将缓冲区满的错误返回给调用者。如下图,则返回给调用者 HELLOGO!,同时返回errors.New("buffer is full"):

其次,我们看下如果缓冲区里有内容,但未满的状态的查找逻辑。假设缓冲区中下次可读的位置在第5个,如下图:

我们从缓冲区的索引5的位置开始查找字符B,发现没有找到。但同时也发现缓冲区处于非满的状态,因为从索引0到索引5之间的内容已经被读取走了,所以这段内存相当于处于空闲的状态。因此,这里会先将缓冲区中5到8之间的内容移动到0到3之间,然后再从文件中读取一段内容填充到缓冲3到8的位置上,最后继续查找。如下图:

上图是移动完内容之后的结果。然后从文件中读取内容填充剩余的缓冲区,如下图:

这样,缓冲区中又有了新的内容,则会从新的内容中继续查找指定的字符B,如下图: 这里要注意的是0-3之间的内容不再重复查找,只会从3-8之间查找。这里找到了字符B的位置在buf[3:8]这段内容的位置0处。最后返回buf[0:4]的这段内容,因为之前的内容也是搜索的一部分。如下图:

以上在缓冲区中移动内容到开始位置,并重新填充内容到缓冲区的过程实际上就是bufio包中的fill方法。而整个按指定字符读取的过程是bufio包中的ReadLine和ReadSlice函数的对应实现(ReadLine函数调用了ReadSlice函数)。ReadLine函数默认是读取内容,直到遇到第一个换行符\n为止。

我们注意到以上的ReadLine和ReadSlice函数都是在缓冲区中的内容中搜索。只要在缓冲区满的状态下,无论是能否搜索到对应的字符,都会返回。我们知道,文件内容的大小一般都会远远大于缓冲区的大小,那如果在缓冲区满的状态下没有找到对应的字符,如何继续往下查找呢?

06 从全文件中读取到指定位置

这种读取方式是从缓冲区中读取,如果该缓冲区中没有读到指定的字符,那么就将该缓冲区的内容暂存到一个临时区,然后再读取文件将缓冲区填满,再次查找,依次循环,直到读到指定的字符为止或读到文件的末尾,将所有的结果返回给调用者。

假设缓冲区处于满的状态,我们要查找指定的字符 r

第一步先从缓冲区中查找,如下:

第二步,在缓冲区中未找到指定的字符 r,所以需要将缓冲区中的内容移动到暂存区存储起来,以备后续返回时用,如下图:

第三步,这时缓冲区实际处于空的状态,然后需要从文件中读取内容再次填充缓冲区,继续查找 是否有 r字符。如下图: 第四步,继续从缓冲区中查找字符字符r,如果找到了,则将暂存区中的内容及缓冲区中r及之前的内容都返回给调用者。如下图: 如果在第四步中依然没找到指定的字符r,那么就会调回第二步,依次循环,直到找到指定的字符或将文件中所有的内容都扫描完为止,最终将暂存区即缓冲区中的内容都返回。

此过程就是bufio中的ReadString函数即对应的collectFragments函数的实现。有兴趣的同学可以查看对应的源码。

说在最后

由以上可知,bufio是利用局部性原理,通过将文件的内容预先加载到缓存中,以减少IO的系统调用来提高读取性能的。也就是说当读取数据时,只有在缓冲区中能够命中才能提高读取的性能。好了,以上就是我们分享的读取逻辑。下一篇文章我们继续解析bufio中的写入逻辑的实现。

本文由哈喽比特于2年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/Lpmm-KwVZTFQ2jy_GROdiQ

 相关推荐

刘强东夫妇:“移民美国”传言被驳斥

京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。

发布于:1年以前  |  808次阅读  |  详细内容 »

博主曝三大运营商,将集体采购百万台华为Mate60系列

日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。

发布于:1年以前  |  770次阅读  |  详细内容 »

ASML CEO警告:出口管制不是可行做法,不要“逼迫中国大陆创新”

据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。

发布于:1年以前  |  756次阅读  |  详细内容 »

抖音中长视频App青桃更名抖音精选,字节再发力对抗B站

今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。

发布于:1年以前  |  648次阅读  |  详细内容 »

威马CDO:中国每百户家庭仅17户有车

日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。

发布于:1年以前  |  589次阅读  |  详细内容 »

研究发现维生素 C 等抗氧化剂会刺激癌症生长和转移

近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。

发布于:1年以前  |  449次阅读  |  详细内容 »

苹果据称正引入3D打印技术,用以生产智能手表的钢质底盘

据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。

发布于:1年以前  |  446次阅读  |  详细内容 »

千万级抖音网红秀才账号被封禁

9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...

发布于:1年以前  |  445次阅读  |  详细内容 »

亚马逊股东起诉公司和贝索斯,称其在购买卫星发射服务时忽视了 SpaceX

9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。

发布于:1年以前  |  444次阅读  |  详细内容 »

苹果上线AppsbyApple网站,以推广自家应用程序

据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。

发布于:1年以前  |  442次阅读  |  详细内容 »

特斯拉美国降价引发投资者不满:“这是短期麻醉剂”

特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。

发布于:1年以前  |  441次阅读  |  详细内容 »

光刻机巨头阿斯麦:拿到许可,继续对华出口

据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。

发布于:1年以前  |  437次阅读  |  详细内容 »

马斯克与库克首次隔空合作:为苹果提供卫星服务

近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。

发布于:1年以前  |  430次阅读  |  详细内容 »

𝕏(推特)调整隐私政策,可拿用户发布的信息训练 AI 模型

据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。

发布于:1年以前  |  428次阅读  |  详细内容 »

荣耀CEO谈华为手机回归:替老同事们高兴,对行业也是好事

9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI操控无人机能力超越人类冠军

《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI生成的蘑菇科普书存在可致命错误

近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。

发布于:1年以前  |  420次阅读  |  详细内容 »

社交媒体平台𝕏计划收集用户生物识别数据与工作教育经历

社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”

发布于:1年以前  |  411次阅读  |  详细内容 »

国产扫地机器人热销欧洲,国产割草机器人抢占欧洲草坪

2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。

发布于:1年以前  |  406次阅读  |  详细内容 »

罗永浩吐槽iPhone15和14不会有区别,除了序列号变了

罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。

发布于:1年以前  |  398次阅读  |  详细内容 »
 相关文章
Android插件化方案 5年以前  |  237227次阅读
vscode超好用的代码书签插件Bookmarks 2年以前  |  8063次阅读
 目录