1. 스트리밍 파싱 소개
Java에서는 역사적으로 XML을 다루는 두 가지 접근 방식이 자리 잡았습니다.
이미 알고 있듯, DOM (Document Object Model)은 메모리에 문서 트리를 구성합니다. 요소를 탐색하고 수정하기는 편리하지만, 큰 파일에는 메모리 비용이 너무 큽니다.
반면 SAX (Simple API for XML)는 파일을 순차적으로 처리하며 태그를 만나면 이벤트를 발생시킵니다. 메모리를 절약할 수 있어 매우 큰 문서도 처리할 수 있습니다. 하지만 핸들러를 작성하기 불편하고, 구조를 거슬러 되돌아갈 수 없습니다.
StAX (Streaming API for XML)는 절충안으로 등장했습니다. SAX처럼 스트리밍 방식이지만, 개발자에게 더 많은 제어권을 줍니다. 필요할 때 스트림에서 이벤트를 직접 “끌어” 옵니다. 이러한 방식을 pull 파싱이라고 하며, 더 이해하기 쉽고 유연한 코드를 작성할 수 있게 합니다.
StAX 입문
StAX (Streaming API for XML) — Java용 현대적인 XML 스트리밍 파서로, JDK 6+에서 도입되었습니다.
핵심 아이디어: pull 파싱(“당겨오는” 파서).
SAX에서는 파서가 알아서 메서드를 호출하는 반면(push 모델), StAX에서는 여러분이 과정을 직접 제어합니다:
파서에게 직접 “다음 이벤트를 줘!”라고 요청합니다.
비유:
SAX — 텔레비전과 같습니다. 이벤트가 일방적으로 흘러들어오며, 여러분은 반응만 하면 됩니다.
StAX — 준비되었을 때 직접 “다음 동영상”을 누르는 서비스와 같습니다.
StAX의 핵심 클래스
StAX를 사용하려면 javax.xml.stream 패키지의 두 핵심 클래스가 필요합니다:
- XMLInputFactory — 파서를 생성하는 팩토리.
- XMLStreamReader — XML을 ‘조각’ 단위로 읽는 스트리밍 파서.
기본 예제:
import javax.xml.stream.*;
import java.io.FileInputStream;
XMLInputFactory factory = XMLInputFactory.newInstance();
XMLStreamReader reader = factory.createXMLStreamReader(new FileInputStream("data.xml"));
while (reader.hasNext()) {
int event = reader.next();
// 이벤트 처리
}
reader.close();
2. StAX 작동 원리: pull 모델
StAX에서는 XML 읽기를 직접 제어합니다:
- 스트림을 엽니다(예: 파일).
- XMLStreamReader를 생성합니다.
- 루프에서 reader.next()를 호출해 다음 이벤트를 가져옵니다.
- 이벤트 유형을 확인합니다(START_ELEMENT, END_ELEMENT, CHARACTERS 등).
- 원하는 위치에 도달하면 데이터를 처리합니다.
- 파서를 닫습니다.
작동 흐름:
flowchart TD
A[XMLStreamReader 열기] --> B{hasNext?}
B -- 예 --> C["next()"]
C --> D{이벤트 유형?}
D -- START_ELEMENT --> E[요소 시작 처리]
D -- CHARACTERS --> F[텍스트 처리]
D -- END_ELEMENT --> G[요소 종료 처리]
D -- END_DOCUMENT --> H[종료]
B -- 아니요 --> H
무엇이 편리한가요?
- 다음 요소를 언제 읽을지 스스로 결정할 수 있습니다.
- 필요한 지점에서 “멈추고”, 파일의 일부만 처리할 수 있습니다.
- SAX처럼 수많은 핸들러를 작성할 필요가 없습니다.
3. StAX의 이벤트 유형
reader.next()를 호출하면, 파서는 이벤트 유형을 반환합니다 — 정수(인터페이스 XMLStreamConstants의 상수). 주요 이벤트 유형은 다음과 같습니다:
- START_ELEMENT — XML 요소의 시작(<tag>).
- END_ELEMENT — XML 요소의 끝(</tag>).
- CHARACTERS — 태그 사이의 텍스트 내용.
- END_DOCUMENT — 문서의 끝.
이벤트 처리 예시:
while (reader.hasNext()) {
int event = reader.next();
switch (event) {
case XMLStreamConstants.START_ELEMENT:
String name = reader.getLocalName();
System.out.println("요소 시작: " + name);
break;
case XMLStreamConstants.CHARACTERS:
String text = reader.getText().trim();
if (!text.isEmpty()) {
System.out.println("텍스트: " + text);
}
break;
case XMLStreamConstants.END_ELEMENT:
System.out.println("요소 끝: " + reader.getLocalName());
break;
}
}
4. 유용한 팁
StAX를 언제 사용할까?
다음과 같은 경우 StAX가 최적입니다:
- XML 파일이 매우 큼(기가바이트 단위) — 파일 전체를 메모리에 올리고 싶지 않을 때.
- 문서의 일부만 처리하면 될 때(예: 특정 요소를 찾은 뒤 중지).
- 간단하고 이해하기 쉬운 코드가 필요할 때: StAX는 SAX보다 단순하며, 수많은 핸들러를 작성할 필요가 없습니다.
예시 과제:
- 대용량 XML 데이터 가져오기(예: 1C 내보내기, 은행 거래 내역, 상품 카탈로그).
- 필요한 요소만 검색 및 처리(예: 백만 건 중에서 오직 <transaction>만).
- XML을 실시간으로 변환(예: 필터링, 집계).
DOM, SAX, StAX 비교
| 접근 방식 | 메모리 | 단순성 | 유연성 | 언제 사용 |
|---|---|---|---|---|
| DOM | 높음(모두 메모리에 적재) | 매우 쉬움 | 트리를 변경 가능 | XML 편집이 필요할 때의 소/중형 파일 |
| SAX | 최소 | 복잡함(이벤트 핸들러) | 읽기 전용, 역추적 불가 | 매우 큰 파일, 단순 처리 |
| StAX | 최소 | 보통(pull 모델) | 부분 읽기 가능, 중간 중지 용이 | 유연성과 단순성이 모두 필요한 대용량 파일 |
StAX — 황금 균형점:
— DOM처럼 메모리를 낭비하지 않습니다.
— SAX처럼 복잡한 핸들러가 필요하지 않습니다.
— 파싱 과정을 직접 제어할 수 있습니다.
5. 예제: StAX로 대용량 XML 파일 읽기
예를 들어, "books.xml" 파일이 있다고 합시다:
<library>
<book>
<title>초보자를 위한 Java</title>
<author>이반 이바노프</author>
</book>
<book>
<title>고급 Java</title>
<author>표트르 페트로프</author>
</book>
<!-- ... 많은 책 ... -->
</library>
과제: 모든 책 제목을 출력합니다.
StAX 코드:
import javax.xml.stream.*;
import java.io.*;
public class StaxDemo {
public static void main(String[] args) throws Exception {
XMLInputFactory factory = XMLInputFactory.newInstance();
XMLStreamReader reader = factory.createXMLStreamReader(new FileInputStream("books.xml"));
while (reader.hasNext()) {
int event = reader.next();
if (event == XMLStreamConstants.START_ELEMENT && "title".equals(reader.getLocalName())) {
reader.next(); // CHARACTERS로 이동
System.out.println("도서: " + reader.getText());
}
}
reader.close();
}
}
장점:
- 파일 전체를 메모리에 로드하지 않습니다.
- 백만 권이라도 처리 가능 — 프로그램이 ‘터지지’ 않습니다.
6. StAX 사용 시 흔한 실수
오류 №1: 파서나 스트림을 닫지 않음. 리소스 누수를 막기 위해 항상 XMLStreamReader와 스트림(InputStream)을 닫으세요.
오류 №2: 이벤트 유형을 확인하지 않음. 모든 이벤트가 요소의 시작/끝은 아닙니다. 이벤트 유형을 확인하지 않으면 빈 문자열을 얻거나 필요한 데이터를 놓칠 수 있습니다.
오류 №3: 요소의 중첩을 고려하지 않음. XML 구조가 복잡한 경우(예: 섹션 안의 책) 현재 중첩 수준을 추적해 요소를 혼동하지 않도록 하세요.
오류 №4: 큰 파일에 DOM 사용. 파일이 크면 DOM을 사용하지 마세요. OutOfMemoryError가 발생할 수 있습니다. 큰 파일에는 StAX 또는 SAX를 사용하세요.
오류 №5: 예외를 처리하지 않음. 파일과 XML을 다룰 때는 예외가 발생할 수 있습니다(XMLStreamException, IOException). 적절히 처리하거나 상위로 던지세요.
GO TO FULL VERSION