2010-07-26 25 views
7

Tengo este enorme archivo XML de tamaño 2.8GB. Este es el volcado de artículos de la Wikipedia polaca. El tamaño de este archivo es muy problemático para mí. La tarea es buscar este archivo para una gran cantidad de datos. Todo lo que tengo son títulos de los artículos. Pensé que podría ordenar esos títulos y utilizar un ciclo lineal en el archivo. La idea no es tan mala, pero los artículos son no ordenados alfabéticamente. Están ordenados por ID, que no sé a priori.Trabajando con un archivo XML muy grande en C#

Entonces, mi segunda idea fue hacer un índice de ese archivo. Para almacenar en otras líneas de archivos (o bases de datos) en el siguiente formato: title;id;index (tal vez sin una identificación). Yo mi otra pregunta pedí ayuda con eso. La hipótesis era que, si tenía el índice de etiqueta necesaria, podía usar el método simple Seek para mover el cursor dentro del archivo sin leer todo el contenido, etc. Para archivos más pequeños, creo que esto podría funcionar bien. Pero en mi computadora (computadora portátil, C2D proc, Win7, VS2008) me da error de que la aplicación no responde.

En mi programa, estoy leyendo cada línea del archivo y compruebo si contiene una etiqueta que necesito. También estoy contando todos los bytes que leo y guardo líneas en el formato mencionado anteriormente. Entonces, mientras el programa de indexación se cuelga. Pero hasta entonces, el archivo índice de resultados es 36.2MB y el último índice es como 2.872.765.202 (B) mientras que el archivo XML completo es 3.085.439.630 B de largo.

Mi tercer pensamiento fue dividir el archivo en trozos más pequeños. Para ser precisos en 26 piezas (hay 26 letras en idioma latino), cada una contiene solo entradas que comienzan por la misma letra, p. en a.xml, todas las entradas que comiencen con títulos en la letra "A". Los archivos finales serían como decenas de MB, máximo alrededor de 200 MB, creo. Pero existe el mismo problema con la lectura de todo el archivo.

Para leer el archivo, probablemente utilicé la forma más rápida: usando StreamReader. Leí en alguna parte que las clases StreamReader y XmlReader de System.Xml son los métodos más rápidos. StreamReader incluso más rápido que XmlReader. Es obvio que no puedo cargar todo este archivo en la memoria. He instalado solo 3GB de RAM y Win7 toma como 800MB-1GB cuando está completamente cargada.

Así que estoy pidiendo ayuda. ¿Qué es lo mejor que puedo hacer? El punto es que la búsqueda de este archivo XML tiene que ser rápida. Tiene que ser más rápido que descargar páginas únicas de Wikipedia en formato HTML. Ni siquiera estoy seguro de si eso es posible.

¿Tal vez cargar todo el contenido necesario en la base de datos? Tal vez eso sería más rápido? Pero aún tendré que leer todo el archivo al menos una vez.

No estoy seguro de si hay algunos límites sobre 1 longitud de pregunta, pero también incluiré una muestra de mi código fuente de indexación.

while (reading) 
{ 
    if (!reader.EndOfStream) 
    { 
     line = reader.ReadLine(); 
     fileIndex += enc.GetByteCount(line) + 2; //+2 - to cover characters \r\n not included into line 
     position = 0; 
    } 
    else 
    { 
     reading = false; 
     continue; 
    } 

    if (currentArea == Area.nothing) //nothing interesting at the moment 
    { 
     //search for position of <title> tag 
     position = MoveAfter("&lt;title>", line, position); //searches until it finds &lt;title> tag 
     if (position >= 0) currentArea = Area.title; 
     else continue; 
    } 

    (...) 

    if (currentArea == Area.text) 
    { 
     position = MoveAfter("&lt;text", line, position); 
     if (position >= 0) 
     { 
       long index = fileIndex; 
       index -= line.Length; 
       WriteIndex(currentTitle, currentId, index); 
       currentArea = Area.nothing; 
     } 
     else continue; 
    } 
} 

reader.Close(); 
reader.Dispose(); 
writer.Close(); 
} 

private void WriteIndex(string title, string id, long index) 
{ 
    writer.WriteLine(title + ";" + id + ";" + index.ToString()); 
} 

Saludos y gracias de antemano,

Ventus

Editar: Enlace al volcado de este wiki http://download.wikimedia.org/plwiki/20100629/

+0

No olvide que tengo incluso un problema con la lectura de todo el archivo. Lleva tanto tiempo con el código anterior, que VS cree que algo está mal aquí y detiene la ejecución posterior después de un tiempo. ¿Cómo evitar eso? – Ventus

+0

OK. Finalmente logré escanear todo el archivo y transferir su contenido a la base de datos SQLite. DB me gustan los 2,81GB así que esto trae a colación problemas de rendimiento. Pero esto es por otra pregunta. Usé el código anterior, en mi pregunta, ligeramente modificado. Tomó mucho tiempo para indexar este archivo, debido a mi falta de conocimiento sobre el uso de SQLite. Gracias por las respuestas. – Ventus

+0

siempre que trabaje con esa cantidad de datos brutos no indexados, llevará algún tiempo procesarlos. Pero al menos solo tienes que hacerlo una vez. Ahora que es un DB puedes obtener resultados en segundos. Buena suerte. –

Respuesta

6

Bueno, si se ajusta a sus requisitos, primero importaría este XML en un RDMS como SQL Server y luego consultaría contra este servidor SQL.

Con los índices correctos (índices de texto completo si desea buscar a través de una gran cantidad de texto), que debe ser bastante rápido ...

Sería reducir una gran cantidad de la sobrecarga proveniente del análisis de la archivo XML por las bibliotecas ...

+0

Gracias, pero no estoy seguro de si tendré acceso a cualquier motor de almacenamiento. – Ventus

+2

@Ventus. Esta es la única manera sensata de ir. eche un vistazo a SQLLite, derby o alguna otra solución de db incorporada. –

+0

Ah, claro, me olvidé de SQLite ... :) – Ventus

7

Bueno .... Si vas a buscarla, yo le recomiendo encarecidamente que encuentre una manera mejor que tratar con el archivo en sí. Sugiero, como mencionas, ponerlo en una base de datos bien normalizada e indexada y buscar allí. Cualquier otra cosa que hagas será efectivamente duplicar exactamente lo que hace una base de datos.

Sin embargo, hacerlo llevará tiempo.XmlTextReader es probablemente su mejor opción, funciona un nodo a la vez. LINQ to XML también debería ser un proceso bastante eficiente, pero no lo he probado con un archivo grande y, por lo tanto, no puedo comentar.

Puedo preguntar: ¿de dónde vino este enorme archivo XML? Tal vez haya una forma de tratar la situación en la fuente, en lugar de tener que procesar un archivo de 3 GB.

+1

+1, aunque sí mencionó la fuente (¿no?): "Este es el vertedero de artículos de la Wikipedia polaca". –

+0

@Jeff: ahh, lo perdí. – Randolpho

+0

XmlTextReader es solo una extensión de la clase XmlReader, por lo que la velocidad es la misma. LINQ to XML es más lento que Xml (Text) Reader.Eso es seguro – Ventus

0

XmlReader será rápido pero debe verificar si es lo suficientemente rápido en su escenario. Supongamos que estamos buscando un valor situado en un nodo denominado Item:

using (var reader = XmlReader.Create("data.xml")) 
{ 
    while (reader.Read()) 
    { 
     if (reader.NodeType == XmlNodeType.Element && reader.Name == "Item") 
     { 
      string value = reader.ReadElementContentAsString(); 
      if (value == "ValueToFind") 
      { 
       // value found 
       break; 
      } 
     } 
    } 
} 
0

Me gustaría hacer esto:

1) Romper el XML en archivos más pequeños. Por ejemplo, si el XML tiene este aspecto, crearía un archivo por nodo de artículo con un nombre que coincida con el atributo de título. Si el título no es único, entonces solo enumeraría los archivos.

Dado que se trata de una gran cantidad de archivos, los dividiría en subdirectorios, cada uno de los cuales no tendría más de 1000 archivos.

<root> 
    <article title="aaa"> ... </article> 
    <article title="bbb"> ... </article> 
    <article title="ccc"> ... </article> 
</root> 

2) Cree una tabla de índice con los nombres de archivo y las columnas que desea buscar.

3) Como opción, puede almacenar los fragmentos XML en la base de datos en lugar de en el disco duro. El tipo varChar (MAX) de SQL Server es bueno para esto.

+1

¿por qué almacenar el XML sin procesar? él debería simplemente analizar el XML y almacenar los datos en una base de datos. –

+0

Incluso si el XML es lo suficientemente limpio como para almacenar como tablas relacionales, y probablemente no lo sea, solo debe almacenar los datos que realmente desea buscar. Algo más y solo desperdicia espacio y puede causar problemas de rendimiento. –

+0

El problema es que necesito almacenar una gran cantidad de datos. Todo el mundo sabe que el vertedero de Wiki tendrá mucho texto. Y necesito recuperar ese texto. Entonces, ¿almacenar todos esos nodos de texto en la base de datos hará una búsqueda más rápida? – Ventus

2

me gusta la idea de crear un índice - usted tiene que mantener su código muy simple y no necesita ninguna dependencia horribles como las bases de datos :)

por lo tanto - Crear una índice donde almacena el siguiente

[content to search]:[byte offset to the start of the xml node that contains the content]

Para capturar el desplazamiento de bytes, deberá crear su propia transmisión sobre el archivo de entrada, y crear un lector a partir de eso. consultará la posición en cada lector. Lea (...). Un registro de índice ejemplo sería:

"Now is the winter of our discontent":554353

Esto significa que la entrada en el archivo XML que contiene "Ahora es el invierno de nuestro descontento" es en el nodo en la posición de byte 554.353. Nota: me sentiría tentado a codificar la porción de búsqueda del índice para que no colisione con los separadores que usa.

Para leer el índice, escanea a través del índice en el disco (es decir, no te molestes en cargarlo en la memoria) buscando el registro apropiado. Una vez encontrado, tendrá la compensación de bytes. Ahora crea un nuevo Stream sobre el archivo .xml y establece su posición para el offset de bytes: crea un nuevo lector y lee el documento desde ese punto.

+0

Sobre el índice, en mi pregunta escribí que quería hacer una. El problema fue que el archivo es tan grande que el programa colgó. Pero creo que lo intentaré con el método 'Read()'. Leer un bloque de datos de tamaño fijo puede ser más rápido que 'ReadLine()'. Vale la pena comprobarlo :) – Ventus

+0

La idea general de mi propuesta es que solo leerá una pequeña fracción del archivo de índice y de datos en cualquier momento. el xmlreader leerá un archivo de cualquier tamaño mientras usa una cantidad muy pequeña de memoria. ¿Dónde está colgado? – headsling

0

Viértalo en un índice de Solr y úselo para buscarlo.Puede ejecutar Solr como un motor de búsqueda independiente, y un simple script para recorrer el archivo y volcar cada artículo en el índice. A continuación, Solr le ofrece la búsqueda de texto completo sobre los campos que decidió indexar ...

0

La única forma en que podrá buscar rápidamente esto es almacenarlo en una base de datos, como otros han sugerido. Si una base de datos no es una opción, va a tomar mucho tiempo, no hay dudas al respecto. Lo que haría es crear una aplicación multiproceso. Cree hilos de trabajo que se leerán en los datos y quizás los pegue en una cola de cadenas. Tenga 5 hilos haciendo esto segmentado a través del archivo completo (de modo que un hilo comenzará desde el principio, el segundo hilo comenzará 1/5 del camino hacia el archivo, el tercer hilo comenzará 2/5 del camino hacia adentro, etc.) Mientras tanto, tenga otro hilo que lea la cola de cadenas y busque lo que sea que esté buscando. Luego haz que el hilo dequeue una vez hecho. Esto llevará un tiempo, pero no debería colapsar ni consumir toneladas de memoria.

Si observa que está consumiendo mucha memoria, establezca un límite en el número de elementos que la cola puede contener y haga que los hilos se suspendan hasta que el tamaño de la cola esté por debajo de este umbral.

+0

La lectura de Xml con subprocesos múltiples no va a ser trivial. Tal vez sea mejor mantener la trituración Xml de un solo subproceso y ver el multi-subprocesamiento del resto. –

+0

@chibacity No es tan difícil. Mire aquí: http://www.c-sharpcorner.com/UploadFile/jbailo/MultithreadedXmlDoc11212005065943AM/MultithreadedXmlDoc.aspx o aquí: http://www.c-sharpcorner.com/UploadFile/mmehta/LoadingXmlInTreeView11172005011544AM/LoadingXmlInTreeView.aspx – Icemanind

+1

No ofensa, pero no creo que hayas leído ese artículo correctamente en el contexto de la pregunta. –

1

puede almacenar el archivo en couchDB. i escribió una pitón-script para hacerlo:

import couchdb 
import datetime 
import time 
from lxml import etree 

couch = couchdb.Server() 
db = couch["wiki"] 

infile = '/Users/johndotnet/Downloads/plwiki-20100629-pages-articles.xml' 


context = etree.iterparse(source=infile, events=("end",), tag='{http://www.mediawiki.org/xml/export-0.4/}page') 


for event, elem in context: 
    #dump(elem) 
couchEle = {} 
for ele in elem.getchildren(): 
    if ele.tag == "{http://www.mediawiki.org/xml/export-0.4/}id": 
    couchEle['id'] = ele.text 
    if ele.tag == "{http://www.mediawiki.org/xml/export-0.4/}title": 
    couchEle['title'] = ele.text 
    if ele.tag == "{http://www.mediawiki.org/xml/export-0.4/}revision": 
    for subEle in ele.getchildren(): 
    if subEle.tag == "{http://www.mediawiki.org/xml/export-0.4/}text": 
    couchEle['text'] = subEle.text 


db[couchEle['title']] = couchEle 

Esto debe importar todo el artículo con id, título y texto couchdb.

ahora usted debe hacer una consulta como esta:

code = ''' 
    function(doc) { 
    if(doc.title.indexOf("Brzeg") > -1) { 
    emit(doc._id, doc); 
    } 

    } 
    ''' 
results = db.query(code) 

espero que ayude!

0

Sé esta pregunta // la respuesta es vieja. Pero recientemente resolví este problema y descubrí Personalmente que usaría JSON.Net (newtonking). Que es tan simple como deserializar los resultados del documento XML a objetos C#.

Ahora, mis documentos (resultados) tienen solo un par de MB de tamaño (con un promedio de 5MB en este momento) pero puedo ver que esto crece con el Índice Solr. Tal como está, obtengo resultados rápidos.

Una discusión sobre CodePlex con referencia al rendimiento.

Cuestiones relacionadas