假设我想实现浏览器历史记录功能。如果我第一次访问该网址,它会进入历史记录,如果我再次访问同一页面,它会出现在历史记录列表中。 让我说我只显示前20个网站,但我可以选择查看上个月,上周的历史记录,依此类推。
最好的办法是什么?我会使用哈希映射来插入/检查它是否先前访问过,但我如何有效地排序最近访问过,我不想使用树图或树集。另外,我如何存储数周和数月的历史。浏览器关闭时是否写在磁盘上?当我点击清除历史记录时,数据结构是如何删除的?
答案 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
}
}