방법: 큰 XML 문서의 스트리밍 변환 수행
큰 XML 파일을 변환하고 응용 프로그램의 메모리 사용 공간이 예상 가능하도록 응용 프로그램을 작성해야 하는 경우가 있습니다.XML 트리를 매우 큰 XML 파일로 채우려는 경우 메모리 사용은 파일 크기에 비례하므로 지나치게 증가하게 됩니다.따라서 스트리밍 기법을 대신 사용해야 합니다.
스트리밍 기법은 소스 문서를 한 번만 처리해야 하고 문서 순서의 요소를 처리할 수 있는 경우에 가장 효과적으로 적용됩니다.OrderBy와 같은 특정 표준 쿼리 연산자는 자신의 소스를 반복하고 모든 데이터를 수집하여 정렬한 다음 시퀀스의 첫 번째 항목을 최종적으로 생성합니다.첫 번째 항목을 반환하기 전에 소스를 유형화하는 쿼리 연산자를 사용하는 경우 응용 프로그램에 대한 작은 메모리 사용 공간이 유지되지 않습니다.
방법: 헤더 정보에 액세스하여 XML 조각 스트림에서 설명하는 기법을 사용하는 경우에도 변환된 문서를 포함하는 XML 트리를 어셈블하려고 하면 메모리 사용 공간이 너무 커집니다.
두 가지 주요 방법이 있습니다.그 중 하나는 XStreamingElement의 지연된 처리 특성을 사용하는 것이고,다른 하나는 XmlWriter를 만들고 LINQ to XML의 기능을 사용하여 요소를 XmlWriter에 쓰는 것입니다.이 항목에서는 두 방법을 모두 보여 줍니다.
예제
다음 예제는 방법: 헤더 정보에 액세스하여 XML 조각 스트림의 예제를 기반으로 합니다.
이 예제에서는 XStreamingElement의 지연된 실행 기능을 사용하여 출력을 스트림합니다.이 예제에서는 작은 메모리 사용 공간을 유지하면서도 매우 큰 문서를 변환할 수 있습니다.
사용자 지정 축(StreamCustomerItem)은 문서에 Customer, Name 및 Item 요소가 있고 이러한 요소가 다음 Source.xml 문서의 경우와 마찬가지로 정렬되어 있다고 가정하고 작성된 것입니다.그러나 더욱 강력한 구현은 잘못된 문서의 구문을 분석할 준비가 되어 있습니다.
참고
다음 예제에서는 C#의 yield return 구문을 사용합니다.Visual Basic에서는 IEnumerable(Of XElement) 인터페이스를 구현하는 클래스를 사용하여 동등한 코드가 제공됩니다.Visual Basic에서 IEnumerable(Of T)을 구현하는 예제는 연습: Visual Basic에서 IEnumerable(Of T) 구현을 참조하십시오.
다음은 소스 문서인 Source.xml입니다.
<?xml version="1.0" encoding="utf-8" ?>
<Root>
<Customer>
<Name>A. Datum Corporation</Name>
<Item>
<Key>0001</Key>
</Item>
<Item>
<Key>0002</Key>
</Item>
<Item>
<Key>0003</Key>
</Item>
<Item>
<Key>0004</Key>
</Item>
</Customer>
<Customer>
<Name>Fabrikam, Inc.</Name>
<Item>
<Key>0005</Key>
</Item>
<Item>
<Key>0006</Key>
</Item>
<Item>
<Key>0007</Key>
</Item>
<Item>
<Key>0008</Key>
</Item>
</Customer>
<Customer>
<Name>Southridge Video</Name>
<Item>
<Key>0009</Key>
</Item>
<Item>
<Key>0010</Key>
</Item>
</Customer>
</Root>
static IEnumerable<XElement> StreamCustomerItem(string uri)
{
using (XmlReader reader = XmlReader.Create(uri))
{
XElement name = null;
XElement item = null;
reader.MoveToContent();
// Parse the file, save header information when encountered, and yield the
// Item XElement objects as they are created.
// loop through Customer elements
while (reader.Read())
{
if (reader.NodeType == XmlNodeType.Element
&& reader.Name == "Customer")
{
// move to Name element
while (reader.Read())
{
if (reader.NodeType == XmlNodeType.Element &&
reader.Name == "Name")
{
name = XElement.ReadFrom(reader) as XElement;
break;
}
}
// loop through Item elements
while (reader.Read())
{
if (reader.NodeType == XmlNodeType.EndElement)
break;
if (reader.NodeType == XmlNodeType.Element
&& reader.Name == "Item")
{
item = XElement.ReadFrom(reader) as XElement;
if (item != null)
{
XElement tempRoot = new XElement("Root",
new XElement(name)
);
tempRoot.Add(item);
yield return item;
}
}
}
}
}
}
}
static void Main(string[] args)
{
XStreamingElement root = new XStreamingElement("Root",
from el in StreamCustomerItem("Source.xml")
select new XElement("Item",
new XElement("Customer", (string)el.Parent.Element("Name")),
new XElement(el.Element("Key"))
)
);
root.Save("Test.xml");
Console.WriteLine(File.ReadAllText("Test.xml"));
}
Module Module1
Sub Main()
Dim root = New XStreamingElement("Root",
From el In New StreamCustomerItem("Source.xml")
Select <Item>
<Customer><%= el.Parent.<Name>.Value %></Customer>
<%= el.<Key> %>
</Item>
)
root.Save("Test.xml")
Console.WriteLine(My.Computer.FileSystem.ReadAllText("Test.xml"))
End Sub
End Module
Public Class StreamCustomerItem
Implements IEnumerable(Of XElement)
Private _uri As String
Public Sub New(ByVal uri As String)
_uri = uri
End Sub
Public Function GetEnumerator() As IEnumerator(Of XElement) Implements IEnumerable(Of XElement).GetEnumerator
Return New StreamCustomerItemEnumerator(_uri)
End Function
Public Function GetEnumerator1() As IEnumerator Implements IEnumerable.GetEnumerator
Return Me.GetEnumerator()
End Function
End Class
Public Class StreamCustomerItemEnumerator
Implements IEnumerator(Of XElement)
Private _current As XElement
Private _customerName As String
Private _reader As Xml.XmlReader
Private _uri As String
Public Sub New(ByVal uri As String)
_uri = uri
_reader = Xml.XmlReader.Create(_uri)
_reader.MoveToContent()
End Sub
Public ReadOnly Property Current As XElement Implements IEnumerator(Of XElement).Current
Get
Return _current
End Get
End Property
Public ReadOnly Property Current1 As Object Implements IEnumerator.Current
Get
Return Me.Current
End Get
End Property
Public Function MoveNext() As Boolean Implements IEnumerator.MoveNext
Dim item As XElement
Dim name As XElement
' Parse the file, save header information when encountered, and return the
' current Item XElement.
' loop through Customer elements
While _reader.Read()
If _reader.NodeType = Xml.XmlNodeType.Element Then
Select Case _reader.Name
Case "Customer"
' move to Name element
While _reader.Read()
If _reader.NodeType = Xml.XmlNodeType.Element AndAlso
_reader.Name = "Name" Then
name = TryCast(XElement.ReadFrom(_reader), XElement)
_customerName = If(name IsNot Nothing, name.Value, "")
Exit While
End If
End While
Case "Item"
item = TryCast(XElement.ReadFrom(_reader), XElement)
Dim tempRoot = <Root>
<Name><%= _customerName %></Name>
<%= item %>
</Root>
_current = item
Return True
End Select
End If
End While
Return False
End Function
Public Sub Reset() Implements IEnumerator.Reset
_reader = Xml.XmlReader.Create(_uri)
_reader.MoveToContent()
End Sub
#Region "IDisposable Support"
Private disposedValue As Boolean ' To detect redundant calls
' IDisposable
Protected Overridable Sub Dispose(ByVal disposing As Boolean)
If Not Me.disposedValue Then
If disposing Then
_reader.Close()
End If
End If
Me.disposedValue = True
End Sub
Public Sub Dispose() Implements IDisposable.Dispose
Dispose(True)
GC.SuppressFinalize(Me)
End Sub
#End Region
End Class
이 코드는 다음과 같이 출력됩니다.
<?xml version="1.0" encoding="utf-8"?>
<Root>
<Item>
<Customer>A. Datum Corporation</Customer>
<Key>0001</Key>
</Item>
<Item>
<Customer>A. Datum Corporation</Customer>
<Key>0002</Key>
</Item>
<Item>
<Customer>A. Datum Corporation</Customer>
<Key>0003</Key>
</Item>
<Item>
<Customer>A. Datum Corporation</Customer>
<Key>0004</Key>
</Item>
<Item>
<Customer>Fabrikam, Inc.</Customer>
<Key>0005</Key>
</Item>
<Item>
<Customer>Fabrikam, Inc.</Customer>
<Key>0006</Key>
</Item>
<Item>
<Customer>Fabrikam, Inc.</Customer>
<Key>0007</Key>
</Item>
<Item>
<Customer>Fabrikam, Inc.</Customer>
<Key>0008</Key>
</Item>
<Item>
<Customer>Southridge Video</Customer>
<Key>0009</Key>
</Item>
<Item>
<Customer>Southridge Video</Customer>
<Key>0010</Key>
</Item>
</Root>
다음 예제는 방법: 헤더 정보에 액세스하여 XML 조각 스트림의 예제도 기반으로 합니다.
이 예제에서는 LINQ to XML의 기능을 사용하여 요소를 XmlWriter에 씁니다.이 예제에서는 작은 메모리 사용 공간을 유지하면서도 매우 큰 문서를 변환할 수 있습니다.
사용자 지정 축(StreamCustomerItem)은 문서에 Customer, Name 및 Item 요소가 있고 이러한 요소가 다음 Source.xml 문서의 경우와 마찬가지로 정렬되어 있다고 가정하고 작성된 것입니다.그러나 더욱 강력한 구현은 XSD를 사용하여 소스 문서의 유효성을 검사하거나, 잘못된 문서의 구문을 분석할 준비가 되어 있습니다.
이 예제에서는 이 항목의 이전 예제와 동일한 소스 문서인 Source.xml을 사용하고똑같은 출력을 생성합니다.
XStreamingElement를 사용하여 출력 XML을 스트림하는 것이 XmlWriter에 쓰는 것보다 좋은 방법입니다.
참고
다음 예제에서는 C#의 yield return 구문을 사용합니다.Visual Basic에서는 IEnumerable(Of XElement) 인터페이스를 구현하는 클래스를 사용하여 동등한 코드가 제공됩니다.Visual Basic에서 IEnumerable(Of T)을 구현하는 예제는 연습: Visual Basic에서 IEnumerable(Of T) 구현을 참조하십시오.
static IEnumerable<XElement> StreamCustomerItem(string uri)
{
using (XmlReader reader = XmlReader.Create(uri))
{
XElement name = null;
XElement item = null;
reader.MoveToContent();
// Parse the file, save header information when encountered, and yield the
// Item XElement objects as they are created.
// loop through Customer elements
while (reader.Read())
{
if (reader.NodeType == XmlNodeType.Element
&& reader.Name == "Customer")
{
// move to Name element
while (reader.Read())
{
if (reader.NodeType == XmlNodeType.Element &&
reader.Name == "Name")
{
name = XElement.ReadFrom(reader) as XElement;
break;
}
}
// loop through Item elements
while (reader.Read())
{
if (reader.NodeType == XmlNodeType.EndElement)
break;
if (reader.NodeType == XmlNodeType.Element
&& reader.Name == "Item")
{
item = XElement.ReadFrom(reader) as XElement;
if (item != null) {
XElement tempRoot = new XElement("Root",
new XElement(name)
);
tempRoot.Add(item);
yield return item;
}
}
}
}
}
}
}
static void Main(string[] args)
{
IEnumerable<XElement> srcTree =
from el in StreamCustomerItem("Source.xml")
select new XElement("Item",
new XElement("Customer", (string)el.Parent.Element("Name")),
new XElement(el.Element("Key"))
);
XmlWriterSettings xws = new XmlWriterSettings();
xws.OmitXmlDeclaration = true;
xws.Indent = true;
using (XmlWriter xw = XmlWriter.Create("Output.xml", xws)) {
xw.WriteStartElement("Root");
foreach (XElement el in srcTree)
el.WriteTo(xw);
xw.WriteEndElement();
}
string str = File.ReadAllText("Output.xml");
Console.WriteLine(str);
}
Module Module1
Sub Main()
Dim srcTree =
From el In New StreamCustomerItem("Source.xml")
Select <Item>
<Customer><%= el.Parent.<Name>.Value %></Customer>
<%= el.<Key> %>
</Item>
Dim xws = New Xml.XmlWriterSettings()
xws.OmitXmlDeclaration = True
xws.Indent = True
Using xw = Xml.XmlWriter.Create("Output.xml", xws)
xw.WriteStartElement("Root")
For Each el In srcTree
el.WriteTo(xw)
Next
xw.WriteEndElement()
End Using
Dim s = My.Computer.FileSystem.ReadAllText("Output.xml")
Console.WriteLine(s)
End Sub
End Module
Public Class StreamCustomerItem
Implements IEnumerable(Of XElement)
Private _uri As String
Public Sub New(ByVal uri As String)
_uri = uri
End Sub
Public Function GetEnumerator() As IEnumerator(Of XElement) Implements IEnumerable(Of XElement).GetEnumerator
Return New StreamCustomerItemEnumerator(_uri)
End Function
Public Function GetEnumerator1() As IEnumerator Implements IEnumerable.GetEnumerator
Return Me.GetEnumerator()
End Function
End Class
Public Class StreamCustomerItemEnumerator
Implements IEnumerator(Of XElement)
Private _current As XElement
Private _customerName As String
Private _reader As Xml.XmlReader
Private _uri As String
Public Sub New(ByVal uri As String)
_uri = uri
_reader = Xml.XmlReader.Create(_uri)
_reader.MoveToContent()
End Sub
Public ReadOnly Property Current As XElement Implements IEnumerator(Of XElement).Current
Get
Return _current
End Get
End Property
Public ReadOnly Property Current1 As Object Implements IEnumerator.Current
Get
Return Me.Current
End Get
End Property
Public Function MoveNext() As Boolean Implements IEnumerator.MoveNext
Dim item As XElement
Dim name As XElement
' Parse the file, save header information when encountered, and return the
' current Item XElement.
' loop through Customer elements
While _reader.Read()
If _reader.NodeType = Xml.XmlNodeType.Element Then
Select Case _reader.Name
Case "Customer"
' move to Name element
While _reader.Read()
If _reader.NodeType = Xml.XmlNodeType.Element AndAlso
_reader.Name = "Name" Then
name = TryCast(XElement.ReadFrom(_reader), XElement)
_customerName = If(name IsNot Nothing, name.Value, "")
Exit While
End If
End While
Case "Item"
item = TryCast(XElement.ReadFrom(_reader), XElement)
Dim tempRoot = <Root>
<Name><%= _customerName %></Name>
<%= item %>
</Root>
_current = item
Return True
End Select
End If
End While
Return False
End Function
Public Sub Reset() Implements IEnumerator.Reset
_reader = Xml.XmlReader.Create(_uri)
_reader.MoveToContent()
End Sub
#Region "IDisposable Support"
Private disposedValue As Boolean ' To detect redundant calls
' IDisposable
Protected Overridable Sub Dispose(ByVal disposing As Boolean)
If Not Me.disposedValue Then
If disposing Then
_reader.Close()
End If
End If
Me.disposedValue = True
End Sub
Public Sub Dispose() Implements IDisposable.Dispose
Dispose(True)
GC.SuppressFinalize(Me)
End Sub
#End Region
End Class
이 코드는 다음과 같이 출력됩니다.
<Root>
<Item>
<Customer>A. Datum Corporation</Customer>
<Key>0001</Key>
</Item>
<Item>
<Customer>A. Datum Corporation</Customer>
<Key>0002</Key>
</Item>
<Item>
<Customer>A. Datum Corporation</Customer>
<Key>0003</Key>
</Item>
<Item>
<Customer>A. Datum Corporation</Customer>
<Key>0004</Key>
</Item>
<Item>
<Customer>Fabrikam, Inc.</Customer>
<Key>0005</Key>
</Item>
<Item>
<Customer>Fabrikam, Inc.</Customer>
<Key>0006</Key>
</Item>
<Item>
<Customer>Fabrikam, Inc.</Customer>
<Key>0007</Key>
</Item>
<Item>
<Customer>Fabrikam, Inc.</Customer>
<Key>0008</Key>
</Item>
<Item>
<Customer>Southridge Video</Customer>
<Key>0009</Key>
</Item>
<Item>
<Customer>Southridge Video</Customer>
<Key>0010</Key>
</Item>
</Root>