SAX Parser校验XML时Catalog Resolver重复解析的性能优化咨询
解决方案:复用CatalogResolver实现单次Catalog解析
问题根源
你的代码中每次解析XML都会新建MyHandler实例,而每个MyHandler都在内部创建新的CatalogResolver,这就导致每处理一个XML文件都会重新解析Catalog,这正是性能瓶颈的核心原因。
优化方案
将CatalogResolver全局实例化一次,让所有XML校验复用同一个Resolver实例,避免重复解析Catalog。具体修改如下:
修改后的代码
private List<ValidationResult> validateWithDTDUsingCR(final List<Path> validateFileList) throws IOException { LOGGER.logEvent("validateWithDTDUsingDOM", "File Parsing started : "); long start = System.currentTimeMillis(); final List<ValidationResult> response = new ArrayList<>(); try { final SAXParserFactory factory = SAXParserFactory.newInstance(); factory.setNamespaceAware(true); factory.setValidating(true); final SAXParser parser = factory.newSAXParser(); parser.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, ""); parser.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, ""); // 全局实例化一次CatalogResolver,所有校验复用 final CatalogResolver sharedCatalogResolver = new CatalogResolver(); // 复用同一个Handler实例,传入共享的Resolver final MyHandler sharedHandler = new MyHandler(sharedCatalogResolver); for (Path targetFolderPath : validateFileList) { final List<String> exceptions = new LinkedList<>(); final File localXMLFile = new File(targetFolderPath.toString()); try { // 重置Handler中的异常记录,避免上一次校验数据干扰 sharedHandler.resetExceptions(); parser.parse(new FileInputStream(localXMLFile), sharedHandler); // 将本次校验的异常信息加入结果 exceptions.addAll(sharedHandler.getCurrentExceptions()); addResponse(response, localXMLFile, exceptions, null); } catch (SAXException | IOException e) { addResponse(response, localXMLFile, exceptions, e); } catch (Exception e) { addResponse(response, localXMLFile, exceptions, e); } } } catch (ParserConfigurationException | SAXException exception) { LOGGER.logEvent("validateWithDTDUsingDOM", "ERROR Occurred: " + exception); } long stop = System.currentTimeMillis(); System.out.println("validateWithDTDUsingDOM Elapsed: " + (stop - start) + " ms"); return response; } final class MyHandler extends DefaultHandler { private final CatalogResolver catalogResolver; private List<String> currentExceptions; // 通过构造函数传入共享的CatalogResolver public MyHandler(CatalogResolver catalogResolver) { this.catalogResolver = catalogResolver; this.currentExceptions = new LinkedList<>(); } // 重置异常列表,复用Handler时清空上一次记录 public void resetExceptions() { currentExceptions.clear(); } public List<String> getCurrentExceptions() { return currentExceptions; } @Override public InputSource resolveEntity(String publicId, String systemId) throws IOException, SAXException { return catalogResolver.resolveEntity(publicId, systemId); } @Override public void warning(SAXParseException e) throws SAXException { currentExceptions.add("WARNING: " + e.getMessage()); } @Override public void error(SAXParseException e) throws SAXException { currentExceptions.add("ERROR: " + e.getMessage()); } }
关键优化点
- 复用CatalogResolver:在循环外仅实例化一次
CatalogResolver,所有XML校验共享该实例,确保Catalog只被解析一次。 - 复用Handler实例:避免每次循环新建
MyHandler,减少对象创建开销,通过重置方法清空上一次校验的异常记录,保证校验独立性。 - 性能额外优化:移除了
resolveEntity和错误方法中的System.out.println,减少IO输出带来的性能损耗。
线程安全说明
如果是单线程执行校验,上述修改完全可行;如果是多线程场景,需为每个线程单独创建CatalogResolver和MyHandler实例,但仍保证每个线程内仅解析一次Catalog,避免线程安全问题。
内容的提问来源于stack exchange,提问作者Kumaran
相关产品推荐
相关产品推荐

