排序浏览历史的数据结构

时间:2014-02-19 21:15:32

标签: data-structures browser-history

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

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

2 个答案:

答案 0 :(得分:3)

这是Java-ish代码。

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

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

在历史记录中添加网址时,请检查它是否在哈希映射中;如果是,则更新其时间戳,将其从链接列表中删除,并将其添加到链接列表的末尾。如果它不在哈希映射中,则将其添加到哈希映射中,并将其添加到链接列表的末尾。添加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
  }
}

从中获取历史记录在最后一周,向后遍历链表,直到您点击正确的时间戳。

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

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

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

答案 1 :(得分:0)

这是一个基于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
    }
}