在C#中寻找简单的独立持久字典实现
对于一个开放源代码项目,我正在寻找一种良好且简单的由文件支持的Dictionary的实现。这意味着,如果应用程序崩溃或者重新启动,字典将保持其状态。我希望每次触摸字典时都更新基础文件。 (添加值或者删除值)。 FileWatcher不是必需的,但可能很有用。
class PersistentDictionary<T,V> : IDictionary<T,V> { public PersistentDictionary(string filename) { } }
要求:
- 开源,不依赖本机代码(没有sqlite)
- 理想的情况是非常简短的实现
- 设置或者清除值时,不应重写整个基础文件,而应查找文件中的位置并更新值。
类似问题
- .Net中的持久性二叉树/哈希表
- 磁盘支持的字典/ C#缓存
- `PersistentDictionary <键,值>
解决方案
听起来很酷,但是我们如何解决存储值(如果是引用类型)本身的更改?如果它是不可变的,那么一切都很好,但是如果不是,那你就有点塞满了:-)
如果我们不处理不可变的值,我怀疑更好的方法是在值级别上处理持久性,并根据需要重新构建字典。
(编辑以添加说明)
只需使用序列化即可。查看BinaryFormatter类。
我什么都不知道能解决问题。它必须是固定大小的结构,以便可以满足无需重写整个文件即可重写记录的要求。
这意味着正常的字符串已用完。
就像道格拉斯所说的那样,我们需要知道类型的固定大小(T和V)。同样,这些实例中的任何实例引用的对象网格中的可变长度实例也将消失。
尽管如此,实现由文件支持的字典还是非常简单的,在继承或者封装了Dictionary <TKey,TValue>类之后,我们可以使用BinaryWriter类将类型写入磁盘。
- bplustreedotnet bplusdotnet包是C#,java和Python中交叉兼容的数据结构实现的库,对于需要存储和检索持久性信息的应用程序很有用。 bplusdotnet数据结构使永久存储与值关联的字符串键变得容易。
- ESENT托管接口不是100%托管代码,但值得一提的是,非托管库本身已经是每个Windows XP / 2003 / Vista / 7机器的一部分ESENT是Windows的一部分,是嵌入式数据库存储引擎(ISAM)。它通过行级锁定,预写日志记录和快照隔离提供可靠的事务处理并发高性能数据存储。这是ESENT Win32 API的托管包装。
- Akavache * Akavache是一个异步的,持久性的键值缓存,用于在C#中编写本机桌面和移动应用程序。可以将其视为桌面应用程序的memcached。
C5通用收藏库
C5提供了标准.NetSystem.Collections.Generic
命名空间未提供的功能和数据结构,例如持久树数据结构,基于堆的优先级队列,哈希索引数组列表和链接列表,以及集合更改事件。
一种方法是使用内置于窗口中的可扩展存储引擎来存储东西。这是一个本地的win数据库,支持索引编制,事务处理等。
考虑一个内存映射文件。我不确定.NET中是否有直接支持,但是我们可以调用Win32调用。
我没有实际使用过,但是该项目显然在C#中提供了类似mmap()的实现
地图
我不是一个程序员,但是不会创建一个非常简单的XML格式来存储数据吗?
<dico> <dicEntry index="x"> <key>MyKey</key> <val type="string">My val</val> </dicEntry> ... </dico>
在这里,我们可以加载XML文件DOM,并根据需要填充字典,
XmlDocument xdocDico = new XmlDocument(); string sXMLfile; public loadDico(string sXMLfile, [other args...]) { xdocDico.load(sXMLfile); // Gather whatever you need and load it into your dico } public flushDicInXML(string sXMLfile, dictionary dicWhatever) { // Dump the dic in the XML doc & save } public updateXMLDOM(index, key, value) { // Update a specific value of the XML DOM based on index or key }
然后,我们可以随时更新DOM并将其保存在磁盘上。
xdocDico.save(sXMLfile);
如果我们有能力将DOM保留在内存性能方面,那么它很容易处理。根据要求,我们甚至可能根本不需要字典。
让我分析一下:
- 通过密钥检索信息
- 持久存储
- 1值更改时,不想写回整个文件
- 应该在崩溃中幸存
我认为我们想要一个数据库。
编辑:我认为我们正在寻找错误的东西。搜索符合我们要求的数据库。并更改一些要求,因为我认为很难满足所有要求。
我找到了此链接,听起来像我们在找什么。它是用Java编程的,但移植到C#并不难:
http://www.javaworld.com/javaworld/jw-01-1999/jw-01-step.html
我认为问题很可能是最后一点:
When setting or clearing a value it should not re-write the entire underlying file, instead it should seek to the position in the file and update the value.
这正是数据库基本上在描述基于文件的简单表结构的方式。
我们可以通过查看字符串来说明问题。
内存中的字符串是灵活的事情,我们在声明字符串类型时无需知道C中字符串的长度。
在数据存储字符串中,其他所有内容都是固定大小的。我们在磁盘上保存的字典按顺序只是字节的集合。
如果我们在中间替换一个值,则其大小必须完全相同,否则我们将不得不重写紧随其后的每个字节。
这就是为什么大多数数据库都将text和blob字段限制为固定大小的原因。 Sql 2005+中的varchar(max)
/varbinary(max)
等新功能实际上是对行的巧妙简化,仅实际存储了指向实际数据的指针。
我们不能在示例中使用固定大小,因为它是通用的,我们不知道将要存储什么类型,因此无法将值填充到最大大小。
我们可以这样做:
class PersistantDictionary<T,V> : Dictionary<T,V> where V:struct
...由于值类型的存储大小没有变化,尽管我们必须谨慎实施以为每种类型保存正确的存储量。
但是,如果我们查看SQL Server和Oracle如何处理表更改,它们不会像这样改变值,那么模型就不会非常有效。相反,他们将旧记录标记为幽灵,并使用新值添加新记录。当数据库不太繁忙时,将清除旧的幻影记录。
我认为我们正在尝试重新发明轮子:
- 如果我们要处理大量数据,则确实需要使用功能完善的数据库进行签出。 MySql或者SqlLite都很好,但是我们不会找到一个好的,简单的,开源的和lite的实现。
- 如果我们不处理大量数据,那么我将进行整个文件序列化,并且这里已经有很多关于如何执行此操作的好建议。
我建议使用SQL Server Express或者其他数据库。
- 免费。
- 它与C#(包括LINQ)很好地集成在一起。
- 它比自制解决方案快。
- 它比自制解决方案更可靠。
- 它比基于磁盘的简单数据结构更强大,因此将来可以轻松完成更多工作。
- SQL是一种行业标准,因此其他开发人员将更容易理解程序,并且我们将拥有将来有用的技能。
我根据一段时间前对另一个项目的非常相似(我认为相同)的要求,自己编写了一个实现。当我这样做时,我意识到的一件事是,在大多数情况下,我们将要进行写操作,而在程序崩溃或者关闭时,我们很少执行读操作。因此,其想法是使写入尽可能快。我所做的是创建一个非常简单的类,该类将在发生任何事情时将所有操作(添加和删除)的日志记录到字典中。因此,过了一会儿,我们会在键之间重复很多。因此,一旦对象检测到一定数量的重复,它将清除日志并将其重写,因此每个键及其值仅出现一次。
不幸的是,我们不能对Dictionary进行子类化,因为我们无法覆盖其中的任何内容。这是我的简单实现,对不起,我还没有测试过,但是我想我们可能想要这个主意。随意使用并根据需要进行更改。
class PersistentDictManager { const int SaveAllThreshold = 1000; PersistentDictManager(string logpath) { this.LogPath = logpath; this.mydictionary = new Dictionary<string, string>(); this.LoadData(); } public string LogPath { get; private set; } public string this[string key] { get{ return this.mydictionary[key]; } set{ string existingvalue; if(!this.mydictionary.TryGetValue(key, out existingvalue)) { existingvalue = null; } if(string.Equals(value, existingvalue)) { return; } this[key] = value; // store in log if(existingvalue != null) { // was an update (not a create) if(this.IncrementSaveAll()) { return; } // because we're going to repeat a key the log } this.LogStore(key, value); } } public void Remove(string key) { if(!this.mydictionary.Remove(key)) { return; } if(this.IncrementSaveAll()) { return; } // because we're going to repeat a key in the log this.LogDelete(key); } private void CreateWriter() { if(this.writer == null) { this.writer = new BinaryWriter(File.Open(this.LogPath, FileMode.Open)); } } private bool IncrementSaveAll() { ++this.saveallcount; if(this.saveallcount >= PersistentDictManager.SaveAllThreshold) { this.SaveAllData(); return true; } else { return false; } } private void LoadData() { try{ using(BinaryReader reader = new BinaryReader(File.Open(LogPath, FileMode.Open))) { while(reader.PeekChar() != -1) { string key = reader.ReadString(); bool isdeleted = reader.ReadBoolean(); if(isdeleted) { this.mydictionary.Remove(key); } else { string value = reader.ReadString(); this.mydictionary[key] = value; } } } } catch(FileNotFoundException) { } } private void LogDelete(string key) { this.CreateWriter(); this.writer.Write(key); this.writer.Write(true); // yes, key was deleted } private void LogStore(string key, string value) { this.CreateWriter(); this.writer.Write(key); this.writer.Write(false); // no, key was not deleted this.writer.Write(value); } private void SaveAllData() { if(this.writer != null) { this.writer.Close(); this.writer = null; } using(BinaryWriter writer = new BinaryWriter(File.Open(this.LogPath, FileMode.Create))) { foreach(KeyValuePair<string, string> kv in this.mydictionary) { writer.Write(kv.Key); writer.Write(false); // is not deleted flag writer.Write(kv.Value); } } } private readonly Dictionary<string, string> mydictionary; private int saveallcount = 0; private BinaryWriter writer = null; }
我正在将EHCache移植到.NET。看看这个项目
http://sourceforge.net/projects/thecache/
持久缓存是已经实现的核心功能。所有主要单元测试都通过了。我在分布式缓存方面有些困难,但是我们不需要那部分。
查看此博客:
http://ayende.com/Blog/archive/2009/01/17/rhino.dht-ndash-persistent-amp-distributed-storage.aspx
看起来正是我们想要的。
我已经实现了我们想要的PersistedDictionary。基础存储是内置在Windows中的ESENT数据库引擎。该代码在此处可用:
http://managedesent.codeplex.com/