在C#中寻找一个简单的独立持久字典实现

对于一个开源项目,我正在寻找一个由文件支持的字典的简单实现。 意思是,如果应用程序崩溃或重新启动字典将保持其状态。 我希望每次触摸字典时都更新底层文件。 (添加值或删除值)。 FileWatcher不是必需的,但它可能很有用。

class PersistentDictionary : IDictionary { public PersistentDictionary(string filename) { } } 

要求:

  • 开源,不依赖于本机代码(没有sqlite)
  • 理想情况下,这是一个非常简短的实现
  • 设置或清除值时,不应重写整个基础文件,而应搜索文件中的位置并更新值。

类似的问题

  • .Net中的持久二进制树/哈希表
  • c#的磁盘支持字典/缓存
  • PersistentDictionary

  • bplustreedotnet

    bplusdotnet包是C#,java和Python中交叉兼容的数据结构实现的库,对于需要存储和检索持久信息的应用程序非常有用。 bplusdotnet数据结构可以轻松存储与值关联的字符串键

  • ESENT管理界面

    不是100%托管代码,但值得一提的是,作为非托管库本身已经是每个Windows XP / 2003 / Vista / 7盒子的一部分

    ESENT是一个可嵌入的数据库存储引擎(ISAM),它是Windows的一部分。 它提供可靠的,事务性的,并发的,高性能的数据存储,具有行级锁定,预写日志记录和快照隔离。 这是ESENT Win32 API的托管包装器。

  • Akavache

    * Akavache是​​一个异步,持久的键值缓存,用于在C#中编写本机桌面和移动应用程序。 可以把它想象成桌面应用程序的memcached。

C5通用馆藏图书馆

C5提供标准.Net System.Collections.Generic命名空间未提供的function和数据结构,例如持久性树数据结构 ,基于堆的优先级队列,哈希索引数组列表和链接列表,以及有关集合更改的事件。

我来分析一下:

  1. 按键检索信息
  2. 持久存储
  3. 1值更改时,不想写回整个文件
  4. 应该能够幸免于难

我想你想要一个数据库。

编辑:我认为你正在寻找错误的东西。 搜索符合您要求的数据库。 并改变你的一些要求,因为我认为很难满足所有这些要求。

我已经实现了你正在寻找的那种PersistedDictionary。 底层存储是内置于Windows中的ESENT数据库引擎。 代码可在此处获得:

http://managedesent.codeplex.com/

一种方法是使用内置于windoows中的可扩展存储引擎来存储您的东西。 它是一个原生的win数据库,支持索引,事务等…

我正在努力将EHCache移植到.NET。 看看这个项目

http://sourceforge.net/projects/thecache/

持久性缓存是已经实现的核心function。 所有主要unit testing都在通过。 我对分布式缓存有点困惑,但你不需要那部分。

听起来很酷,但是你如何绕过存储值的变化(如果它是一个引用类型)呢? 如果它是不可变的那么一切都很好,但如果不是你有点填充:-)

如果你不处理不可变值,我会怀疑更好的方法是处理值级别的持久性,并根据需要重建字典。

(编辑添加澄清)

我发现这个链接听起来像你在找什么。 它是用Java编程的,但它不应该很难将它移植到C#:

http://www.javaworld.com/javaworld/jw-01-1999/jw-01-step.html

我认为你的问题很可能是最后一点:

设置或清除值时,不应重写整个基础文件,而应搜索文件中的位置并更新值。

这正是数据库的作用 – 您基本上是在描述一个基于文件的简单表结构。

我们可以通过查看字符串来说明问题。

内存中的字符串是灵活的东西 – 当你声明它的类型时,你不需要知道C#中字符串的长度。

在数据存储字符串和其他一切都是固定大小。 您在磁盘上保存的字典只是一个字节集合,按顺序排列。

如果你在中间替换一个值,它必须是完全相同的大小,否则你将不得不重写它之后的每个字节

这就是大多数数据库将text和blob字段限制为固定大小的原因。 Sql 2005+中的varchar(max) / varbinary(max)等新function实际上是对行的巧妙简化,只实际存储指向实际数据的指针。

您不能在示例中使用固定大小,因为它是通用的 – 您不知道将要存储的类型,因此您无法将值填充到最大大小。

你可以这样做:

 class PersistantDictionary : Dictionary where V:struct 

…因为值类型的存储大小不同,但您必须小心实施,以便为每种类型保存适当的存储量。

但是,您的模型不会非常高效 – 如果您查看SQL Server和Oracle如何处理表更改,它们不会更改这样的值。 相反,他们将旧记录标记为鬼,并使用新值添加新记录。 当数据库不太忙时,旧的幻影记录会在以后清除。

我想你正试图重新发明轮子:

  • 如果您正在处理大量数据,那么您真的需要使用完整的数据库进行检查。 MySql或SqlLite都很好,但你不会找到一个好的,简单的,开源和精简的实现。

  • 如果你没有处理大量数据,那么我会选择整个文件序列化,并且已经有很多关于如何做到这一点的好建议。

我自己写了一个实现,基于我前一段时间对另一个项目非常相似(我认为相同)的要求。 当我这样做时,我意识到的一件事是,你大部分时间都在做写作,你只会在程序崩溃或关闭时很少读取。 因此,我们的想法是尽可能快地进行写入。 我所做的是创建一个非常简单的类,它只会在发生事情时将所有操作(添加和删除)的日志写入字典。 所以过了一会儿你会在键之间重复一遍。 因此,一旦对象检测到一定量的重复,它将清除日志并重写它,以便每个键及其值仅出现一次。

不幸的是,您不能将Dictionary子类化,因为您无法覆盖其中的任何内容。 这是我的简单实现,虽然我很抱歉,但我还没有测试过,我想你可能想要这个想法。 随意使用它并根据需要进行更改。

 class PersistentDictManager { const int SaveAllThreshold = 1000; PersistentDictManager(string logpath) { this.LogPath = logpath; this.mydictionary = new Dictionary(); 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 kv in this.mydictionary) { writer.Write(kv.Key); writer.Write(false); // is not deleted flag writer.Write(kv.Value); } } } private readonly Dictionary mydictionary; private int saveallcount = 0; private BinaryWriter writer = null; } 

我推荐SQL Server Express或其他数据库。

  • 免费。
  • 它与C#集成得非常好,包括LINQ。
  • 它比自制解决方案更快。
  • 它比自制解决方案更可靠。
  • 它比简单的基于磁盘的数据结构更强大,因此将来很容易做得更多。
  • SQL是行业标准,因此其他开发人员将更容易理解您的程序,并且您将拥有将来有用的技能。

只需使用序列化。 查看BinaryFormatter类。

我不知道有什么可以解决你的问题。 它需要是一个固定大小的结构,以便您可以满足能够重写记录而无需重写整个文件的要求。

这意味着普通字符串已经输出。

就像道格拉斯所说,你需要知道你的类型的固定大小(T和V)。 此外,任何这些实例引用的对象网格中的可变长度实例都已输出。

仍然,实现由文件支持的字典非常简单,在inheritance或封装Dictionary类之后,您可以使用BinaryWriter类将类型写入磁盘。

考虑一个内存映射文件。 我不确定.NET中是否有直接支持,但你可以调用Win32调用。

我实际上并没有使用它,但这个项目显然提供了一个mmap() – 就像在C#中实现一样

MMAP

我不是一个程序员,但是不会创建一个非常简单的XML格式来存储你的数据吗?

   MyKey My val  ...  

从那里,你加载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保持在内存性能方面,那么它很容易处理。 根据您的要求,您可能根本不需要字典。