我需要从固定的可能ID列表中找到下一个可用的ID(或密钥)。在这种情况下,有效ID为1到9999(含)。当我们找到下一个可用的ID时,我们就会在最后一个分配的ID之后开始查看,最后回合 - 当然只有一次 - 并且需要检查每个ID是否在我们作为可用ID返回之前被采用。
我有一些代码可以做到这一点,但我认为它既不优雅也不高效,并且对更简单的方法来完成同样的事情感兴趣。我正在使用Ruby,但我的问题并不是针对该语言的,所以如果您想使用任何其他语言撰写答案,我将非常感谢您的意见!
我已经省略了一些关于检查ID是否可用的详细信息,因此只需将其视为存在函数incr_last_id
,id_taken?(id)
和set_last_id(id)
的情况。 (incr_last_id
将在数据存储(Redis)中为最后分配的ID添加1并返回结果。id_taken?(id)
返回一个布尔值,指示ID是否可用。set_last_id(id)
更新带有新的最后一个ID的数据存储。)
MaxId = 9999
def next_id
id = incr_last_id
# if this ID is taken or out of range, find the next available id
if id > MaxId || id_taken?(id)
id += 1 while id < MaxId && id_taken?(id)
# wrap around if we've exhausted the ID space
if id > MaxId
id = 1
id += 1 while id < MaxId && id_taken?(id)
end
raise NoAvailableIdsError if id > MaxId || id_taken?(id)
set_last_id(id)
end
id
end
我对那些要求我建立所有可能ID列表的解决方案并不感兴趣,然后获取分配的ID和可用ID之间的集合或列表差异。这不规模。我意识到这是一个线性操作,无论你如何切片它,那部分都很好,我只是认为代码可以简化或改进。我不喜欢因不得不环绕而导致的重复,但也许没有办法解决这个问题。
有更好的方法吗?请告诉我!
答案 0 :(得分:1)
使用数据库表(本例中为MySQL):
SELECT id FROM sequences WHERE sequence_name = ? FOR UPDATE
UPDATE sequences SET id = id + 1 WHERE sequence_name = ?
FOR UPDATE
获得对表的独占锁定,确保您可以成为同时执行相同操作的唯一可能进程。
使用内存中的固定列表:
# somewhere global, done once
@lock = Mutex.new
@ids = (0..9999).to_a
def next_id
@lock.synchronize { @ids.shift }
end
使用redis:
LPOP list_of_ids
或者只是:
INCR some_id
Redis会为您解决并发问题。
答案 1 :(得分:1)
改进这种算法的通常答案是保持一个“免费对象”列表方便;如果你真的不想要额外的维护列表,你可以只使用列表中的单个对象。 (这会降低免费对象缓存的效率,但管理大量自由对象的开销可能会增加负担。它取决于。)
因为当你点击MaxId
时,你正在包装你的搜索,我认为有一个函数give_up_id
会将id返回到空闲池。您可以使用新变量@most_recently_free
或将其附加到列表@free_ids[]
,而不是简单地将已释放的ID重新放入大型池中,而是将其附加到列表def give_up_id(id)
@free_ids.push(id)
end
def next_id
if @free_ids.empty?
id = old_next_id()
else
id = @free_ids.pop()
return id
end
。
当您需要新的ID时,如果列表中有一个,请从列表中取一个。如果列表没有,请按照您目前的步骤开始搜索。
这是伪代码中的草图:
{{1}}
如果你允许多个执行线程与你的id分配/自由例程交互,你当然也需要保护这些例程。
答案 2 :(得分:1)
由于您已经在第一次迭代中从incr_last_id搜索到MaxId,因此实际上不需要再次重复它。
在第二轮中从1搜索到incr_last_id至少将搜索精确地减少到O(n)而不是更糟的O(2n)
如果您想在单个循环中执行此操作,请使用modulo,
MaxId = 9999
def next_id
id = incr_last_id
limit = id - 1 #This sets the modulo test to the id just before your start point
id += 1 while (id_taken?(id) && (i % MaxId) != limit)
raise NoAvailableIdsError if id_taken?(id)
set_last_id(id)
id
end