Buscando una implementación de diccionario persistente independiente simple en C# [cerrado]


Para un proyecto de código abierto estoy buscando una implementación buena y simple de un diccionario que esté respaldado por un archivo. Es decir, si una aplicación se bloquea o se reinicia, el diccionario mantendrá su estado. Me gustaría que actualizara el archivo subyacente cada vez que se toque el diccionario. (Añadir un valor o eliminar un valor). Un FileWatcher no es necesario, pero podría ser útil.

class PersistentDictionary<T,V> : IDictionary<T,V>
{
    public PersistentDictionary(string filename)
    {

    } 
}

Requisitos:

  • Código abierto, sin dependencia del código nativo (sin sqlite)
  • Idealmente una implementación muy corta y simple
  • Al establecer o borrar un valor, no debe reescribir todo el archivo subyacente, sino que debe buscar la posición en el archivo y actualizar el valor.

Preguntas Similares

 24
Author: Community, 2008-09-19

17 answers

  • Bplustreedotnet

    El paquete bplusdotnet es una biblioteca de implementaciones de estructura de datos compatibles en C#, java y Python que son útiles para aplicaciones que necesitan almacenar y recuperar información persistente. Las estructuras de datos de bplusdotnet facilitan almacenar claves de cadena asociadas con valores de forma permanente.

  • Interfaz Gestionada ESENT

    No es código 100% administrado, pero vale la pena mencionarlo como biblioteca no administrada ya es parte de cada windows XP/2003 / Vista / 7 box

    ESENT es un motor de almacenamiento de base de datos incrustable (ISAM) que forma parte de Windows. Proporciona almacenamiento de datos confiable, transactado, concurrente y de alto rendimiento con bloqueo a nivel de fila, registro de escritura anticipada y aislamiento de instantáneas. Este es un wrapper administrado para la API ESENT Win32.

  • Akavache

    * Akavache es un valor clave asíncrono y persistente caché creada para escribir aplicaciones nativas de escritorio y móviles en C#. Piense en ello como memcached para aplicaciones de escritorio.

- La Biblioteca de la Colección Genérica C5

C5 proporciona funcionalidad y estructuras de datos no proporcionadas por el espacio de nombres estándar.Net System.Collections.Generic, como estructuras de datos de árbol persistentes, colas de prioridad basadas en montones, listas de matrices indexadas hash y listas vinculadas, y eventos sobre cambios de colección.

 23
Author: lubos hasko,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-01-04 00:56:03

Permítanme analizar esto:

  1. Recuperar información por clave
  2. Almacenamiento persistente
  3. No desea escribir todo el archivo cuando cambia 1 valor
  4. Debería sobrevivir a los accidentes

Creo que quieres una base de datos.

Editar: Creo que estás buscando lo incorrecto. Busque una base de datos que se ajuste a sus necesidades. Y cambie algunos de sus requisitos, porque creo que será difícil cumplirlos todos.

 14
Author: GvS,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-01-28 08:21:14

He implementado el tipo de PersistedDictionary que está buscando. El almacenamiento subyacente es el motor de base de datos ESENT que está integrado en Windows. El código está disponible aquí:

Http://managedesent.codeplex.com /

 8
Author: ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-09-12 23:11:19

Una forma es usar el Extensible Storage Engine integrado en windoows para almacenar tus cosas. Es una base de datos nativa de win que admite indexación, transacciones, etc...

 6
Author: Mladen Prajdic,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-01-27 11:03:52

Estaba trabajando en portar EHCache a. NET. Echa un vistazo al proyecto

Http://sourceforge.net/projects/thecache /

El almacenamiento en caché persistente es la funcionalidad principal que ya está implementada. Todas las pruebas Unitarias están pasando. Me quedé un poco atascado en el almacenamiento en caché distribuido, pero no necesita esa parte.

 2
Author: Timur Fanshteyn,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-01-31 08:03:17

Suena bien, pero ¿cómo evitará los cambios en el valor almacenado (si fuera un tipo de referencia) en sí? Si es inmutable, entonces todo está bien, pero si no estás un poco relleno : -)

Si no está tratando con valores inmutables, sospecharía que un mejor enfoque sería manejar la persistencia a nivel de valor y reconstruir el diccionario según sea necesario.

(editado para añadir una aclaración)

 1
Author: chrisb,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-09-19 09:15:58

Encontré este enlace y suena como lo que estás buscando. Está programado en Java, pero no debería ser tan difícil portarlo a C#:

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

 1
Author: Igor Zelaya,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-01-29 07:31:47

Creo que su problema es probable que sea ese último punto:

Al establecer o borrar un valor, no debe reescribir todo el archivo subyacente, sino que debe buscar la posición en el archivo y actualizar el valor.

Esto es exactamente lo que hace una base de datos: básicamente estás describiendo una estructura de tabla simple basada en archivos.

Podemos ilustrar el problema mirando las cadenas.

Las cadenas en la memoria son cosas flexibles - no necesita saber la longitud de una cadena en C# cuando se declara su tipo.

En las cadenas de almacenamiento de datos y todo lo demás son tamaños fijos. Su diccionario guardado en el disco es solo una colección de bytes, en orden.

Si reemplaza un valor en el medio, tiene que ser exactamente del mismo tamaño o tendrá que reescribir cada byte que viene después de.

Esta es la razón por la que la mayoría de las bases de datos restringen los campos de texto y blob a tamaños fijos. Nuevas características como varchar(max)/varbinary(max) en Sql 2005 + son realmente inteligentes simplificaciones a la fila solo almacenan realmente un puntero a los datos reales.

No puede usar los tamaños fijos con su ejemplo porque es genérico: no sabe qué tipo va a almacenar, por lo que no puede rellenar los valores a un tamaño máximo.

Usted podría hacer:

class PersistantDictionary<T,V> : Dictionary<T,V>
    where V:struct

As como los tipos de valor no varían en tamaño de almacenamiento, aunque tendría que tener cuidado con su implementación para guardar la cantidad correcta de almacenamiento para cada tipo.

Sin embargo su modelo no sería muy eficiente: si observa cómo SQL Server y Oracle manejan los cambios de tabla, no cambian los valores de esta manera. En su lugar, marcan el registro antiguo como un fantasma y agregan un nuevo registro con el nuevo valor. Los viejos registros fantasma se limpian más tarde cuando la base de datos está menos ocupada.

Creo que estás tratando de reinventar la rueda:

  • Si estás tratando con grandes cantidades de datos, entonces realmente necesitas revisar usando una base de datos completa. MySQL o SQLLite son ambos bien, pero no vas a encontrar una implementación buena, simple, de código abierto y lite.

  • Si no estás lidiando con un montón de datos, entonces iría por la serialización de archivos completos, y ya hay muchas buenas sugerencias aquí sobre cómo hacerlo.

 1
Author: Keith,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-01-29 13:15:11

Yo mismo escribí una implementación basada en un requisito muy similar (creo que idéntico) que tenía en otro proyecto hace un tiempo. Cuando lo hice, una cosa que me di cuenta fue que la mayoría de las veces que vas a hacer escribe, solo haces una lectura rara vez cuando el programa se bloquea o cuando está cerrado. Así que la idea es hacer las escrituras lo más rápido posible. Lo que hice fue hacer una clase muy simple que simplemente escribiría un registro de todas las operaciones (adiciones y eliminaciones) al diccionario como cosas ocurrir. Así que después de un tiempo se obtiene un montón de repetición entre las teclas. Debido a eso, una vez que el objeto detecta una cierta cantidad de repetición, borrará el registro y lo reescribirá para que cada clave y su valor solo aparezcan una vez.

Desafortunadamente, no puedes usar el diccionario de subclases porque no puedes anular nada en él. Esta es mi implementación simple, no la he probado, aunque lo siento, pensé que podría querer la idea. Siéntase libre de usarlo y cambiarlo tanto como desee.

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;
}
 1
Author: Ray Hidayat,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-01-30 13:41:34

Yo recomendaría SQL Server Express u otra base de datos.

  • Es gratis.
  • Se integra muy bien con C#, incluyendo LINQ.
  • Es más rápido que una solución casera.
  • Es más confiable que una solución casera.
  • Es mucho más potente que una simple estructura de datos basada en disco, por lo que será fácil hacer más en el futuro.
  • SQL es un estándar de la industria, por lo que otros desarrolladores entenderán su programa más fácilmente, y tendrá un habilidad que es útil en el futuro.
 1
Author: Jay Bazuzi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-02-02 03:15:54

Echa un vistazo a este blog:

Http://ayende.com/Blog/archive/2009/01/17/rhino.dht-ndash-persistent-amp-distributed-storage.aspx

Parece ser exactamente lo que estás buscando.

 1
Author: Mouk,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-01-31 02:35:47

Simplemente use serialización. Mira la clase BinaryFormatter.

 0
Author: leppie,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-09-19 09:18:43

No se nada para resolver tu problema. Tendrá que ser una estructura de tamaño fijo, para que pueda cumplir con los requisitos de poder reescribir registros sin reescribir todo el archivo.

Esto significa que las cadenas normales están fuera.

 0
Author: Douglas Leeder,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-10-03 19:01:45

Como dijo Douglas, necesitas saber el tamaño fijo de tus tipos (tanto T como V). Además, las instancias de longitud variable en la cuadrícula de objetos a las que hace referencia cualquiera de esas instancias están fuera.

Aún así, implementar un diccionario respaldado por un archivo es bastante simple y puede usar la clase BinaryWriter para escribir los tipos en el disco, después de heredar o encapsular la clase Dictionary<TKey, TValue>.

 0
Author: Omer van Kloeten,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-10-06 23:01:11

Considere un archivo mapeado de memoria. No estoy seguro de si hay soporte directo en.NET, pero podría pinvoke las llamadas Win32.

 0
Author: kenny,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-01-27 11:04:41

En realidad no lo he usado, pero este proyecto aparentemente proporciona una implementación similar a mmap () en C#

Mmap

 0
Author: axel_c,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-01-27 11:33:35

No soy un gran programador, pero no crearía un formato XML realmente simple para almacenar sus datos hacer el truco?

<dico> 
   <dicEntry index="x">
     <key>MyKey</key>
     <val type="string">My val</val>
   </dicEntry>
   ...
</dico>

Desde allí, carga el archivo XML DOM y llena tu diccionario como quieras,

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
}

Luego, cuando lo desee, puede actualizar el DOM y guardarlo en el disco.

xdocDico.save(sXMLfile);

Si puede permitirse el lujo de mantener el DOM en memoria en cuanto al rendimiento, es bastante fácil de tratar. Dependiendo de sus requisitos, es posible que ni siquiera necesite el diccionario en todo.

 -1
Author: ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-01-27 12:55:28