comandos - Cómo analizar grandes archivos XML(50 GB) en Java
comandos elasticsearch (2)
Actualmente estoy intentando usar un SAX Parser, pero aproximadamente 3/4 a través del archivo, se congela completamente, he intentado asignar más memoria, etc. pero no obteniendo ninguna mejora.
Hay alguna manera de acelerar esto? ¿Un método mejor?
Lo despojé por completo, así que ahora tengo el siguiente código y cuando lo ejecuto en la línea de comandos, todavía no funciona tan rápido como me gustaría.
Al ejecutarlo con "java -Xms-4096m -Xmx8192m -jar reader.jar" obtengo un límite de sobrecarga del GC excedido alrededor del artículo 700000
Principal:
public class Read {
public static void main(String[] args) {
pages = XMLManager.getPages();
}
}
XMLManager
public class XMLManager {
public static ArrayList<Page> getPages() {
ArrayList<Page> pages = null;
SAXParserFactory factory = SAXParserFactory.newInstance();
try {
SAXParser parser = factory.newSAXParser();
File file = new File("..//enwiki-20140811-pages-articles.xml");
PageHandler pageHandler = new PageHandler();
parser.parse(file, pageHandler);
pages = pageHandler.getPages();
} catch (ParserConfigurationException e) {
e.printStackTrace();
} catch (SAXException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return pages;
}
}
PageHandler
public class PageHandler extends DefaultHandler{
private ArrayList<Page> pages = new ArrayList<>();
private Page page;
private StringBuilder stringBuilder;
private boolean idSet = false;
public PageHandler(){
super();
}
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
stringBuilder = new StringBuilder();
if (qName.equals("page")){
page = new Page();
idSet = false;
} else if (qName.equals("redirect")){
if (page != null){
page.setRedirecting(true);
}
}
}
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
if (page != null && !page.isRedirecting()){
if (qName.equals("title")){
page.setTitle(stringBuilder.toString());
} else if (qName.equals("id")){
if (!idSet){
page.setId(Integer.parseInt(stringBuilder.toString()));
idSet = true;
}
} else if (qName.equals("text")){
String articleText = stringBuilder.toString();
articleText = articleText.replaceAll("(?s)<ref(.+?)</ref>", " "); //remove references
articleText = articleText.replaceAll("(?s)//{//{(.+?)//}//}", " "); //remove links underneath headings
articleText = articleText.replaceAll("(?s)==See also==.+", " "); //remove everything after see also
articleText = articleText.replaceAll("//|", " "); //Separate multiple links
articleText = articleText.replaceAll("//n", " "); //remove new lines
articleText = articleText.replaceAll("[^a-zA-Z0-9- //s]", " "); //remove all non alphanumeric except dashes and spaces
articleText = articleText.trim().replaceAll(" +", " "); //convert all multiple spaces to 1 space
Pattern pattern = Pattern.compile("([//S]+//s*){1,75}"); //get first 75 words of text
Matcher matcher = pattern.matcher(articleText);
matcher.find();
try {
page.setSummaryText(matcher.group());
} catch (IllegalStateException se){
page.setSummaryText("None");
}
page.setText(articleText);
} else if (qName.equals("page")){
pages.add(page);
page = null;
}
} else {
page = null;
}
}
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
stringBuilder.append(ch,start, length);
}
public ArrayList<Page> getPages() {
return pages;
}
}
El enfoque de Don Roby recuerda algo al enfoque que seguí al crear un generador de código diseñado para resolver este problema en particular (una versión anterior se concibió en 2008). Básicamente, cada complexType
tiene su equivalente de Java POJO
y los controladores para el tipo particular se activan cuando el contexto cambia a ese elemento. Utilicé este enfoque para SEPA, transacciones bancarias y, por ejemplo, discogs (30 GB). Puede especificar qué elementos desea procesar en tiempo de ejecución, utilizando de forma declarativa un archivo de propiedades.
XML2J utiliza la asignación de complexTypes
a Java POJOs por un lado, pero le permite especificar los eventos que desea escuchar. P.ej
account/@process = true
account/accounts/@process = true
account/accounts/@detach = true
La esencia está en la tercera línea. La separación se asegura de que las cuentas individuales no se agreguen a la lista de cuentas. Así que no se desbordará.
class AccountType {
private List<AccountType> accounts = new ArrayList<>();
public void addAccount(AccountType tAccount) {
accounts.add(tAccount);
}
// etc.
};
En su código, debe implementar el método de proceso (de manera predeterminada, el generador de código genera un método vacío:
class AccountsProcessor implements MessageProcessor {
static private Logger logger = LoggerFactory.getLogger(AccountsProcessor.class);
// assuming Spring data persistency here
final String path = new ClassPathResource("spring-config.xml").getPath();
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(path);
AccountsTypeRepo repo = context.getBean(AccountsTypeRepo.class);
@Override
public void process(XMLEvent evt, ComplexDataType data)
throws ProcessorException {
if (evt == XMLEvent.END) {
if( data instanceof AccountType) {
process((AccountType)data);
}
}
}
private void process(AccountType data) {
if (logger.isInfoEnabled()) {
// do some logging
}
repo.save(data);
}
}
Tenga en cuenta que XMLEvent.END
marca la etiqueta de cierre de un elemento. Entonces, cuando lo estás procesando, está completo. Si tiene que relacionarlo (usando un FK) con su objeto principal en la base de datos, puede procesar XMLEvent.BEGIN
para el principal, crear un marcador de posición en la base de datos y usar su clave para almacenar con cada uno de sus secundarios. En el último XMLEvent.END
luego actualizaría el padre.
Tenga en cuenta que el generador de código genera todo lo que necesita. Solo tienes que implementar ese método y por supuesto el código de pegamento DB.
Hay muestras para empezar. El generador de código incluso genera sus archivos POM, por lo que puede generar su proyecto inmediatamente después de la generación.
El método de proceso predeterminado es así:
@Override
public void process(XMLEvent evt, ComplexDataType data)
throws ProcessorException {
/*
* TODO Auto-generated method stub implement your own handling here.
* Use the runtime configuration file to determine which events are to be sent to the processor.
*/
if (evt == XMLEvent.END) {
data.print( ConsoleWriter.out );
}
}
Descargas:
- https://github.com/lolkedijkstra/xml2j-core
- https://github.com/lolkedijkstra/xml2j-gen
- https://sourceforge.net/projects/xml2j/
Primero, mvn clean install
el núcleo (tiene que estar en el repositorio local de maven), luego el generador. Y no olvide configurar la variable de entorno XML2J_HOME
según las instrucciones en el manual de usuario.
Es probable que su código de análisis funcione bien, pero el volumen de datos que está cargando es probablemente demasiado grande para almacenar en la memoria en esa lista de ArrayList
.
Necesita algún tipo de canalización para pasar los datos a su destino real sin tener que almacenarlos todos en la memoria a la vez.
Lo que algunas veces he hecho para este tipo de situación es similar a lo siguiente.
Crea una interfaz para procesar un solo elemento:
public interface PageProcessor {
void process(Page page);
}
Proporcione una implementación de esto al PageHandler
través de un constructor:
public class Read {
public static void main(String[] args) {
XMLManager.load(new PageProcessor() {
@Override
public void process(Page page) {
// Obviously you want to do something other than just printing,
// but I don''t know what that is...
System.out.println(page);
}
}) ;
}
}
public class XMLManager {
public static void load(PageProcessor processor) {
SAXParserFactory factory = SAXParserFactory.newInstance();
try {
SAXParser parser = factory.newSAXParser();
File file = new File("pages-articles.xml");
PageHandler pageHandler = new PageHandler(processor);
parser.parse(file, pageHandler);
} catch (ParserConfigurationException e) {
e.printStackTrace();
} catch (SAXException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
Envíe datos a este procesador en lugar de ponerlos en la lista:
public class PageHandler extends DefaultHandler {
private final PageProcessor processor;
private Page page;
private StringBuilder stringBuilder;
private boolean idSet = false;
public PageHandler(PageProcessor processor) {
this.processor = processor;
}
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
//Unchanged from your implementation
}
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
//Unchanged from your implementation
}
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
// Elide code not needing change
} else if (qName.equals("page")){
processor.process(page);
page = null;
}
} else {
page = null;
}
}
}
Por supuesto, puede hacer que su interfaz maneje trozos de múltiples registros en lugar de solo uno, y PageHandler
el PageHandler
recopile las páginas localmente en una lista más pequeña y periódicamente envíe la lista para su procesamiento y borre la lista.
O (quizás mejor) podría implementar la interfaz de PageProcessor
como se define aquí y construir la lógica allí que almacena en búfer los datos y los envía para su posterior manejo en trozos.