有队列实现吗?
谁能建议Go容器来实现简单快速的FIFO/队列,Go有3种不同的容器:heap
、list
和vector
。哪一种更适合实现队列?
Can anyone suggest Go container for simple and fast FIFO/queue, Go has 3 different containers: heap
, list
and vector
. Which one is more suitable to implement a queue?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(19)
事实上,如果您想要的是一个基本且易于使用的 fifo 队列,slice 可以满足您所需的一切。
当然,我们假设我们可以信任追加和切片的内部实现,以便它避免无用的调整大小和重新分配。对于基本使用来说,这已经足够了。
In fact, if what you want is a basic and easy to use fifo queue, slice provides all you need.
Of course, we suppose that we can trust the inner implementation of append and slicing so that it avoid useless resize and reallocation. For basic usage, this is perfectly sufficient.
令人惊讶的是,无论如何,对于大小限制的 FIFO 队列,还没有人建议缓冲通道。
Surprised to see no one has suggested buffered channels yet, for size bound FIFO Queue anyways.
大多数队列实现都是以下三种类型之一:基于切片、基于链表和基于循环缓冲区(环缓冲区)。
基于环形缓冲区的队列通过环绕其存储来重用内存:当队列增长到超出底层切片的一端时,它会向切片的另一端添加额外的节点。请参阅 双端队列图
另外,还有一些代码来说明:
这个 特定实现始终使用 2 的幂的缓冲区大小,因此可以更有效地计算按位模数。
这意味着只有当所有容量用完时,切片才需要增长。通过调整大小策略,可以避免在同一边界上增加和缩小存储空间,这使得内存效率非常高。
下面是调整底层切片缓冲区大小的代码:
另一件需要考虑的事情是您是否希望在实现中内置并发安全性。您可能希望避免这种情况,以便您可以采取最适合您的并发策略的措施,如果您不需要它,您当然不会想要它;与不想要具有某些内置序列化的切片的原因相同。
如果您在 godoc 上搜索 deque,则有许多基于环缓冲区的 Go 队列实现。选择最适合您口味的一款。
Most queue implementations are in one of three flavors: slice-based, linked list-based, and circular-buffer (ring-buffer) based.
The ring-buffer based queue reuses memory by wrapping its storage around: As the queue grows beyond one end of the underlying slice, it adds additional nodes to the other end of the slice. See deque diagram
Also, a bit of code to illustrate:
This particular implementation always uses a buffer size that is a power of 2, and can therefore compute the bitwise modulus to be a little more efficient.
This means the slice needs to grow only when all its capacity is used up. With a resizing strategy that avoids growing and shrinking storage on the same boundary, this makes it very memory efficient.
Here is code that resizes the underlying slice buffer:
Another thing to consider is if you want concurrency safety built into the implementation. You may want to avoid this so that you can do whatever works best for your concurrency strategy, and you certainly do not want it if your do not need it; same reason as not wanting a slice that has some built-in serialization.
There are a number of ring-buffer based queue implementations for Go if you do a search on godoc for deque. Choose one that best suits your tastes.
编辑,队列的更清晰的实现:
或者只是在简单的实现中嵌入一个
“容器/列表”
并公开接口:Edit, cleaner implementation of a Queue:
Or just embed a
"container/list"
inside a simple implementation and expose the interface:向量或列表都应该有效,但向量可能是最佳选择。我这样说是因为向量的分配频率可能比列表少,而且垃圾收集(在当前的 Go 实现中)相当昂贵。不过,在小程序中这可能并不重要。
Either vector or list should work, but vector is probably the way to go. I say this because vector will probably allocate less often than list and garbage collection (in the current Go implementation) is fairly expensive. In a small program it probably won't matter, though.
为了在实施方面进行扩展,Moraes 在 他的要点来自队列和堆栈的一些结构:
您可以在此 游乐场示例。
To expand on the implementation side, Moraes proposes in his gist some struct from queue and stack:
You can see it in action in this playground example.
在顶部使用切片和适当的(“循环”)索引方案似乎仍然是可行的方法。这是我的看法: https://github.com/phf/go-queue 基准测试还证实通道速度更快,但代价是功能更有限。
Using a slice and an appropriate ("circular") indexing scheme on top still seems to be the way to go. Here's my take on it: https://github.com/phf/go-queue The benchmarks there also confirm that channels are faster, but at the price of more limited functionality.
不幸的是,队列目前不是 go 标准库的一部分,因此您需要编写自己的/导入其他人的解决方案。遗憾的是,在标准库之外编写的容器无法使用泛型。
固定容量队列的一个简单示例是:
这里避免的问题包括切片不会无限制增长(由于使用切片 [1:] 操作进行丢弃而导致),以及将弹出元素清零以确保其内容可用于垃圾回收。请注意,在
MyQueueElement
结构体仅包含 int 的情况下(如此处),它不会产生任何影响,但如果结构体包含指针,则会产生影响。如果需要自动增长的队列,该解决方案可以扩展到重新分配和复制。
此解决方案不是线程安全的,但如果需要,可以向 Push/Pop 添加锁。
游乐场 https://play.golang.org/
Unfortunately queues aren't currently part of the go standard library, so you need to write your own / import someone else's solution. It's a shame as containers written outside of the standard library aren't able to use generics.
A simple example of a fixed capacity queue would be:
Gotchas avoided here include not having unbounded slice growth (caused by using the slice [1:] operation to discard), and zeroing out popped elements to ensure their contents are available for garbage collection. Note, in the case of a
MyQueueElement
struct containing only an int like here, it will make no difference, but if struct contained pointers it would.The solution could be extended to reallocate and copy should an auto growing queue be desired.
This solution is not thread safe, but a lock could be added to Push/Pop if that is desired.
Playground https://play.golang.org/
我还如上所述从切片实现了队列。但是,它不是线程安全的。所以我决定加一个锁(互斥锁)来保证线程安全。
您可以在 github 上查看我的解决方案 简单队列
I also implement the queue from slice as above. However, It's not thread-safe. So I decided to add a lock (mutex lock) to guarantee thread-safe.
You can check my solution on github here simple queue
从 Go v1.18 开始,添加了泛型,我将用它来创建泛型队列。
以下是我的实现
每当调用出队时,都会调整队列大小以使用切片释放内存,以避免复制内存。这不是线程安全的,在这些情况下,通道可能更好 - 但需要知道队列容量才能指定正确的缓冲区大小。
为了好玩,我对使用
interface{}
的队列进行了基准测试 - 这是在 Go v1.18 之前获得通用解决方案的方法。该测试追加 1、10、100 和 1.000 个整数并将其出列。在所有情况下,泛型速度更快,内存使用量更少。
下面给出了使用
interface{}
的队列实现 - 添加了我认为必要的错误处理。From Go v1.18 generics have been added which I would use to make a generic queue.
Below are my implementations
Whenever dequeue is called the queue is resized to release memory using slicing to avoid copying memory. This isn't thread safe, in those cases channels are probably better - but one needs to know the queues capacity to specify a correct buffer size.
For fun I have made a benchmark run against a queue which uses
interface{}
- the way to have a generic solution before Go v1.18.The test appends and the dequeues 1, 10, 100 and 1.000 integers. In all cases generics are a lot faster with less memory usages.
The implementation of queue using
interface{}
are given below - error handling is added which I think is necessary.你可以尝试这样的事情,
you can try something like this,
对于您的基本需求,上面的代码就可以了
For your basic need the code above would do
入队、出队、Front 和 Front 的时间为 O(1)后方查找
O(n) 容量空间
O(1) Time for EnQueue, DeQueue, Front & Rear Lookups
O(n) Space for Capacity
我实现了一个将自动扩展底层缓冲区的队列:
I implemented a queue that will expand the underlying buffer automatically:
list 对于队列和堆栈来说就足够了,我们应该做的是
l.Remove(l.Front())
对于队列 Poll,l.Remove(l.Back())
>用于堆栈轮询,PushBack
用于堆栈和队列的添加操作。 list有前后指针,时间复杂度为O(1)list is enough for queue and stack, what we shoud do is
l.Remove(l.Front())
for queue Poll,l.Remove(l.Back())
for stack Poll,PushBack
for the Add Operation for stack and queue. there are front and back pointer for list, such that time complexity is O(1)双栈实现:
O(1)
Enqueue
和Dequeue
并使用slices
(这对于缓存来说往往更好)错过)。Double stack implementation:
O(1)
Enqueue
andDequeue
and usesslices
(which tends to be better for cache misses).在Golang中实现队列数据结构的最简单方法是使用切片。
由于队列遵循 FIFO(先进先出)结构,因此可以按如下方式执行出队和入队操作:
下面的代码片段使用切片实现了一个基本队列。请注意入队和出队是如何在切片的两端发生的。
我们可以使用动态数据结构(链表)来避免内存泄漏。示例代码如下:
The simplest way to implement the queue data structure in Golang is to use a slice.
Since a queue follows a FIFO (First-In-First-Out) structure, the dequeue and enqueue operations can be performed as follows:
The following code snippet implements a basic queue using a slice. Note how enqueuing and dequeuing occur at opposite ends of the slice.
We can use dynamic data structure(linked list) in order to avoid memory leaks. The sample code is given below:
Slice可以用来实现队列。
更新:
这是我的 GitHub 页面上的完整实现 https://github.com/raiskumar/algo-ds/blob/master/tree/queue.go
Slice can be used to implement queue.
Update:
here is complete implementation on my GitHub page https://github.com/raiskumar/algo-ds/blob/master/tree/queue.go