You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.16 14:32:54