tutorial - Buscando una implementación simple e independiente del diccionario en C#
lenguaje de programacion c# ventajas y desventajas (17)
Para un proyecto de código abierto, estoy buscando una implementación buena y simple de un diccionario respaldado por un archivo. Es decir, si una aplicación falla o se reinicia, el diccionario mantendrá su estado. Me gustaría actualizar el archivo subyacente cada vez que se toca el diccionario. (Agregue un valor o elimine un valor). No se requiere un FileWatcher 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 volver a escribir todo el archivo subyacente, sino que debe buscar la posición en el archivo y actualizar el valor.
Preguntas similares
Solo usa serialización. Mira la clase BinaryFormatter.
Suena genial, pero ¿cómo va a solucionar los cambios en el valor almacenado (si fuera un tipo de referencia)? Si es inmutable, entonces todo está bien, pero si no estás un poco relleno :-)
Si no se trata de valores inmutables, sospecho que un mejor enfoque sería manejar la persistencia en el nivel de valor y simplemente reconstruir el diccionario según sea necesario.
(editado para agregar una aclaración)
No sé nada para resolver tu problema. Tendrá que ser una estructura de tamaño fijo, para que pueda cumplir con los requisitos de poder reescribir los registros sin reescribir el archivo completo.
Esto significa que las cadenas normales están fuera.
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 referenciadas por cualquiera de esas instancias están fuera.
Aún así, la implementación de 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>
.
Considere un archivo mapeado de memoria. No estoy seguro de si hay soporte directo en .NET, pero podría pinvoke las llamadas de Win32.
En realidad, no lo he usado, pero este proyecto aparentemente proporciona una implementación similar a mmap () en C #
No soy muy programador, ¿pero no crear un formato XML realmente simple para almacenar tus datos es suficiente?
<dico>
<dicEntry index="x">
<key>MyKey</key>
<val type="string">My val</val>
</dicEntry>
...
</dico>
A partir de ahí, carga el archivo XML DOM y rellena el diccionario como lo desee,
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 mantener el DOM en la memoria en cuanto al rendimiento, es bastante fácil de tratar. Dependiendo de sus requisitos, es posible que ni siquiera necesite el diccionario.
Una forma es usar el motor de almacenamiento extensible integrado en windoows para almacenar sus cosas. Es una base de datos win nativa que admite indización, transacciones, etc.
Déjame analizar esto:
- Recuperar información por clave
- Almacenamiento persistente
- No quiero volver a escribir todo el archivo cuando cambia el valor 1
- Debería sobrevivir a los accidentes
Creo que quieres una base de datos.
Editar: creo que estás buscando algo equivocado. Busque una base de datos que se adapte a sus necesidades. Y cambie algunos de sus requisitos, porque creo que será difícil cumplirlos a todos.
Creo que su problema probablemente sea ese último punto:
Al establecer o borrar un valor, no debe volver a escribir todo el archivo subyacente, sino que debe buscar la posición en el archivo y actualizar el valor.
Esto es exactamente lo que hace un DB: básicamente está describiendo una estructura de tabla simple basada en archivos.
Podemos ilustrar el problema mirando cadenas.
Las cadenas en la memoria son cosas flexibles: no necesita saber la longitud de una cadena en C # cuando 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 volver a escribir cada byte que viene después .
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. Las nuevas características como varchar(max)
/ varbinary(max)
en Sql 2005+ son en realidad simplificaciones inteligentes para la fila que solo almacenan 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 hasta un tamaño máximo.
Podrías hacerlo:
class PersistantDictionary<T,V> : Dictionary<T,V>
where V:struct
... ya que los tipos de valor no varían en tamaño de almacenamiento, aunque deberá 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 el servidor SQL y Oracle se ocupan de los cambios en la tabla, no cambian los valores de esta manera. En su lugar, marcan el antiguo registro como un fantasma y agregan un nuevo registro con el nuevo valor. Los viejos registros fantasma se limpian más tarde cuando el DB está menos ocupado.
Creo que estás tratando de reinventar la rueda:
Si está tratando con grandes cantidades de datos, entonces realmente necesita consultar usando una base de datos en toda regla. MySql o SqlLite son buenos, pero no vas a encontrar una implementación buena, simple, de código abierto y ligera.
Si no está lidiando con una gran cantidad de datos, entonces elegiría la serialización de todo el archivo, y ya hay muchas buenas sugerencias aquí sobre cómo hacerlo.
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
Escribí una implementación yo mismo basado en un requisito muy similar (creo que idéntico) que tuve en otro proyecto hace un tiempo. Cuando lo hice, una cosa de la que me di cuenta fue que la mayoría de las veces escribirás, solo lees rara vez cuando el programa falla o cuando está cerrado. Entonces, la idea es hacer las escrituras lo más rápido posible. Lo que hice fue crear una clase muy simple que simplemente escribiera un registro de todas las operaciones (adiciones y eliminaciones) en el diccionario a medida que ocurrían las cosas. Entonces, después de un tiempo, se repiten muchas veces entre las teclas. Por eso, una vez que el objeto detecta una cierta cantidad de repetición, borrará el registro y lo reescribirá de modo que cada clave y su valor solo aparezcan una vez.
Lamentablemente, no puede subclasificar el diccionario porque no puede anular nada en él. Esta es mi implementación simple, no lo he probado aunque lo siento, pensé que quizás querrías 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;
}
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 de la unidad principal están pasando. Me quedé un poco atrapado en el almacenamiento en caché distribuido, pero no necesitas esa parte.
Recomendaría SQL Server Express u otra base de datos.
- Es gratis.
- Se integra muy bien con C #, incluido LINQ.
- Es más rápido que una solución casera.
- Es más confiable que una solución casera.
- Es mucho más poderoso que una simple estructura de datos basada en disco, por lo que será más 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 usted tendrá una habilidad que es útil en el futuro.
Implementé el tipo de PersistedDictionary que estás buscando. El almacenamiento subyacente es el motor de base de datos ESENT que está integrado en Windows. El código está disponible aquí:
El paquete bplusdotnet es una biblioteca de implementaciones de estructura de datos compatibles entre sí en C #, java y Python, que son útiles para aplicaciones que necesitan almacenar y recuperar información persistente. Las estructuras de datos bplusdotnet hacen que sea más fácil almacenar claves de cadena asociadas con valores de forma permanente .
No es 100% código administrado, pero vale la pena mencionarlo ya que la biblioteca no administrada ya es parte de cada caja de Windows XP / 2003 / Vista / 7
ESENT es un motor de almacenamiento de base de datos incrustable (ISAM) que es parte de Windows. Proporciona almacenamiento de datos de alto rendimiento confiable, transaccionado, concurrente con bloqueo de nivel de fila, registro de escritura anticipada y aislamiento de instantáneas. Este es un contenedor administrado para la API de ESENT Win32.
* Akavache es un caché de valor-clave asíncrono y persistente creado para escribir aplicaciones móviles y de escritorio nativas en C #. Piense en ello como memcached para aplicaciones de escritorio.
- La Biblioteca de Colección Genérica C5
C5 proporciona funcionalidad y estructuras de datos no proporcionadas por .Net System.Collections.Generic
namespace, como estructuras de datos de árbol persistentes , colas de prioridad basadas en heap, listas de matrices indexadas en hash y listas enlazadas, y eventos en cambios de colección.
Mira 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.