用于排序浏览历史的数据结构

use*_*093 5 browser-history data-structures

假设我想实现浏览器历史功能。如果我第一次访问该 url,它会进入历史记录,如果我再次访问同一页面,它会出现在历史记录列表中。假设我只显示前 20 个站点,但我可以选择查看上个月、上周等的历史记录。

最好的方法是什么?我会使用哈希图来插入/检查它是否更早被访问过,但是我如何有效地对最近访问过的进行排序,我不想使用树图或树集。另外,我如何存储周和月的历史记录。浏览器关闭时是否写入磁盘?当我点击清除历史记录时,数据结构是如何被删除的?

Zim*_*oot 6

这是在 Java-ish 代码中。

您将需要两种数据结构:哈希映射和双向链表。双向链表包含按时间戳排序的 History 对象(其中包含一个 url 字符串和一个时间戳);哈希映射是 a Map<String, History>,以 urls 作为键。

class History {
  History prev
  History next
  String url
  Long timestamp
  void remove() {
    prev.next = next
    next.prev = prev
    next = null
    prev = null
  }
}
Run Code Online (Sandbox Code Playgroud)

当您向历史记录添加 url 时,请检查它是否在哈希映射中;如果是,则更新其时间戳,将其从链表中删除,并将其添加到链表的末尾。如果它不在哈希映射中,则将其添加到哈希映射并将其添加到链表的末尾。添加一个 url(无论它是否已经在哈希映射中)是一个恒定时间操作。

class Main {
  History first // first element of the linked list
  History last // last element of the linked list
  HashMap<String, History> map

  void add(String url) {
    History hist = map.get(url)
    if(hist != null) {
      hist.remove()
      hist.timestamp = System.currenttimemillis()
    } else {
      hist = new History(url, System.currenttimemillis())
      map.add(url, hist)
    }
    last.next = hist
    hist.prev = last
    last = hist
  }
}
Run Code Online (Sandbox Code Playgroud)

要获取例如上周的历史记录,请向后遍历链表,直到找到正确的时间戳。

如果关注线程安全,则使用线程安全队列将 url 添加到历史记录中,并使用单个线程来处理此队列;这样你的地图和链表不需要是线程安全的,即你不需要担心锁等。

对于持久性,您可以序列化/反序列化链表;当您反序列化链表时,通过遍历哈希映射并将其元素添加到映射来重建哈希映射。然后要清除历史记录,您将列表清空并映射到内存中,然后删除将数据序列化到的文件。

在内存消耗和 IO(即(反)序列化成本)方面更有效的解决方案是使用无服务器数据库,如SQLite;这样你就不需要将历史保存在内存中,如果你想从上周获取历史,你只需要查询数据库而不是遍历链表。然而,SQLite 本质上是一个树状图(特别是一个 B 树,它针对存储在磁盘上的数据进行了优化)。

  • @user775093 您使用哈希映射来确保某个网址不会在历史列表中出现两次。假设一个 url 位于 `List[i]` 的历史列表中 - 当您第二次添加该 url 时,您从哈希映射中检索 `List[i]`,然后您对其调用 `remove` 来拼接 `List[ i-1]` 到 `List[i+1]` ,从而从列表中删除 `List[i]` (现在是 `List[null]`),然后将 `List[null]` 附加到列表的末尾使其成为“List[last]”的列表。如果没有哈希映射,从历史列表中删除重复的 url 将是一个线性时间操作。 (2认同)