时间轮
相比较于基于排序链表的定时器(使用一条链表来管理所有的定时器,插入效率随着链表长度增加而降低),时间轮则使用哈希表的思想,将定时器散列到不同的链表上,每条链表上的定时器个数明显少于原来的排序链表,插入的效率基本不受定时器个数的影响。
在时间轮中,指针每次指向轮子上的一个槽(slot,也就是一个链表),他以恒定的速度顺时针转动,每次转动称为一个(tick),一个滴答的时间称为时间轮的槽间隔si(slot interval),实际上就是心搏时间,共有N个槽,转上一周的时间N*si,因此每条链表上的定时器都有一个相同的特征,他们的定时时间都是N*si的整数倍,
想要提高定时精度,就要si的值足够小;想要提高执行效率,就要N足够大。插入和删除一个定时器的时间复杂度是O(1),执行一个定时器的时间复杂度是O(n)。此外,当使用多个轮子来实现时间轮,执行一个定时器的时间复杂度也接近O(1)。
时间堆
时间轮是以固定的频率调用tick,依次检测到期的定时器,然后执行上面的回调函数。时间堆则是根据超时时间最小的一个定时器的超时值作为tick的间隔。一旦tick被调用,超时时间最小的定时器到期,处理该定时器,然后找出下一个超时时间最小的定时器,把他的超时值作为下一次tick的时间。
使用最小堆(每个根节点都小于其子节点的完全二叉树)。
插入操作(自底向上),首先在堆的底部创建一个空穴,如果插入值放入空穴而不破坏堆的结构,插入完成;否则,交换该元素与其根节点元素的位置,按照此过程向上过滤,直到插入成功。
删除操作(自顶向下),在根节点处创建了一个空穴,交换空穴和他两个儿子的较小者,不断执行上述过程。但是要注意的是, 在交换之前应该把对的最后一个节点先推走,然后再自顶向下过滤,最后把该元素插入空穴,否则可能会破坏堆的结构(不符合完全二叉树的结构)。如下图所示。
添加一个定时器事件复杂度是O(Logn),删除一个定时器的时间复杂度为O(logn),执行一个定时器的时间复杂度O(1)。