【发布时间】:2017-09-20 23:57:23
【问题描述】:
我有两个 XML 文件:A.xml 和 B.xml。
每个 XML 都包含许多这样的事件:
<Event>
<EventData Name="Time">09/10/2017 12:54:16</EventData>
<EventData Name="WorkstationName">USER2-PC</EventData>
<EventData Name="UserName">user2</EventData>
</Event>
这些值是示例。
我想搜索 A.xml 中是否存在与 B.xml 中的 "WorkstationName" 和 "UserName" 值相同的事件。
例如,这是 XML:
A.xml
<?xml version="1.0" encoding="UTF-8"?>
<Events>
<Event>
<EventData Name="Time">09/10/2017 12:54:16</EventData>
<EventData Name="WorkstationName">USER2-PC</EventData>
<EventData Name="UserName">user2</EventData>
</Event>
</Events>
B.xml
<?xml version="1.0" encoding="UTF-8"?>
<Events>
<Event>
<EventData Name="Time">09/10/2017 14:54:16</EventData>
<EventData Name="WorkstationName">USER1-PC</EventData>
<EventData Name="UserName">user1</EventData>
</Event>
<Event>
<EventData Name="Time">09/10/2017 13:54:16</EventData>
<EventData Name="WorkstationName">USER2-PC</EventData>
<EventData Name="UserName">user2</EventData>
</Event>
</Events>
预期结果:
<Event>
<EventData Name="Time">09/10/2017 13:54:16</EventData>
<EventData Name="WorkstationName">USER2-PC</EventData>
<EventData Name="UserName">user2</EventData>
</Event>
我写了一个代码:
算法:
$fileA = "C:\tmp\A.xml"
$fileB = "C:\tmp\B.xml"
$a = New-Object Xml.XmlDocument
$a.Load($fileA)
$b = New-Object Xml.XmlDocument
$b.Load($fileB)
$pc = ($event.EventData | Where-Object {$_.Name -eq "WorkstationName"})."#text"
$username = ($event.EventData | Where-Object {$_.Name -eq "UserName"})."#text"
$result = $b.Events.Event | Where-Object {
(($_.EventData | where-object {$_.Name -eq "WorkstationName"})."#text" -eq $pc) -and
(($_.EventData | where-object {$_.Name -eq "UserName"})."#text" -eq $username)
}
$result.EventData
问题是当我使用大型 B.XML 文件(约 250,000 行)时。
我编写了一个代码来创建两个 XML 示例(小 A.XML 文件和大 B.XML 文件):
function createXMLFiles($numberOfLinesToCreateInB){
$legitXmlPrefix = @(0xef, 0xbb, 0xbf, 0x3c, 0x3f, 0x78, 0x6d, 0x6c, 0x20, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x3d, 0x22, 0x31, 0x2e, 0x30, 0x22, 0x20, 0x65, 0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x3d, 0x22, 0x75, 0x74, 0x66, 0x2d, 0x38, 0x22, 0x3f, 0x3e, 0x0d, 0x0a, 0x3c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x3e, 0x0d, 0x0a)
$XMLEnd = @(0x0d, 0x0a, 0x3c, 0x2f, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x3e)
$enc = [system.Text.Encoding]::UTF8
$aXML = @"
<Event>
<EventData Name="Time">09/10/2017 12:54:16</EventData>
<EventData Name="WorkstationName">USER2-PC</EventData>
<EventData Name="UserName">user2</EventData>
</Event>
"@
$data1 = $enc.GetBytes($aXML)
$newXmlFile = "c:\tmp\A.xml"
$newArr = $legitXmlPrefix + $data1 + $XMLEnd
[io.file]::WriteAllBytes($newXmlFile, $newArr)
$bXML = @"
<Event>
<EventData Name="Time">09/10/2017 14:54:16</EventData>
<EventData Name="WorkstationName">USER1-PC</EventData>
<EventData Name="UserName">user1</EventData>
</Event>
<Event>
<EventData Name="Time">09/10/2017 13:54:16</EventData>
<EventData Name="WorkstationName">USER2-PC</EventData>
<EventData Name="UserName">user2</EventData>
</Event>
"@
$newXmlFile = "c:\tmp\B.xml"
$data1 = $enc.GetBytes($bXML)
$newArr = $legitXmlPrefix
$additionals = @"
<Event>
<EventData Name="Time">09/10/2017 14:54:16</EventData>
<EventData Name="WorkstationName">USER1-PC</EventData>
<EventData Name="UserName">user1</EventData>
</Event> `n
"@
$data2 = $enc.GetBytes($additionals)
if($numberOfLinesToCreateInB -gt 0){
$data2 = $data2 * $numberOfLinesToCreateInB
$newArr += $data2
}
$newArr += $data1
$newArr += $XMLEnd
[io.file]::WriteAllBytes($newXmlFile, $newArr)
}
createXMLFiles 50000
如果您运行我编写的算法,您会发现在 B.XML 中从 A.XML 中查找事件需要很长时间。
这是因为 A.XML 中的事件是 B.XML 中的最后一个事件,所以只有当它遇到 B.XML 中的最后一个节点时才会结束。
有没有提高效率的选项?
我虽然可能通过划分部分来使用多线程:一个线程将在事件 0..1000 之间搜索,第二个线程将搜索 1001..2000 等。
但也许你有更好的解决方案。
参考:
How can i use XmlReader in PowerShell to stream big/huge XML files?
How to create a new System.Xml.Linq.XElement with PowerShell
编辑(快速):
我尝试使用 XPATH。传递所有事件时它仍然工作缓慢:
Select-Xml -Path $fileB -XPath "/Events/Event" | Where-Object {
(($events[0].Node.EventData | where-object {$_.Name -eq "WorkstationName"})."#text" -eq $pc) -and
(($events[0].Node.EventData | where-object {$_.Name -eq "UserName"})."#text" -eq $username)
}
按照@Tomalak 的建议,我删除了几乎所有的Where-Object 管道,事情开始变得更快。
Measure-Command {
Select-Xml -Path $fileB -XPath "/Events/Event" | Where-Object {
($_.Node.EventData[1]."#text" -eq $pc) -and
($_.Node.EventData[2]."#text" -eq $username)
}
}
Days : 0
Hours : 0
Minutes : 0
Seconds : 6
Milliseconds : 253
Ticks : 62535333
TotalDays : 7.23788576388889E-05
TotalHours : 0.00173709258333333
TotalMinutes : 0.104225555
TotalSeconds : 6.2535333
TotalMilliseconds : 6253.5333
Measure-Command {
$result = $b.Events.Event | Where-Object {
($_.EventData[1]."#text" -eq $pc) -and
($_.EventData[2]."#text" -eq $username)
}
}
Days : 0
Hours : 0
Minutes : 0
Seconds : 17
Milliseconds : 700
Ticks : 177006124
TotalDays : 0.000204868199074074
TotalHours : 0.00491683677777778
TotalMinutes : 0.295010206666667
TotalSeconds : 17.7006124
TotalMilliseconds : 17700.6124
它的运行速度要快得多,而且-XPath 的使用比常规传递方式(17 秒)更快(6 秒)。
测试涉及 50,000 个事件,250,000 个 XML 行。
编辑(非常快):
大结局是这样的:
Measure-Command {
Select-Xml -Path $fileB -XPath "/Events/Event[EventData[@Name = 'WorkstationName'] = '$pc' and EventData[@Name = 'UserName'] = '$username']"
}
Days : 0
Hours : 0
Minutes : 0
Seconds : 0
Milliseconds : 609
Ticks : 6099484
TotalDays : 7.05958796296296E-06
TotalHours : 0.000169430111111111
TotalMinutes : 0.0101658066666667
TotalSeconds : 0.6099484
TotalMilliseconds : 609.9484
这使它非常快(不到 1 秒!)。
【问题讨论】:
-
首先,测量加载 XML 文件需要多长时间。只需致电
$b.Load($fileB)。如果这是可以接受的,请切换到使用 XPath 来查找您的节点,而不是使用 Powershell 循环,看看这是否会有所改善。如果加载时间本身不可接受,请切换到XMLReader 处理文件,就像您已经建议的那样(C# examples,Powershell example)。 -
@Tomalak,使用
Load()加载大型 XML 文件速度很快(1 秒,甚至更少)。 -
没有。避免所有
where-object管道。单独使用 XPath。 -
您可以将所有这些合并到一个 XPath 中:
"/Events/Event[EventData[@Name = 'WorkstationName'] = '$pc' and EventData[@Name = 'UserName'] = '$username']"- 这会选择分别匹配$pc和$username的任何<Event>。当您将/EventData[@Name = 'Time']/text()附加到该路径时,它只会选择此类事件的时间值。 -
不,实际的正则表达式是不可能的,因为 .NET 本身只支持 XPath 1.0,而正则表达式不是该标准的一部分。虽然它们已在 XPath 2.0 中引入,但您可能能够找到支持此版本的独立命令行工具。但这取决于您想要做什么,因为在 XPath 1.0 中有个基本字符串函数(
substring()、substring-before()、substring-after()、starts-with()、contains())可能是足够你想要的。 (小心,the docs 还提到了 XPath 2.0 函数。)
标签: xml powershell