在 Powershell 中从非常大的 XML 文件中删除节点
Posted
技术标签:
【中文标题】在 Powershell 中从非常大的 XML 文件中删除节点【英文标题】:Deleting nodes from VERY LARGE XML file in Powershell 【发布时间】:2020-11-05 04:52:15 【问题描述】:我目前正在阅读一个巨大的 (3GB) XML 文件。这个 XML 文件由记录组成,我希望根据属性值删除一些(大约 5% 的记录),然后将剩余的 95% 写入新文件。
我当前的代码:
$Stopwatch = [System.Diagnostics.Stopwatch]::StartNew()
$xml = [xml]''
$xml.Load("C:\Users\Jack\Documents\development\record removal\records.xml")
$nodes = $xml.SelectNodes("//record[@category='APPLE'] | //record[@category='BANANA'] |
//record[@category='ORANGE']")
foreach ($node in $nodes)
$node.ParentNode.RemoveChild($node)
$xml.save("C:\Users\Jack\Documents\development\record removal\records-NEW.xml")
$StopWatch.Stop()
$StopWatch.Elapsed.TotalSeconds
完成任务所花费的时间太多,我需要它更有效率。当我一次只处理一个类别时,速度快了很多,我是否遗漏了一些明显的东西?
我应该使用 XMLReader 之类的其他东西吗?
XML 示例:
<?xml version="1.0" encoding="UTF-8"?>
<records xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<record category="APPLE" sub-category="FRUIT">
</record>
<record category="BANANA" sub-category="FRUIT">
</record>
<record category="ORANGE" sub-category="FRUIT">
</record>
<record category="KIWI" sub-category="FRUIT">
</record>
<record category="GRAPE" sub-category="FRUIT">
</record>
</records>
更新的解决方案
使用 jdweng 的代码,我已将其导入到我的 powershell 代码中。完整代码如下:
$Stopwatch = [System.Diagnostics.Stopwatch]::StartNew()
$id = get-random
$Assem = (
"C:\Windows\Microsoft.NET\Framework\v4.0.30319\System.XML.dll",
"C:\Windows\Microsoft.NET\Framework\v4.0.30319\System.Xml.Linq.dll"
)
$Source = @”
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
namespace Jack.Tools
public class Class$id
const string INPUT_FILENAME = @"C:\temp\old.xml";
const string OUTPUT_FILENAME = @"C:\temp\new.xml";
public static void method()
XmlReader reader = XmlReader.Create(INPUT_FILENAME);
XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true;
XmlWriter writer = XmlWriter.Create(OUTPUT_FILENAME, settings);
writer.WriteStartDocument();
writer.WriteStartElement("records");
writer.WriteAttributeString("xmlns", "xsi", null, "http://www.w3.org/2001/XMLSchema-instance");
while (!reader.EOF)
if(reader.Name != "record")
reader.ReadToFollowing("record");
if (!reader.EOF)
XElement record = (XElement)XElement.ReadFrom(reader);
if ((string)record.Attribute("category") != "APPLE")
record.WriteTo(writer);
writer.WriteEndElement();
writer.WriteEndDocument();
writer.Flush();
writer.Close();
“@
Add-Type -AssemblyName Microsoft.CSharp
Add-Type -AssemblyName System
Add-Type -AssemblyName System.Core
Add-Type -AssemblyName System.Data
Add-Type -AssemblyName System.Data.DataSetExtensions
Add-Type -AssemblyName System.Xml
Add-Type -AssemblyName System.Linq
Add-Type -ReferencedAssemblies $Assem -TypeDefinition $Source -Language CSharp
iex "[Jack.Tools.Class$id]::method()"
$StopWatch.Stop()
$StopWatch.Elapsed.TotalSeconds
【问题讨论】:
在这种情况下,是的。 XmlReader 应该一次读取一条记录并将它们写入 XmlWriter,或者如果它们符合条件则丢弃它们。现在,您正在内存中加载 3GB 的文本,将其解析为超过 3GB 的对象,扫描这 3GB 的记录,最后将剩余的 3GB 的节点写入磁盘。使用 XmlReader/XmlWriter 组合,您一次只能在内存中保留一条记录,以及用于读取器、写入器流的文件缓冲区 @PanagiotisKanavos 这可以在 Powershell 中完成吗?我还在学习 Powershell,还不知道如何用 C# 编写代码。 您已经在编写 C# - 您编写的代码直接创建和使用 .NET 类。您将失去任何 Intellisense 和编译优势,这意味着编写此代码相当棘手 @PanagiotisKanavos 感谢队友,我设法使用 Add-Type 导入了 c# 方法。我会用我的代码在这里发布答案,感谢你和 jdweng 它完美地工作!!! 【参考方案1】:尝试以下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
namespace ConsoleApplication11
class Program
const string INPUT_FILENAME = @"c:\temp\test.xml";
const string OUTPUT_FILENAME = @"c:\temp\test1.xml";
static void Main(string[] args)
XmlReader reader = XmlReader.Create(INPUT_FILENAME);
XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true;
XmlWriter writer = XmlWriter.Create(OUTPUT_FILENAME, settings);
writer.WriteStartDocument();
writer.WriteStartElement("records");
writer.WriteAttributeString("xmlns", "xsi", null, "http://www.w3.org/2001/XMLSchema-instance");
while (!reader.EOF)
if(reader.Name != "record")
reader.ReadToFollowing("record");
if (!reader.EOF)
XElement record = (XElement)XElement.ReadFrom(reader);
if ((string)record.Attribute("category") != "BANANA")
record.WriteTo(writer);
writer.WriteEndElement();
writer.WriteEndDocument();
writer.Flush();
writer.Close();
【讨论】:
以上是关于在 Powershell 中从非常大的 XML 文件中删除节点的主要内容,如果未能解决你的问题,请参考以下文章