【问题标题】:data structure for sorted browsing history排序浏览历史的数据结构
【发布时间】:2014-02-19 21:15:32
【问题描述】:

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

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

【问题讨论】:

  • 如果您使用的是哈希映射,则无法快速检索排序结果。为什么不想使用树图,也就是红黑搜索树?
  • 因为红黑树在内部需要大量的旋转来保持平衡,特别是如果有很多添加,我假设这发生在浏览器中,因为用户可以从已知站点跳到新站点. HashMap 会表现得更好,问题是使用辅助数据结构对内容进行排序和移动..
  • 您希望有多少网站在历史缓存中?如果你一整年有 100,000 页,我会感到惊讶,但即使是 100 万页也没什么大不了的。只需将它们存储在线性列表中并按顺序搜索即可。对于像浏览器这样的用户界面应用来说,这已经足够快了。

标签: data-structures browser-history


【解决方案1】:

这是Java-ish代码。

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

class History {
  History prev
  History next
  String url
  Long timestamp
  void remove() {
    prev.next = next
    next.prev = prev
    next = null
    prev = null
  }
}

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

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
  }
}

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

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

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

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

【讨论】:

  • 不错!但这两者是如何联系起来的? hashmap 值是 History 列表,那么它究竟包含什么?对象在列表中的位置?
  • @user775093 您使用哈希映射来确保 url 在历史列表中不会出现两次。假设 url 在 List[i] 的历史列表中 - 当您第二次添加该 url 时,您从哈希映射中检索 List[i],然后您调用 removeList[i-1] 拼接到 List[i+1] 从而删除列表中的List[i](现在是List[null]),然后将List[null] 附加到列表的末尾,使其成为List[last]。如果没有哈希映射,从历史列表中删除重复的 url 将是一个线性时间操作。
【解决方案2】:

这是基于 Zim-Zam O'Pootertoot 的回答的 Swift 4.0 实现,包括用于遍历历史的迭代器:

import Foundation

class SearchHistory: Sequence {
    var first: SearchHistoryItem
    var last: SearchHistoryItem
    var map = [String: SearchHistoryItem]()
    var count = 0
    var limit: Int

    init(limit: Int) {
        first = SearchHistoryItem(name: "")
        last = first
        self.limit = Swift.max(limit, 2)
    }

    func add(name: String) {
        var item: SearchHistoryItem! = map[name]
        if item != nil {
            if item.name == last.name {
                last = last.prev!
            }
            item.remove()
            item.timestamp = Date()
        } else {
            item = SearchHistoryItem(name: name)
            count += 1
            map[name] = item
            if count > limit {
                first.next!.remove()
                count -= 1
            }
        }
        last.next = item
        item.prev = last
        last = item
    }

    func makeIterator() -> SearchHistory.SearchHistoryIterator {
        return SearchHistoryIterator(item: last)
    }

    struct SearchHistoryIterator: IteratorProtocol {
        var currentItem: SearchHistoryItem

        init(item: SearchHistoryItem) {
            currentItem = item
        }

        mutating func next() -> SearchHistoryItem? {
            var item: SearchHistoryItem? = nil
            if let prev = currentItem.prev {
                item = currentItem
                currentItem = prev
            }
            return item
        }
    }
}

class SearchHistoryItem {
    var prev: SearchHistoryItem?
    var next: SearchHistoryItem?
    var name: String
    var timestamp: Date

    init(name: String) {
        self.name = name
        timestamp = Date()
    }

    func remove() {
        prev?.next = next
        next?.prev = prev
        next = nil
        prev = nil
    }
}

【讨论】:

    猜你喜欢
    • 2011-10-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-11-14
    • 2015-10-10
    • 2018-05-28
    • 2015-04-03
    • 2011-04-27
    相关资源
    最近更新 更多