日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > asp.net >内容正文

asp.net

用Java分割大型XML文件

發布時間:2023/12/3 asp.net 24 豆豆
生活随笔 收集整理的這篇文章主要介紹了 用Java分割大型XML文件 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

上周,我被要求用Java編寫一些東西,該東西能夠將單個30GB XML文件拆分為可配置文件大小的較小部分。 該文件的使用者將是一個中間件應用程序,該應用程序存在XML較大的問題。 在后臺,它使用某種DOM解析技術,使它在一段時間后耗盡內存。 由于它是基于供應商的中間件,因此我們無法自行糾正。 最好的選擇是創建一些預處理工具,該工具會先將大文件分成多個較小的塊,然后再由中間件處理。

XML文件帶有一個相應的W3C模式,該模式由強制性頭部分和緊隨其后嵌套有多個0 .. *數據元素的內容元素組成。 對于演示代碼,我以簡化形式重新創建了架構:
標頭的大小可以忽略。 單個數據元素的重復也很小,可以說少于50kB。 由于數據元素重復的次數,XML太大了。 要求是:

  • 分割后的XML的每一部分都應為語法有效的XML,并且每一部分還應針對原始模式進行驗證
  • 該工具應根據架構驗證XML,并報告所有驗證錯誤。 驗證不得阻塞,并且不可在輸出中跳過非驗證元素或屬性
  • 對于標頭,決定將其復制到每個新的輸出文件中,而不是將其復制到每個新的輸出文件中,并使用一些處理信息和一些默認值來重新生成該標頭

因此,使用諸如Unix Split之類的二進制拆分工具是不可能的。 在固定數量的字節之后,這將拆分,從而確保XML損壞。 我不太確定,但是諸如Split之類的工具也不了解編碼。 因此,在字節“ x”之后進行拆分不僅會導致在XML元素的中間進行拆分(例如),而且甚至會在字符編碼序列的中間進行拆分(例如,在使用經過UTF8編碼的Unicode時)。 顯然,我們需要更智能的東西。

XSLT作為核心技術也是行不通的。 乍一看,可能會很想嘗試:使用XSLT2.0,可以從單個輸入文件創建多個輸出文件。 甚至可以在轉換時驗證輸入文件。 但是,細節始終是魔鬼。 否則,在Java中進行簡單的操作(例如將驗證錯誤寫入單獨的文件或檢查當前輸出文件的大小)可能需要自定義Java代碼。 對于Xalan和Saxon來說,當然可以有這樣的擴展,但是Xalan不是XSLT2.0實現,因此只剩下Saxon。 最后但并非最不重要的一點是,XSLT1.0 / 2.0是非流式的,這意味著它們會將整個源文檔讀入內存,因此這顯然將XSLT排除在了可能性之外。

剩下的唯一選擇就是Java XML解析。 當然,在這種情況下,理想的選擇是StAX。 我不在這里進行SAX與StAX的比較,事實是StAX能夠針對架構的身份進行驗證(至少某些解析器可以)并且還可以編寫XML。 而且,與SAX相比,API的使用要容易得多,因為基于pull的API提供了對迭代文檔的更多控制,并且比SAX的推送方式更令人愉快。 好的,我們需要什么:

  • 能夠驗證XML的StAX實現
    • Oracle的JDK默認附帶SJSXP作為StAX實現,但是此驗證無效。
  • 最好具有某種對象/ XML映射技術,用于(重新)創建標頭,而不是手動擺弄元素并必須查找正確的數據類型/格式
    • 顯然是JAXB。

該代碼有點大,無法在此處整體顯示。 可以訪問源文件,XSD和測試XML
了這里 GitHub上。 它具有Maven pom文件,因此您應該能夠在選擇的IDE中將其導入。 JAXB綁定編譯器將自動編譯模式,并將生成的源放在類路徑上。

public void startSplitting() throws Exception {XMLStreamReader2 xmlStreamReader = ((XMLInputFactory2) XMLInputFactory.newInstance()).createXMLStreamReader(BigXmlTest.class.getResource("/BigXmlTest.xml"));PrintWriter validationResults = enableValidationHandling(xmlStreamReader);int fileNumber = 0;int dataRepetitions = 0;XMLStreamWriter xmlStreamWriter = openOutputFileAndWriteHeader(++fileNumber); // Prepare first file

第一行創建了StAX流讀取器,這意味著我們正在使用游標API。 迭代器API使用XMLEventReader類。 類名中還有一個奇怪的“ 2”,它表示Woodstox的StAX 2功能,其中之一可能是對驗證的支持。 從
在這里 :

StAX2 is an experimental API that is intended to extend basic StAX specifications in a way that allows implementations to experiment with features before they end up in the actual StAX specification (if they do). As such, it is intended to be freely implementable by all StAX implementations same way as StAX, but without going through a formal JCP process. Currently Woodstox is the only known implementation.

可以在“ enableValidationHandling”中看到
源文件(如果需要)。 我將重點介紹重要的部分。 首先,加載XML模式:

XMLValidationSchema xmlValidationSchema = xmlValidationSchemaFactory.createSchema(BigXmlTest.class.getResource("/BigXmlTest.xsd"));

用于將可能的驗證結果寫入輸出文件的回調;

public void reportProblem(XMLValidationProblem validationError) throws XMLValidationException {validationResults.write(validationError.getMessage()+ "Location:"+ ToStringBuilder.reflectionToString(validationError.getLocation(),ToStringStyle.SHORT_PREFIX_STYLE) + "\r\n");}

“ openOutputFileAndWriteHeader”將創建一個XMLStreamWriter(它又是游標API的一部分,迭代器API具有XMLEventWriter),我們可以將其輸出或原始XML文件的一部分。 它還將使用JAXB創建我們的標頭,并將其寫入輸出。 默認情況下,使用Schema編譯器(xjc)生成JAXB對象。

private XMLStreamWriter openOutputFileAndWriteHeader(int fileNumber) throws Exception {XMLOutputFactory xmlOutputFactory = XMLOutputFactory.newInstance();xmlOutputFactory.setProperty(XMLOutputFactory.IS_REPAIRING_NAMESPACES, true);XMLStreamWriter writer = xmlOutputFactory.createXMLStreamWriter(new FileOutputStream(new File(System.getProperty("java.io.tmpdir"), "BigXmlTest." + fileNumber + ".xml")));writer.setDefaultNamespace(DOCUMENT_NS);writer.writeStartDocument();writer.writeStartElement(DOCUMENT_NS, BIGXMLTEST_ROOT_ELEMENT);writer.writeDefaultNamespace(DOCUMENT_NS);HeaderType header = objectFactory.createHeaderType();header.setSomeHeaderElement("Something something darkside");marshaller.marshal(new JAXBElement<HeaderType>(new QName(DOCUMENT_NS, HEADER_ELEMENT, ""), HeaderType.class,HeaderType.class, header), writer);writer.writeStartElement(CONTENT_ELEMENT);return writer;}

在第3行,我們啟用“修復名稱空間”。 規格說明如下:

javax.xml.stream.isRepairingNamespaces: Function: Creates default prefixes and associates them with Namespace URIs. Type: Boolean Default Value: False Required: Yes

我從中了解到,處理默認名稱空間是必需的。 事實是,如果未啟用,則不會以任何方式編寫默認名稱空間。 在第6行,我們設置默認名稱空間。 設置它實際上不會將其寫入流。 因此,需要writeDefaultNamespace(第9行),但這只能在寫入start元素之后才能完成。 因此,您必須在編寫任何元素之前定義默認名稱空間,但是您需要在編寫第一個元素之后編寫默認名稱空間。 理由是StAX需要知道它是否必須為要寫yes或no的根元素生成前綴。

在第8行,我們編寫了root元素。 指示此元素所屬的名稱空間很重要。 如果您未指定前綴,則會為您生成一個前綴,或者,在本例中,將不會生成任何前綴,因為StAX知道我們已經設置了默認名稱空間。 如果您要刪除第6行的默認名稱空間指示,則將為根元素添加前綴(帶有隨機前綴),例如:<wstxns1:BigXmlTest xmlns:wstxns1 =“ http:// www ...接下來,我們編寫默認名稱空間,它將被寫入先前開始的元素(順便說一句,為了對此順序有更深入的了解,請參閱這篇不錯的文章 )在第11-14行中,我們使用JAXB生成的模型創建標頭,然后讓我們的JAXB marshaller直接將其寫到我們的StAX輸出流。

重要提示: JAXB編組器以片段模式初始化,否則它將開始添加XML聲明,這對于獨立文檔是必需的,當然,在現有文檔中間是不允許的:

marshaller.setProperty(Marshaller.JAXB_FRAGMENT, true);

附帶說明一下:在此示例中,JAXB集成并不是真正有用,它會增加復雜性并占用更多代碼行,然后僅使用XMLStreamWriter添加元素即可。 但是,如果您有一個更復雜的結構需要創建并合并到文檔中,則具有自動對象映射非常方便。

因此,我們有啟用驗證的閱讀器。 從我們開始遍歷源文檔的那一刻起,它將同時驗證和解析。 然后,我們的writer已經編寫了一個初始化的文檔和標頭,并準備接受更多數據。 最后,我們必須遍歷源代碼并將每個部分寫入輸出文件。 如果輸出文件變大,我們將換一個新文件:

while (xmlStreamReader.hasNext()) {xmlStreamReader.next();if (xmlStreamReader.getEventType() == XMLEvent.START_ELEMENT&& xmlStreamReader.getLocalName().equals(DATA_ELEMENT)) {if (dataRepetitions != 0 && dataRepetitions % 2 == 0) { // %2 = just for testing: replace this by for example checking the actual size of the current output filexmlStreamWriter.close(); // Also closes any open Element(s) and the documentxmlStreamWriter = openOutputFileAndWriteHeader(++fileNumber); // Continue with next filedataRepetitions = 0;}// Transform the input stream at current position to the output streamtransformer.transform(new StAXSource(xmlStreamReader), new StAXResult(new FragmentXMLStreamWriterWrapper(new AvoidDefaultNsPrefixStreamWriterWrapper(xmlStreamWriter, DOCUMENT_NS))));dataRepetitions++;} }

重要的一點是,我們不斷迭代源文檔,并檢查是否存在Data元素的開頭。 如果是這樣,我們將相應的元素及其同級元素流式傳輸到輸出。 在我們的簡單示例中,我們沒有兄弟姐妹,只有文本值。 但是,如果結構更復雜,則所有基礎節點將自動復制到輸出中。 每隔兩個數據元素,我們將循環輸出文件。 關閉編寫器,并初始化一個新的編寫器(當然,可以通過檢查文件大小而不是%2來代替此檢查)。 如果作家是關閉的,它將自動處理關閉打開的元素并最終關閉文檔本身,而無需您自己這樣做。 作為將節點從輸入流傳輸到輸出的機制,需要注意以下幾點:

  • 由于驗證,我們不得不使用游標API,因此必須使用XSLT將節點及其兄弟節點傳輸到輸出。 XSLT具有一些默認模板,如果您未專門指定XSL,則將調用這些模板。 在這種情況下,它將輸入轉換為給定的輸出。
  • 需要一個自定義的FragmentXMLStreamWriterWrapper ,我在JavaDoc中對此進行了記錄。 再次將這個包裝器包裝在PreventDefaultNsPrefixStreamWriterWrapper中 。 最后一個原因是默認的XSLT模板無法識別源文檔中的默認名稱空間。 一分鐘內提供更多信息(或搜索避免使用DefaultDefaultNsPrefixStreamWriterWrapper)。
  • 您使用的轉換器必須是Oracle JDK的內部版本。 在初始化轉換器的地方,我們直接引用內部TransformerFactory的實例: com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl然后創建正確的轉換器: Transformer = new TransformerFactoryImpl()。newTransformer(); 通常,您將使用TransformerFactory.newInstance()并使用classpath上可用的轉換器。 但是,解析器和轉換器可以通過提供META-INF /服務來安裝自己。 如果另一個轉換器(例如默認的Xalan,而不是重新打包的JDK版本)將在類路徑上,則轉換將失敗。 原因是顯然只有JDK內部版本才可以從StAXSource轉換為StAXResult
  • 轉換器實際上將讓我們的XMLStreamReader在迭代過程中繼續。 因此,在處理完一個數據元素之后,理論上閱讀器的光標將在下一個數據元素處就緒。 從理論上講,如果格式化XML,則下一個事件類型可能是空格。 因此,在下一個Data元素實際準備就緒之前,它仍可能需要在while循環中對xmlStreamReader.next()進行一些迭代。

結果是我們有3個輸出文件,每個輸出文件都符合原始架構,每個文件都有2個數據元素:

要將大約30GB的XML(我在說我的原始工作分配XML具有更復雜的結構,而不是此處使用的演示XSD)拆分為大約500MB的部分,并花費了大約25分鐘的時間。 為了測試內存使用率,我特意將Xmx設置為32MB。 從圖中可以看出,內存消耗非常低,并且沒有GV開銷: 生活是美好的,但并非完全如此。 在那兒,我發現有些尷尬的事情需要小心。

在我的實際場景中,輸入XML沒有與之關聯的名稱空間,我很確定它永遠不會。 這就是我堅持使用此解決方案的原因。 在演示中,這里只有一個名稱空間,并且已經開始使設置更加脆弱。 問題不在于StAX:使用StAX處理名稱空間非常簡單。 您可以決定具有一個與該模式的目標名稱空間相對應的默認名稱空間(假設您的模式為elementFormDefault = qualified),并可以為該模式中導入的其他名稱空間聲明一些帶前綴的名稱空間。 當XSLT開始干擾輸出流時,問題就開始出現(您可能已經注意到了)。 顯然,它不會檢查已經定義了哪些名稱空間或發生其他事情。

結果是,它們通過使用其他前綴重新定義現有名稱空間或重置默認名稱空間和其他不需要的內容,使文檔嚴重混亂。 如果您需要比默認模板更多的名稱空間操作,則可能需要XSL。 如果輸入文檔使用默認名稱空間,則XSLT也會觸發異常。 它將嘗試注冊名稱為“ xmlns”的前綴。 不允許這樣做,因為xmlns保留用于指示默認名稱空間,不能用作前綴。 我為此測試申請的解決方案是忽略任何前綴“ xmlns”,并忽略與xmlns前綴組合的目標名稱空間的添加(這就是為什么要使用避免DefaultNsPrefixStreamWriterWrapper)。 前綴和名稱空間都需要在PreventDefaultNsPrefixStreamWriterWrapper中進行匹配,因為如果您要使用的輸入文檔中沒有默認名稱空間,而是帶有前綴(例如<bigxml:BigXmlTest xmlns:bigxml =“ http://…。”> <bigxml:Header …。),那么您就不能忽略添加名稱空間(該組合將成為帶有“ bigxml”前綴的目標名稱空間),因為這只會產生數據元素的前綴而沒有名稱空間綁定,例如:

<?xml version='1.0' encoding='UTF-8'?> <BigXmlTest xmlns="http://www.error.be/bigxmltest"><Header><SomeHeaderElement>Something something darkside</SomeHeaderElement></Header><Content><bigxml:Data>Data1</bigxml:Data><bigxml:Data>Data2</bigxml:Data></Content> </BigXmlTest>

請記住,XML的生產者可以自由選擇(還是在elementFormDefault =合格的情況下)選擇使用默認命名空間還是為每個元素添加前綴。 該代碼應該透明地能夠處理這兩種情況。 為方便起見,請使用PreventDefaultNsPrefixStreamWriterWrapper代碼:

public class AvoidDefaultNsPrefixStreamWriterWrapper extends XMLStreamWriterAdapter { ...@Overridepublic void writeNamespace(String prefix, String namespaceURI) throws XMLStreamException {if (defaultNs.equals(namespaceURI) && "xmlns".equals(prefix)) {return;}super.writeNamespace(prefix, namespaceURI);}@Overridepublic void setPrefix(String prefix, String uri) throws XMLStreamException {if (prefix.equals("xmlns")) {return;}super.setPrefix(prefix, uri);}

最后,我還寫了一個版本(點擊
此處完全適用于GitHub),但這次使用的是StAX迭代器API。 您會注意到,不再需要繁瑣的XSLT來流傳輸到輸出。 只需將每個感興趣的事件添加到輸出中即可。 通過首先使用游標API驗證輸入,然后使用Iterator API解析輸入,可以解決缺少驗證的問題。 這將花費更長的時間,但是在大多數情況下仍然可以接受。 最重要的是:

while (xmlEventReader.hasNext()) {XMLEvent event = xmlEventReader.nextEvent();if (event.isStartElement() && event.asStartElement().getName().getLocalPart().equals(CONTENT_ELEMENT)) {event = xmlEventReader.nextEvent();while (!(event.isEndElement() && event.asEndElement().getName().getLocalPart().equals(CONTENT_ELEMENT))) {if (dataRepetitions != 0 && event.isStartElement()&& event.asStartElement().getName().getLocalPart().equals(DATA_ELEMENT)&& dataRepetitions % 2 == 0) { // %2 = just for testing: replace this by for example checking the actual size of the current// output filexmlEventWriter.close(); // Also closes any open Element(s) and the documentxmlEventWriter = openOutputFileAndWriteHeader(++fileNumber); // Continue with next filedataRepetitions = 0;}// Write the current event to outputxmlEventWriter.add(event);event = xmlEventReader.nextEvent();if (event.isEndElement() && event.asEndElement().getName().getLocalPart().equals(DATA_ELEMENT)) {dataRepetitions++;}}}}

在第2行,您將看到返回XMLEvent,其中包含有關當前節點的所有信息。 在第4行上,您看到使用此表單檢查元素類型更容易(與其與常量進行比較,還可以使用對象模型)。 在第19行,要將元素從輸入復制到輸出,我們只需將Event添加到XMLEventWriter。

參考:來自Koen Serneels –技術博客博客的JCG合作伙伴 Koen Serneels 分離Java中的大型XML文件 。

翻譯自: https://www.javacodegeeks.com/2013/08/splitting-large-xml-files-in-java.html

總結

以上是生活随笔為你收集整理的用Java分割大型XML文件的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。