转载自https://mp.weixin.qq.com/s/ILgdI7JUBsiATFICyyDQ9w

Osprey  鱼鹰谈单片机 3月2日

预计阅读时间: 6 分钟

这里的 FIFO 是先入先出的意思,即谁先进入队列,谁先出去。比如我们需要串口打印数据,当使用缓存将该数据保存的时候,在输出数据时必然是先进入的数据先出去,那么该如何实现这种机制呢?

首先就是建立一个缓存空间,这里假设为 7 个字节空间进行说明。

缓存一开始没有数据,并且用一个变量 rear 指示下一个存入缓存的索引地址,这里下一个存放的位置就是 0,用另一个变量 front 指示下一个存入缓存的索引地址,并且下一个读出数据的索引地址也是 0。目前队列中是没有数据的,也就是不能读出数据,队列为空的判断条件在这里就是两个索引值相同。

现在开始存放数据:

在这里可以看到队列中加入了 5 个数据,并且每加入一个数据后队尾索引加 1,队头不变,这就是数据加入队列的过程。但是缓存空间只有 7 个,如何判断队列已满呢?

如果只是先一次性加数据到队列中,然后再读出数据,那这里的判断条件显然是队尾索引为 6,但实际上是在加入数据的同时也可能出现有数据已出队的情况,比如:

这个时候索引是 6,但是实际上还是有一个索引为 0 的位置是空的,也就是实际上还是可以再加入一个数据的。这个时候又该如何判断是否队列已满呢?

通过以下算法即可:

(rear + 1) % 7  ==  front

你可以发现这个算法的巧妙。通过%运算将索引又从 6 返回到了 0 处,这是实现循环队列的关键之处。通过该算法就能知道队列是否已满了。队列空的算法就是队头队尾索引相同。

front == rear

刚才说过通过%运算可以实现索引值的循环,所以当索引为 6 的时候,一般思维是通过 if 判断语句将其定位到下一个索引位置,而这里通过以下算法即可将其重新定位:

rear = (rear + 1) %7

这样当 rear 等于 6 的时候下一个索引就是 0 了。非常巧妙的实现了数值的循环。这个时候就出现了如下情况:

队尾索引跑到了队头索引的前头。并且周而复始,这就是循环队列了,充分的利用了空间。

但你有没有发现其实在有 7 个空间的情况下其实只能存放 6 个数据,另一个数据空间是没法使用的,为什么呢?看看以下两种情况:

这里一种为队列为空的情况,有一种队列已满的情况,这个时候到底是空还是满的单靠这两个变量是无法判断的,这个时候就需要增加一个变量指示队列已满的情况,并且需要加入判断语句,降低了运行效率,所以建议采用留空的方式进行统一处理。附上代码自行感受。

这样入队出队操作都有了,也就算完成了基本操作,实际上有时候需要获取整个队列存放的数量,这时又涉及到了一个有意思的公式:

(rear – front + 7)  %  7

来看一看这个公式的巧妙性。当出现如下 rear 在后,front 在前这种正常情况时,只要两种相减即可得到队列的长度。

但实际上对于循环队列来说 rear 在前,front 在后也是再正常不过的事情,如下:

这个时候又该如何获取呢?就是利用上面的公式了。通过它就能适应这两种情况。

通过了解这个公式,感受它的巧妙,又可以想到利用这个公式干点其他的,比如在时间的获取上,不管你计时变量设置得多么大,总有一个限度,总会出现计时溢出的情况,但实际上你只要获取溢出时间内的时间即可,比如一个 16 位变量,每 1ms 自加 1,你想获取 100ms 的定时时间(小于 65535),正常情况下只需通过如下判断即可准确获取:

CurrentTime >= (Time + 100)

CurrentTime 是变化的时间,Time 是开始的时刻。但你开始的时刻是有可能是在 CurrentTime = 65435 的情况下的,CurrentTime 必然溢出,开始从零开始计时,这样你这个条件满足必须是 CurrentTime = 65535,也就是说你从 65435 – 0 - 65535,整整多了一个溢出周期时间。而如果你使用上面的公式就即使计数器溢出了,也能获取准确的定时时间。

(CurrentTime  -  Time + 65535)  % 65535  >= 100

利用如下算法验证:

结果如下:

          

(实际上,这种使用一个整型数的最大值来计算不需要这么麻烦,可以直接简单的 if(CurrentTime - Time + 65536 >100)即可)

还有一种方法就是利用无符号减法的特性来获取,自行研究即可。验证代码如下:

结果如下:

                   

有一种功能需求可能是覆盖最旧的数据,也就是说缓存区中始终保留最近的数据,而将最旧的数据删除掉,如何实现呢?通过学习循环队列,可以从中得到启发。就是通过%运算将数据始终循环保存在缓存中,在不需要判断是否队列已满的情况下继续入队就可以实现将最旧的数据覆盖了。这是我在做一个项目的时候想的,想过用队列,想过用链表,也想过用栈,到头来发现其实没那么复杂,幸好之前有看过循环队列的源码,从中得到很多启发。在这个项目中还利用了一个小技巧,这是我从学习文件系统得到的启发。那就是如果入队的单个元素比较多,一个元素有 100byte,如下:

你是否就是将所有的 7*100 个字节数据进行删除呢?那你的效率也是够低的,写入数据的时候没办法,必须一个个准确写入,但删除的时候也需要如此吗?看如下处理:

只要队列的每个元素设置一个标志,删除这个标志就代表这个队列元素已删除即可,而插入元素则就重新设置该标志,表明你已经存放了数据,并将数据写入对应的位置即可。当然这个标志其实还可以用于标志这个元素属于什么种类的数据,这样就是将两种功能结合了在一个标志内了,而你要做的就是实现他们之间的一一对应关系即可。

-----------------------------------------------------------------------------------------2018-08-08 Osprey

数据结构系列文章之队列 FIFO的更多相关文章

  1. <数据结构系列3>队列的实现与变形(循环队列)

    数据结构第三课了,今天我们再介绍一种很常见的线性表——队列 就像它的名字,队列这种数据结构就如同生活中的排队一样,队首出队,队尾进队.以下一段是百度百科中对队列的解释: 队列是一种特殊的线性表,特殊之 ...

  2. 【JavaScript数据结构系列】04-优先队列PriorityQueue

    [JavaScript数据结构系列]04-优先队列PriorityQueue 码路工人 CoderMonkey 转载请注明作者与出处 ## 1. 认识优先级队列 经典的案例场景: 登机时经济舱的普通队 ...

  3. 【数据结构(C语言版)系列三】 队列

    队列的定义 队列是一种先进先出的线性表,它只允许在表的一端进行插入,而在另一端删除元素.这和我们日常生活中的排队是一致的,最早进入队列的元素最早离开.在队列中,允许插入的一端叫做队尾(rear),允许 ...

  4. 我理解的数据结构(三)—— 队列(Queue)

    我理解的数据结构(三)-- 队列(Queue) 一.队列 队列是一种线性结构 相比数组,队列对应的操作是数组的子集 只能从一端(队尾)添加元素,只能从另一端(队首)取出元素 队列是一种先进先出的数据结 ...

  5. 【JavaScript数据结构系列】03-队列Queue

    [JavaScript数据结构系列]03-队列Queue 码路工人 CoderMonkey 转载请注明作者与出处 1. 认识队列Queue结构 队列,跟我们的日常生活非常贴近,我们前面举例了食堂排队打 ...

  6. Redis 学习笔记系列文章之 Redis 的安装与配置 (一)

    1. 介绍 Redis is an open source (BSD licensed), in-memory data structure store, used as database, cach ...

  7. MySQL优化篇系列文章(二)——MyISAM表锁与InnoDB锁问题

    我可以和面试官多聊几句吗?只是想... MySQL优化篇系列文章(基于MySQL8.0测试验证),上部分:优化SQL语句.数据库对象,MyISAM表锁和InnoDB锁问题. 面试官:咦,小伙子,又来啦 ...

  8. 【微信小程序开发•系列文章六】生命周期和路由

    这篇文章理论的知识比较多一些,都是个人观点,描述有失妥当的地方希望读者指出. [微信小程序开发•系列文章一]入门 [微信小程序开发•系列文章二]视图层 [微信小程序开发•系列文章三]数据层 [微信小程 ...

  9. 学习javascript数据结构(一)——栈和队列

    前言 只要你不计较得失,人生还有什么不能想法子克服的. 原文地址:学习javascript数据结构(一)--栈和队列 博主博客地址:Damonare的个人博客 几乎所有的编程语言都原生支持数组类型,因 ...

随机推荐

  1. list列表相关操作

    ] ] ] : :-]print(s10)# a.sort(reve# rse=True)# print(a)# a.reverse()# print(a) lst = [], 'wusir','cg ...

  2. mysql非主键提示key2 检查索引是否设定为唯一

  3. 初识RedisCluster集群

    为什么需要Redis集群 需要提高更大的并发量 Redis官方提出拥有10万QPS的请求量 如果业务需要Redis拥有100万的QPS 可以通过集群来提升并发量. 需要存储更大的数据量 一般服务器的机 ...

  4. 分享 Shiro 学习过程中遇到的一些问题

    最近在学习 shiro 安全框架后,自己手写了一个小的管理系统 web 项目,并使用 shiro 作为安全管理框架.接下来分享一下在这过程中,遇到的一些问题以及自己的解决思路和方法. 一.Log ou ...

  5. RabbitMQ 幂等性概念及业界主流解决方案

    RabbitMQ 幂等性概念及业界主流解决方案 2019年01月24日 15:57:03 JAVA@架构 阅读数:506   一.什么是幂等性 可以参考数据库乐观锁机制,比如执行一条更新库存的 SQL ...

  6. MyBatis学习存档(5)——联表查询

    之前的数据库操作都是基于一张表进行操作的,若一次查询涉及到多张表,那该如何进行操作呢? 首先明确联表查询的几个关系,大体可以分为一对一和一对多这两种情况,接下来对这两种情况进行分析: 一.建立表.添加 ...

  7. 怎样获取页面中所有带href属性的标签集合

    使用: document.links document.links instanceof HTMLCollection; 注意: 1. a 标签和 area 标签可以设置 href属性, 因此可以被获 ...

  8. vue项目中导出PDF的两种方式

    参考大家导出的方式,基本上是如下两种: 1.使用 html2Canvas + jsPDF 导出PDF, 这种方式什么都好,就是下载的pdf太模糊了.对要求好的pdf这种方式真是不行啊! 2.调用浏览器 ...

  9. BigDecimal与Long、int之间的相互转换

    //bigDecimal 转换成 Long类型 public static Long bigDecimalToLong(BigDecimal b){ BigDecimal c = new BigDec ...

  10. springboot集成websocket的两种实现方式

    WebSocket跟常规的http协议的区别和优缺点这里大概描述一下 一.websocket与http http协议是用在应用层的协议,他是基于tcp协议的,http协议建立链接也必须要有三次握手才能 ...