我有两个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秒!)。
答案 0 :(得分:1)
这种方法的缓慢部分是复杂的PowerShell Where-Object
管道。这对于大输入文档来说效率不高,因为它涉及创建一整套特定于Powershell的临时包装对象Where-Object
实际可以使用。
最适合从XML文档中有效选择特定节点的工具是XPath。 Select-Xml
cmdlet允许您针对XML文档运行XPath过滤器。
您的代码:
$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)
}
翻译成英文如下:
<Event>
中的所有<Events>
个节点中,选择那些节点
<EventData>
@Name
WorkstationName
且值$pc
和<EventData>
@Name
UserName
且值$username
直接转换为XPath,如下所示:
$events_by_user_and_pc = "
/Events/Event[
EventData[@Name = 'WorkstationName'] = '$pc' and
EventData[@Name = 'UserName'] = '$username'
]
"
$result = Select-Xml -Path $fileB -XPath $events_by_user_and_pc
...看起来非常像Powershell代码。 XPath并不关心换行符和空格,所以你可以很好地格式化它。
String functions可用于进行部分比较。在@WorkstationName
以USER2
开头的任何PC上为特定用户选择所有事件的路径如下所示:
/Events/Event[
starts-with(EventData[@Name = 'WorkstationName'], 'USER2') and
EventData[@Name = 'UserName'] = '$username'
]