ringbuffer原理_hashset数据结构

ringbuffer原理_hashset数据结构本篇介绍一种简单高效的数据缓存结构:RingBuffer,这种结构实现起来只需要几行代码即可,但使用场景却很广泛,比如在Linux内核中网络数据包的缓存,系统日志的存储等多处使用过该结构。同时它也被广泛的应用于异步通信以及嵌入式设备中,提供高效的数据缓存读写操作。1.实现原理RingBufferr实现比较简单,基本上只需要一个数组结构,外加两个用于存储位置信息的变量即可。其中的数组采用固定大小容量,便于重用内存,不会出现动态内存不断分配和销毁的情况,这对于一些GC类编程语言来说,大…


Jetbrains全系列IDE使用 1年只要46元 售后保障 童叟无欺

本篇介绍一种简单高效的数据缓存结构: RingBuffer, 这种结构实现起来只需要几行代码即可,但使用场景却很广泛,比如在Linux内核中网络数据包的缓存,系统日志的存储等多处使用过该结构。同时它也被广泛的应用于异步通信以及嵌入式设备中,提供高效的数据缓存读写操作。

 

1. 实现原理

 

RingBufferr实现比较简单,基本上只需要一个数组结构,外加两个用于存储位置信息的变量即可。其中的数组采用固定大小容量,便于重用内存,不会出现动态内存不断分配和销毁的情况,这对于一些GC类编程语言来说,大幅减少了内存管理的成本。

下面就是一个典型的RingBuffer图例,包含一个容量大小为6的数组,以及一个读和写指针。其中Write和Read用于管理读写的位置序列,读写开始后,不断增加该值来定位下一次的读写位置。

ringbuffer原理_hashset数据结构

 

每次写数据:在Write对应位置写入新值,并向前移动对应的Write指针位置,如果遇到指针已经处于尾部,则移动到最开始位置,形成一个环形, 类似于双向链表。

每次读取数据:在Read位置读取当前值,并移动Read位置,同样如果遇到已经到达尾部,则返回到最开始的初始位置。

整个数据流的读写过程就是通过不断的操作Write和Read来实现数据的高效处理。

 

2. 读写操作实例

这里我们通过一个简单的实例来介绍如何操作RingBuffer来管理数据: 首先初始化一个空的数组,并设置Read=0和Write=0, 图例中黄色代表已写入的数据,绿色代表已读取的数据,红色代表异常情况:

(1) 写入三个元素分别是:1,2,3, 这时候读指针位置不变,写指针移动三个位置到索引为3的位置(数组索引位置从0开始)

ringbuffer原理_hashset数据结构

(2)读取一个元素,读指针移动一个位置,写指针不变,获取数据值1。

ringbuffer原理_hashset数据结构

(3)继续写入四个元素,分别是:4,5,6,7, 其中4,5,6分别放入剩余的数组空缺中,但是7 由于已经没有位置可写,则从0开始覆盖原有写入1的位置。注意这里我们没有设置write=0,而是直接在原Write值上继续加1,我们取模size即可获得新的写入位置(取模后重新返回头部)。

ringbuffer原理_hashset数据结构

(4)当我们再次写入两个值:8,9的时候,由于与上一轮的Read发生了交叉,为了保证前后读写的顺序性,我们需要同时移动读指针的位置,使得读位置总是指向最旧的数据。

ringbuffer原理_hashset数据结构

(5)这时候如果读取两个数据,则读指针只需要按照当前序列向前移动两个位置即可,分别获得值4,5 代表了最早的数据项,假如上面我们没有移动读指针,则读取的可能会是最新数据。

ringbuffer原理_hashset数据结构

(6)如果我们这时候读取速度加快,假如读取5个值,可成功读取6,7,8,9,当读取到4值的时候由于此时,读写位置重叠(读数据不能超过写数据的位置,否则重复读取的问题),无法进一步读取数据。退出读取流程

ringbuffer原理_hashset数据结构

后面的处理逻辑与上面保持一致即可,这里需要注意的是读写指针的序列值与实际读写数组序列值的映射关系,以及写操作会推动读指针向前移动的过程。

 

3. 代码实现

 

这里我们通过一个简单程序来实现上面的逻辑,采用Go语言实现,主要的处理逻辑部分很容易复制到其他的编程语言。

首先定义一个结构体来存储数据集合,并创建一个初始化函数,其参数就是该RingBuffer的容量大小。

type RingBuffer struct {
   data        []int
   size        int64
   writeCursor int64
   readCursor  int64
}

func NewRingBuffer(size int64) (*RingBuffer, error) {
   if size <= 0 {
      return nil, errors.New("size must be positive")
   }
   rb := RingBuffer{
      data: make([]int, size),
      size: size,
   }
   return &rb, nil
}

 

写入数据的流程通过Write函数来实现主要流程为:

  1. 通过指针序列号,取模获取写位置索引

  2. 写入数据到指定的索引位置

  3. 比较读写位置的索引号,如果两个指针序列号相差一个周期,则读指针序列值+1

  4. 写指针序列值+1

 

对于上面介绍的插入过程,我们其实可以设置不同的策略来应对读写位置重叠情况(写位置-读位置=容量)的情况,

  1. 忽略新的插入

  2. 覆盖旧的数据(上面的实例实现)

  3. 阻塞写入,直到有空间写入新的数据

  4. 重新分类数组,并复制到新的更大的数组

不同的情况可以满足不同的使用场景和需求,需要根据实际业务需求来选择。

func (rb *RingBuffer) Write(x int) {
   rb.data[rb.writeCursor%rb.size] = x
   if rb.writeCursor - rb.readCursor  == rb.size {
      rb.readCursor++
   }
   rb.writeCursor = rb.writeCursor + 1
}

 

读取指针,由于涉及到可能存在的读位置大于写位置的情况,所以我们引入错误来捕获这种异常情况,流程如下:

  • 如果读指针大于等于写指针则报错误,数据不可读取

  • 否则取模当前读指针,获取索引位置

  • 获取该位置的数据

  • 读指针序列值+1

func (rb *RingBuffer) Read() (int, error) {
   if rb.writeCursor > rb.readCursor {
      temp := rb.data[rb.readCursor%rb.size]
      rb.readCursor++
      return temp, nil
   }
   return 0, errors.New("read pointer great then write, wait seconds")
}

4. 使用场景

 

我们可以设想一下,假如我们使用链表或者容量可变的数组去存储上面的数据流,会出现哪些问题?

如果我们的数据流写入速度特别快,而读取的比较慢,则可能出现内存不断增长,甚至最终可能会导致服务OOM而崩溃.  而RingBuffer使用一个固定大小的数组,除了降低了动态内存分配的开销,还会限制最大内存占用容量,简直就是一举两得,除了可能会丢数据的问题。

其实算法的选择本身就是一种权衡选择,就像我们之前讲过的空间换时间的权衡,而在这里则是通过牺牲一部分数据完整性,来提升应用的可用性,想想布隆过滤器这种概率算法也应该是属于这一类。

对于并发访问的使用场景,我们可以通过锁或者信号量的机制提供安全的并发访问管理。另外也存在一些LockFree的RingBuffer实现,感兴趣的话可以自行搜索一下。

如果大家感兴趣可以订阅我的公众号: 银河系算法指南,获得更多有意思的算法知识。

ringbuffer原理_hashset数据结构

 

 

 
 
 

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/195290.html原文链接:https://javaforall.net

赞 (0)
全栈程序员-站长的头像全栈程序员-站长

相关推荐

  • f1 score是什么_F1值

    f1 score是什么_F1值F1score是一个平均数;对精确率与召回率进行平均的一个结果;平均算法有四个,如图所示:调和平均数:Hn=n/(1/a1+1/a2+…+1/an)几何平均数:Gn=(a1a2…an)^(1/n)算术平均数:An=(a1+a2+…+an)/n平方平均数:Qn=√[(a1^2+a2^2+…+an^2)/n]这四种平均数满足Hn≤Gn≤An≤QnF…

    2022年10月14日
  • 使用EDU邮箱申请JetBrains学生包免费使用一年JetBrains全家桶

    使用EDU邮箱申请JetBrains学生包免费使用一年JetBrains全家桶写在最前,使用EDU邮箱申请JetBrains学生包可以免费使用一年JetBrains全家桶欢迎光顾本人的博客,以后会经常记录生活点滴,学习工作所见1.首先你需要一个EDU邮箱JetBrains学生包的申请很简单,所以国内的EDU邮箱也可以.2.进去JetBrains官网注册账号,这个邮箱可以自己随便选,用自己的邮箱就可以.!之后会给你的邮箱发送一个验证邮件,点击激活…

    2022年7月25日
  • 通配符掩码计算「建议收藏」

    通配符掩码计算「建议收藏」一,通配符掩码1.通配符掩码的用途和结构①用途通配符掩码(wildcard-mask)路由器使用的通配符掩码与源或目标地址一起来分辨匹配的地址范围,它与子网掩码不同。它不像子网掩码告诉路由器IP地址的哪一位属于网络号一样,通配符掩码告诉路由器为了判断出匹配,它需要检查IP地址中的多少位。②结构通配符掩码中,0表示要检查的位,1表示不需要检查的位通配符掩码中,可以用255.255.255…

    2022年7月24日
  • gpio matrix keypad linux driver

    gpio matrix keypad linux driverLinuxkernel&devicedriverprogrammingCross-ReferencedLinuxandDeviceDriverCode[sourcenavigation][diffmarkup][identifiersearch][freetextsearch][files

    2022年5月1日
  • UART串口流控制(Flow control)「建议收藏」

    UART串口流控制(Flow control)「建议收藏」一般在串行通讯中,我们会在一些上位机上看到RTS/CTS、DTR/DSR和XON/XOFF的选项,这是对流控制的选项,一般是应用于RS232接口的,是拿来调制解调器的数据通讯的一、流控制的作用这里讲到的“流”,指的是数据流;在数据通信中,流控制是管理两个节点之间数据传输速率的过程,以防止出现接收端的数据缓冲区已满,而发送端依然继续发送数据,所导致数据丢失二、工作原理当接收端的数…

    2022年6月5日
  • 怎么查看maven仓库地址_修改maven本地仓库

    怎么查看maven仓库地址_修改maven本地仓库Maven一般用来存放jar包的地方,有中央仓库和远程仓库1)找到下载的maven的路径,C:\Users\lqw\Documents\apache-maven-3.3.9\conf找到seting.xml文件,查看&lt;localRepository&gt;C:/repository&lt;/localRepository&gt;这里我改过默认地址了,可以自己设置2)找到自己的maven项目,…

    2022年11月1日

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号