Если у вас очень большой исходный XML-файл (например, ваш файл объемом 0,5 ГБ) и вы хотите извлечь из него информацию, возможно, создав новый XML, вы можете подумать об использовании анализатора на основе событий, который не требует загрузки всего XML в памяти. Простейшей из этих реализаций является синтаксический анализатор SAX, который требует, чтобы вы записывали прослушиватель событий, который будет захватывать такие события, как начало документа, начало элемента, конец элемента и т. Д., Где вы можете проверять данные, которые вы читаете (имя элемент, атрибуты и т. д.) и решить, будете ли вы игнорировать его или делать что-то с данными.
Поиск учебника SAX с использованием JAXP, и вы должны найти несколько примеров. Другая стратегия, которую вы можете рассмотреть, в зависимости от того, что вы хотите сделать, - это StAX.
Вот простой пример использования SAX для чтения данных из файла XML и извлечения некоторой информации на основе критериев поиска. Это очень простой пример, который я использую для обучения SAX. Я думаю, это может помочь вам понять, как это работает. Критерии поиска жестко связаны и состоят из имен режиссеров фильмов для поиска в гигантском XML с выбором фильма, созданным из данных IMDB.
XML Source пример ("source.xml" ~ 300MB файл)
<Movies>
...
<Movie>
<Imdb>tt1527186</Imdb>
<Title>Melancholia</Title>
<Director>Lars von Trier</Director>
<Year>2011</Year>
<Duration>136</Duration>
</Movie>
<Movie>
<Imdb>tt0060390</Imdb>
<Title>Fahrenheit 451</Title>
<Director>François Truffaut</Director>
<Year>1966</Year>
<Duration>112</Duration>
</Movie>
<Movie>
<Imdb>tt0062622</Imdb>
<Title>2001: A Space Odyssey</Title>
<Director>Stanley Kubrick</Director>
<Year>1968</Year>
<Duration>160</Duration>
</Movie>
...
</Movies>
Вот пример обработчика событий. Он выбирает элементы Movie
, сопоставляя строки. Я продлил DefaultHandler
и реализовал startElement()
(называется, когда найден тэг открытия), characters()
(вызывается, когда считывается блок символов), endElement()
(называется, когда найден конечный тег) и endDocument()
(один раз, когда документ завершен). Поскольку считываемые данные не сохраняются в памяти, вам необходимо сохранить интересующие вас данные. Я использовал некоторые булевы флаги и переменные экземпляра, чтобы сохранить текущую метку, текущие данные и т.д.
class ExtractMovieSaxHandler extends DefaultHandler {
// These are some parameters for the search which will select
// the subtrees (they will receive data when we set up the parser)
private String tagToMatch;
private String tagContents; // OR match
private boolean strict = false; // if strict matches will be exact
/**
* Sets criteria to select and copy Movie elements from source XML.
*
* @param tagToMatch Must contain text only
* @param tagContents Text contents of the tag
* @param strict If true, match must be exact
*/
public void setSearchCriteria(String tagToMatch, String tagContents, boolean strict) {
this.tagToMatch = tagToMatch;
this.tagContents = tagContents;
this.strict = strict;
}
// These are the temporary values we store as we parse the file
private String currentElement;
private StringBuilder contents = null; // if not null we are in Movie tag
private String currentData;
List<String> result = new ArrayList<String>(); // store resulting nodes here
private boolean skip = false;
...
Эти методы являются реализация ContentHandler
. Первый обнаруживает, что элемент найден (начальный тег). Мы сохраняем имя тега (дочерний Movie
) в переменной, так как это может быть один мы используем в поиске:
...
@Override
public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException {
// Store the current element that started now
currentElement = qName;
// If this is a Movie tag, save the contents because we might need it
if (qName.equals("Movie")) {
contents = new StringBuilder();
}
}
...
Это один вызывается каждый раз, когда блок символов называется. Мы проверяем, встречаются ли эти символы внутри интересующего нас элемента. Если это так, мы сопоставляем содержимое и сохраняем его, если оно соответствует.
...
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
// if we discovered that we don't need this data, we skip it
if (skip || currentElement == null) {
return;
}
// If we are inside the tag we want to search, save the contents
currentData = new String(ch, start, length);
if (currentElement.equals(tagToMatch)) {
boolean discard = true;
if (strict) {
if (currentData.equals(tagContents)) { // exact match
discard = false;
}
} else {
if (currentData.toLowerCase().indexOf(tagContents.toLowerCase()) >= 0) { // matches occurrence of substring
discard = false;
}
}
if (discard) {
skip = true;
}
}
}
...
Это называется, когда найден конечный тег. Мы можем теперь добавить его к документу, который мы строим в памяти, если хотим.
...
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
// Rebuild the XML if it's a node we didn't skip
if (qName.equals("Movie")) {
if (!skip) {
result.add(contents.insert(0, "<Movie>").append("</Movie>").toString());
}
// reset the variables so we can check the next node
contents = null;
skip = false;
} else if (contents != null && !skip) {
contents.append("<").append(qName).append(">")
.append(currentData)
.append("</").append(qName).append(">");
}
currentElement = null;
}
...
И наконец, этот вызов вызывается, когда документ заканчивается. Я также использовал его для печати результата в конце.
...
@Override
public void endDocument() throws SAXException {
StringBuilder resultFile = new StringBuilder();
resultFile.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
resultFile.append("<Movies>");
for (String childNode : result) {
resultFile.append(childNode.toString());
}
resultFile.append("</Movies>");
System.out.println("=== Resulting XML containing Movies where " + tagToMatch + " is one of " + tagContents + " ===");
System.out.println(resultFile.toString());
}
}
Здесь представлено небольшое приложение Java, которое загружает этот файл и использует обработчик событий для извлечения данных.
public class SAXReaderExample {
public static final String PATH = "src/main/resources"; // this is where I put the XML file
public static void main(String[] args) throws ParserConfigurationException, SAXException, IOException {
// Obtain XML Reader
SAXParserFactory spf = SAXParserFactory.newInstance();
SAXParser sp = spf.newSAXParser();
XMLReader reader = sp.getXMLReader();
// Instantiate SAX handler
ExtractMovieSaxHandler handler = new ExtractMovieSaxHandler();
// set search criteria
handler.setSearchCriteria("Director", "Kubrick", false);
// Register handler with XML reader
reader.setContentHandler(handler);
// Parse the XML
reader.parse(new InputSource(new FileInputStream(new File(PATH, "source.xml"))));
}
}
Вот результирующий файл, после обработки:
<?xml version="1.0" encoding="UTF-8"?>
<Movies>
<Movie>
<Imdb>tt0062622</Imdb>
<Title>2001: A Space Odyssey</Title>
<Director>Stanley Kubrick</Director>
<Year>1968</Year>
<Duration>160</Duration>
</Movie>
<Movie>
<Imdb>tt0066921</Imdb>
<Title>A Clockwork Orange</Title>
<Director>Stanley Kubrick</Director>
<Year>1972</Year>
<Duration>136</Duration>
</Movie>
<Movie>
<Imdb>tt0081505</Imdb>
<Title>The Shining</Title>
<Director>Stanley Kubrick</Director>
<Year>1980</Year>
<Duration>144</Duration>
</Movie>
...
</Movies>
Ваш сценарий может быть различным, но этот пример показывает общее решение, которое вы, вероятно, может адаптироваться к вашей проблеме. Более подробную информацию вы можете найти в руководствах по SAX и JAXP.
Рассмотрите возможность использования XSLT, который позволяет вам писать шаблон (в XML), который действует как рецепт для извлечения элементов и/или атрибутов, которые вы хотите, а затем записывать их как новый документ (в случае необходимости XML). Я использовал [Saxon] (http://saxon.sourceforge.net/) для этого в прошлом (используя скрипт командной строки, а не приложение Java). – Bobulous
Возможно, вы захотите прочитать файл последовательно, сохранив только те элементы, которые вам действительно нужны. С помощью этой стратегии вам не нужно будет выделять память для хранения и управления вашим 0,5 ГБ файлом. Вы можете сделать это с помощью анализатора SAX. Вы также можете использовать Stax в Java. – helderdarocha