Java는 큰 xml 파일을 구문 분석합니다.

기본 소개

Java를 사용하여 xml 파일을 구문 분석하는 구현 방법과 장단점은 여전히 ​​고전적인 인터뷰 질문이므로 경험하지 않았거나 인식하지 못하는 경우 여전히 오래 전입니다. 일상 작업에서 일반적인 구성 파일 유형에는 xml, yml, 속성, ini, json 및 기타 형식이 포함됩니다.일부 일반적이고 간단한 구성은 더 이상 xml의 첫 번째 선택이 아닐 수 있지만 xml 형식은 결국 포기되지 않습니다.후 모두 다른 형식이 가질 수 없는 적합한 애플리케이션 시나리오와 장점이 많이 있습니다.

xml 형식 파일의 분석은 JDK6에 추가된 dom, jdom, jom4j, sax 및 Jaxb 구성 요소가 메모리에서 일반적으로 사용됩니다. Control, 지난 몇 년 동안 Jaxb는 xml 파일 분석에 사용되어 왔으며 실제로 가장 편리하고 사용하기 쉬운데 작년에 크기가 50M 이상인 xml 파일을 만났습니다. 파일이 약간 큰 경우 상황에서 가장 먼저 떠오르는 것은 한 번에 파일을 읽은 다음 xml 분석을 사용하는 것이 확실히 불가능하다는 것입니다. 즉, 위에서 언급한 모든 구성 요소는 직접 사용할 수 있습니다. 저도 동료의 추천으로 Guava의 Files 도구 클래스인 Files를 사용합니다. xml 콘텐츠 세그먼트를 구문 분석하고 마지막으로 모든 콘텐츠 세그먼트를 반복하여 일회성 읽기를 방지합니다. 파일이 서버에 메모리 압력을 생성합니다. 따라서 초점은 Guava의 파일 도구 클래스를 사용하여 캐시된 스트림의 형태로 파일 콘텐츠를 한 줄씩 읽는 것입니다. 유사한 도구 클래스인 Apache Commons IO의 FileUtils도 파일을 한 줄씩 읽는 readLines 구현을 제공합니다. 맨 아래 계층은 IOUtils를 사용합니다. 최종 구현은 BufferedReader를 사용하여 파일을 한 번에 읽고 파일 내용을 한 줄씩 읽는 것입니다. List<String> , 이 방법은 너무 큰 파일에는 적합하지 않습니다. 그렇지 않으면 메모리 오버플로가 발생하므로 더 작은 파일을 사용하는 것이 좋습니다. 이 글에서 추천하는 Guava Files도 readLines 함수를 가지고 있습니다.(상위 버전은 만료된 것으로 표시되어 있습니다.) asCharSource 함수를 사용하는 것을 권장합니다. 그것의 기본 구현은 LineProcessor(라인 프로세서)를 지원합니다. 우리는 생산과 소비를 동시에 할 수 있습니다. 적용 시 시간 또는 일괄 소비 후 Batch를 생성함과 동시에 한 라인을 읽고 소비를 기다린 후 다음 라인을 읽으며 읽은 데이터는 큐에 저장됩니다.

이 예제는 두 부분으로 나뉩니다. 1. 코드를 사용하여 150M xml 파일을 생성합니다. 2. 특수 라인의 시작 태그와 종료 태그에 따라 섹션별로 파일을 읽고 읽은 내용을 콘솔에 인쇄합니다(콘솔 인쇄는 데이터의 최종 처리로 시뮬레이션됨). 예제 1 파일의 경우 다음 그림과 같이 파일 구조의 예를 살펴보십시오.

(예제 파일 형식, 실제 파일은 1개의 헤더 라인과 200만, 2개의 데이터로 데모)

참조 코드

데이터 소비자 정의

package cn.chendd.xml;

import java.util.List;

/**
 * 按行读取文件内容的批量处理
 *
 * @author chendd
 * @date 2023/2/18 8:51
 */
@FunctionalInterface
public interface RowBatchListProcessor {

    /**
     * 批量处理数据
     * @param rows 文本数据
     */
    void execute(List<String> rows);

}

행 단위로 일괄 읽기

package cn.chendd.xml;

import com.google.common.io.LineProcessor;

import java.util.List;

/**
 * 行解析实现
 *
 * @author chendd
 * @date 2023/2/18 8:41
 */
public class RowLineProcessor implements LineProcessor<String> {

    /**
     * xml文件内容的行节点个数
     */
    private static final int BATCH_SIZE = 200;

    private List<String> rows;
    private String beginMarker;
    private String endMarker;
    private RowBatchListProcessor processor;

    /**
     * 构造函数
     * @param rows 文件行数据
     * @param beginMarker 开始行标记
     * @param endMarker 结束行标记
     * @param processor 逻辑处理类
     */
    public RowLineProcessor(List<String> rows , String beginMarker , String endMarker , RowBatchListProcessor processor) {
        this.rows = rows;
        this.beginMarker = beginMarker;
        this.endMarker = endMarker;
        this.processor = processor;
    }

    /**
     * 单次获取的内容
     */
    private StringBuilder textBuilder = new StringBuilder();
    /**
     * 是否开始读取文件
     */
    private boolean begin = false;

    @Override
    public boolean processLine(String line) {
        if (line.endsWith(beginMarker)) {
            begin = true;
        }
        if (line.endsWith(endMarker)) {
            begin = false;
            textBuilder.append(line);
            rows.add(textBuilder.toString());
            textBuilder.setLength(0);
        } else if (begin) {
            textBuilder.append(line);
        }
        if (rows.size() > 0 && rows.size() % BATCH_SIZE == 0) {
            processor.execute(rows);
            rows.clear();
        }
        return true;
    }

    @Override
    public String getResult() {
        if (rows.isEmpty()) {
            return null;
        }
        this.processor.execute(rows);
        return null;
    }

}

호출 예

package cn.chendd.xml;

import com.google.common.collect.Lists;
import com.google.common.io.Files;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

import java.io.File;
import java.io.IOException;
import java.net.URLDecoder;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 文件按行读取验证
 *
 * @author chendd
 * @date 2023/2/18 9:59
 */
@RunWith(JUnit4.class)
public class RowLineReaderTest {

    @Test
    public void reader() throws IOException {
        AtomicInteger atomicInteger = new AtomicInteger();
        //批量数据解析实现
        RowBatchListProcessor execute = rows -> {
            System.out.println(String.format("第 %d 批数据处理,数据 %d 行!" , atomicInteger.addAndGet(1) , rows.size()));
        };
        RowLineProcessor processor = new RowLineProcessor(Lists.newArrayList() , "<tr>" , "</tr>" , execute);
        Files.asCharSource(this.getFile(), Charset.defaultCharset()).readLines(processor);
    }

    private File getFile() throws IOException {
        String fileFolder = URLDecoder.decode(getClass().getResource("").getFile() , StandardCharsets.UTF_8.name());
        return new File(fileFolder , "data.xml");
    }


}

샘플 출력

기타 지침

(1) IO 버퍼를 사용하여 라인 스트림으로 파일을 읽고, 라인의 특수 마크를 시작 마크와 종료 마크로 사용하고, 파일 내용을 세그먼트로 읽고, 읽은 파일을 일치시킨 다음 Jaxb 매핑을 사용하고 구문 분석합니다. ;

(2) 이 예제의 코드 구현은 특수한 형식의 시작 줄과 끝 줄을 가진 일반 파일을 구문 분석하는 것으로, 어떤 형식의 파일도 구문 분석할 수 없으며 유사한 규칙으로 파일 구문 분석에 적용할 수 있습니다.

(3) 이 예는 xml 및 xml 구문 분석을 생성하는 두 가지 예를 제공하며, 데이터 항목 수는 2백만이고 파일 크기는 150M보다 큽니다.

(4) 소스 코드 프로젝트 다운로드는 https://gitee.com/88911006/chendd-examples/tree/master/xml 에서 볼 수 있습니다.

(5) 자세한 내용은 https://www.chendd.cn/blog/article/1626771133537509378.html 을 참조하십시오.

추천

출처blog.csdn.net/haiyangyiba/article/details/129098684