用环形缓冲区实现循环日志 | 字数总计: 1.6k | 阅读时长: 5分钟 | 阅读量: |
环形缓冲区在数据结构中是一种特殊的线性数据结构,具有以下特点和优势:
结构特点
固定大小:环形缓冲区通常具有固定的容量,一旦创建,其大小就不能改变。这使得在内存使用方面更加可控,避免了动态分配内存带来的开销和不确定性。
循环利用空间:正因为其环形的特性,当写指针到达缓冲区的末尾时,会自动回绕到开头继续写入数据;读指针在读取完数据后也会相应地移动,实现空间的循环利用。
操作方式
写入操作:当向环形缓冲区写入数据时,首先检查缓冲区是否已满。如果未满,则将数据写入写指针所指的位置,然后写指针向前移动一位。如果写指针移动到缓冲区的末尾,它会回绕到开头。
读取操作:从环形缓冲区读取数据时,先检查缓冲区是否为空。如果不为空,则读取读指针所指位置的数据,然后读指针向前移动一位。同样,当读指针到达缓冲区的末尾时,会回绕到开头。
应用场景
实时数据处理:在需要对连续产生的实时数据进行处理的场景中,环形缓冲区可以作为数据的暂存区域。例如,音频和视频流的处理,数据采集系统等。
多生产者 - 多消费者模型:环形缓冲区可以方便地在多个生产者和多个消费者之间共享数据。生产者将数据写入缓冲区,消费者从缓冲区读取数据,通过合理的同步机制,可以实现高效的数据交换。
缓存数据:可以用作缓存,存储最近使用的数据,以提高数据的访问速度。例如,在数据库查询中,可以将最近查询的结果存储在环形缓冲区中,以便下次相同的查询可以直接从缓冲区中获取结果。
实现要点
指针管理:准确地管理写指针和读指针是实现环形缓冲区的关键。需要确保指针的移动正确无误,并且在进行读写操作时不会出现越界的情况。
同步机制:在多线程环境下使用环形缓冲区时,需要考虑同步问题。可以使用互斥锁、条件变量等同步机制来确保线程安全。
空满判断:需要有可靠的方法来判断环形缓冲区是为空还是已满。常见的方法有使用计数器、位运算等。
示例:错误日志记录 描述 https://www.nowcoder.com/practice/2baa6aba39214d6ea91a2e03dff3fbeb
开发一个简单错误记录功能小模块,能够记录出错的代码所在的文件名称和行号。
处理:
记录最多8条错误记录,循环记录,最后只用输出最后出现的八条错误记录。对相同的错误记录只记录一条,但是错误计数增加。最后一个斜杠后面的带后缀名的部分(保留最后16位)和行号完全匹配的记录才做算是“相同”的错误记录。
超过16个字符的文件名称,只记录文件的最后有效16个字符;
输入的文件可能带路径,记录文件名称不能带路径。也就是说,哪怕不同路径下的文件,如果它们的名字的后16个字符相同,也被视为相同的错误记录
循环记录时,只以第一次出现的顺序为准,后面重复的不会更新它的出现时间,仍以第一次为准
数据范围:错误记录数量满足 1≤n≤100 1≤n≤100 ,每条记录长度满足 1≤len≤100 1≤len≤100
输入描述:
每组只包含一个测试用例。一个测试用例包含一行或多行字符串。每行包括带路径文件名称,行号,以空格隔开。
输出描述:
将所有的记录统计并将结果输出,格式:文件名 代码行数 数目,一个空格隔开,如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 输入: D:\z wtymj\x ccb\l jj\c qzlyaszjvlsjmkwoqijggmybr 645 E:\j e\r zuwnjvnuz 633 C:\k m\t gjwpb\g y\a tl 637 F:\w eioj\h add\c onnsh\r wyfvzsopsuiqjnr 647 E:\n s\m fwj\w qkoki\e ez 648 D:\c fmwafhhgeyawnool 649 E:\c zt\o pwip\o snll\c 637 G:\n t\f 633 F:\f op\y wzqaop 631 F:\y ay\j c\y wzqaop 631 D:\z wtymj\x ccb\l jj\c qzlyaszjvlsjmkwoqijggmybr 645 输出: rzuwnjvnuz 633 1 atl 637 1 rwyfvzsopsuiqjnr 647 1 eez 648 1 fmwafhhgeyawnool 649 1 c 637 1 f 633 1 ywzqaop 631 2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 import sysSIZE = 8 def formatFileName (f, size = 16 ): if len (f) > size: return f[-16 :] else : return f class RingBuffer : def __init__ (self, size ) -> None : self.buffer = [] self.legacy = [] self.head = 0 self.size = size def put (self, item ): if item is not None : for l in self.legacy: if l["file" ] == item["file" ] and l["line" ] == item["line" ]: return if len (self.buffer) < self.size: self.buffer.append(item) else : self.legacy.append(self.buffer[self.head]) self.buffer[self.head] = item self.head = (self.head + 1 ) % self.size def findItem (self, file, line ): for idx, i in enumerate (self.buffer): if i["file" ] == file and i["line" ] == line: return idx return None def showItem (self, idx ): if idx < len (self.buffer): return self.buffer[idx] else : return None def updateItem (self, idx, key, value ): self.buffer[idx][key] = value def print (self ): if not len (self.buffer): return headNode = self.buffer[self.head] print (f'{headNode.get("file" )} {headNode.get("line" )} {headNode.get("count" )} ' ) h = (self.head + 1 ) % len (self.buffer) while (h != self.head): node = self.buffer[h] print (f'{node.get("file" )} {node.get("line" )} {node.get("count" )} ' ) h = (h + 1 ) % len (self.buffer) def filterLog (log ): if not log or not isinstance (log, str ): return None , None [path, line] = log.split() return path.split("\\" )[-1 ], line buffer = RingBuffer(SIZE) for line in sys.stdin: f, l = filterLog(line.strip()) if f is None or l is None : continue f = formatFileName(f) idx = buffer.findItem(f, l) if idx is not None : item = buffer.showItem(idx) if item: buffer.updateItem(idx, "count" , item["count" ] + 1 ) else : buffer.put({"file" : f, "line" : l, "count" : 1 }) buffer.print ()