【问题标题】:How to find event object in large XML file如何在大型 XML 文件中查找事件对象
【发布时间】: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 的任何 &lt;Event&gt;。当您将/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


【解决方案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)    
}

翻译成英文如下:

  • 在&lt;Events&gt; 中的所有&lt;Event&gt; 节点中,选择那些
    • 有一个&lt;EventData&gt; 和一个WorkstationName 的@Name 和一个$pc 的值与
    • 有一个&lt;EventData&gt;,其@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'
]

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-11-16
    • 1970-01-01
    • 1970-01-01
    • 2012-07-14
    • 1970-01-01
    • 2017-10-14
    • 2020-12-24
    • 1970-01-01
    相关资源
    最近更新 更多