Data structures 分类浏览历史的数据结构
假设我想要实现浏览器历史记录功能。如果我第一次访问url,它会进入历史记录,如果我再次访问同一页面,它会出现在历史记录列表中。 假设我只显示排名前20位的网站,但我可以选择查看上个月、上周等的历史记录Data structures 分类浏览历史的数据结构,data-structures,browser-history,Data Structures,Browser History,假设我想要实现浏览器历史记录功能。如果我第一次访问url,它会进入历史记录,如果我再次访问同一页面,它会出现在历史记录列表中。 假设我只显示排名前20位的网站,但我可以选择查看上个月、上周等的历史记录 最好的方法是什么?我会使用哈希映射来插入/检查它是否在之前访问过,但如何对最近访问过的文件进行有效排序,我不想使用树映射或树集。此外,如何存储周和月的历史记录。浏览器关闭时是否将其写入磁盘?当我单击clear history时,数据结构是如何删除的?这是Java ish代码 您将需要两种数据结构:
最好的方法是什么?我会使用哈希映射来插入/检查它是否在之前访问过,但如何对最近访问过的文件进行有效排序,我不想使用树映射或树集。此外,如何存储周和月的历史记录。浏览器关闭时是否将其写入磁盘?当我单击clear history时,数据结构是如何删除的?这是Java ish代码 您将需要两种数据结构:哈希映射和双链接列表。双链接列表包含按时间戳排序的历史对象(包含url字符串和时间戳);哈希映射是一个
映射
,以URL作为键
class History {
History prev
History next
String url
Long timestamp
void remove() {
prev.next = next
next.prev = prev
next = null
prev = null
}
}
将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
}
}
主类{
历史记录第一//链表的第一个元素
历史记录最后//链表的最后一个元素
哈希映射
无效添加(字符串url){
History hist=map.get(url)
if(hist!=null){
hist.remove()
hist.timestamp=System.currenttimemillis()
}否则{
hist=新历史记录(url,System.currenttimemillis())
map.add(url,hist)
}
last.next=hist
hist.prev=last
最后=历史
}
}
要获取例如上周的历史记录,请向后遍历链接列表,直到找到正确的时间戳
如果关注线程安全,那么使用线程安全队列将URL添加到历史记录中,并使用单个线程处理该队列;这样,你的地图和链表就不需要线程安全,也就是说,你不需要担心锁等问题
对于持久性,您可以序列化/反序列化链表;反序列化链表时,通过遍历哈希映射并将其元素添加到映射中来重构哈希映射。然后,要清除历史记录,您需要在内存中为列表和映射设置空值,并删除将数据序列化到的文件
在内存消耗和IO(即(反)序列化成本)方面,更有效的解决方案是使用无服务器数据库,如;这样,您就不需要将历史记录保存在内存中,如果您想从(例如)上周获取历史记录,您只需要查询数据库,而不是遍历链表。然而,SQLite本质上是一个树映射(特别是一个B树,它针对存储在磁盘上的数据进行了优化)。这里是一个基于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
}
}
如果使用的是哈希映射,则无法快速检索排序结果。你为什么不想使用树形图,又称红黑搜索树呢?因为红黑树在内部需要大量的旋转来保持平衡,特别是如果有大量的添加,我假设这发生在浏览器中,因为用户可以从已知站点跳到新站点。HashMap的性能会更好,问题是使用辅助数据结构对内容进行排序和移动。您希望历史缓存中有多少个站点?如果你一整年有10万页,我会很惊讶,但即使是100万页也没什么大不了的。只需将它们存储在线性列表中并按顺序搜索即可。这对于像浏览器这样的用户界面应用来说足够快了。太好了!。但这两者是如何联系在一起的呢?hashmap值是历史记录列表,那么它到底包含什么呢?对象在列表中的位置?@user775093使用哈希映射确保url不会在历史记录列表中出现两次。假设一个url位于历史记录列表中的
list[i]
-当您第二次从哈希映射中检索list[i]
时添加url,然后在其上调用remove
,将list[i-1]
拼接到list[i+1]
,从而从列表中删除list[i]
(现在是list[null]
),然后在列表末尾追加List[null]
,使其成为List[last]
。如果没有哈希映射,从历史记录列表中删除重复的URL将是一个线性时间操作。