我有一个ITunes库XML文件备份文件 - 大约15 MB。
我的C驱动器上有20K音乐文件,E驱动器上有大约25K文件,文件夹结构完全相同。
我正在遍历第一个位置并逐个文件并检查文件是否在第二个位置。那部分适合我。
现在,对于所有这些重复文件,如果XML驱动器中存在来自E驱动器的文件路径,但XML中不存在C驱动器路径,那么我想从C驱动器中删除该文件。
检查XML文件中是否存在字符串的最佳方法是什么(我必须至少执行20K次)?
答案 0 :(得分:3)
根据您是否要计算字符串出现的次数,或者您只是检查字符串是否存在,您的方法会略有不同。但是,这些是我考虑做的两种方式:
如果你想用最少的记忆来做这件事:
逐行加载文件(或者,如果您的XML没有像这样格式化,请使用XML解析器逐个节点...我相信有XML解析器可以执行此操作)。对每个字符串执行搜索操作。如果正确覆盖最后一行,一次只能有一个行/节点在内存中。这样做的缺点是它需要更长的时间,文件将更长时间打开。
如果您想快速执行此操作:
将整个文件加载到内存中,不要费心解析它,只搜索每个字符串。
<强> 修改 强>
根据您的说明,我首先收集数组中的所有重复文件名,然后使用我的第一种方法(上面)继续扫描XML文件的每一行。如果您已经在内存中存储了20K文件名,我会毫不犹豫地同时加载整个15MB XML。
答案 1 :(得分:2)
建议:以文本形式加载,使用正则表达式提取所需的字符串(我想它们是用特定标记括起来的)并用它们构建一个哈希列表。您可以使用该列表来检查是否存在。
答案 2 :(得分:2)
这是使用Linq的简单解决方案。运行足够快,一次性使用:
using System;
using System.IO;
using System.Linq;
using System.Xml.Linq;
class ITunesChecker
{
static void Main(string[] args)
{
// retrieve file names
string baseFolder = @"E:\My Music\";
string[] filesM4a = Directory.GetFiles(baseFolder, "*.m4a", SearchOption.AllDirectories);
string[] filesMp3 = Directory.GetFiles(baseFolder, "*.mp3", SearchOption.AllDirectories);
string[] files = new string[filesM4a.Length + filesMp3.Length];
Array.Copy(filesM4a, 0, files, 0, filesM4a.Length);
Array.Copy(filesMp3, 0, files, filesM4a.Length, filesMp3.Length);
// convert to the format used by iTunes
for (int i = 0; i < files.Length; i++)
{
Uri uri = null;
if (Uri.TryCreate(files[i], UriKind.Absolute, out uri))
{
files[i] = uri.AbsoluteUri.Replace("file:///", "file://localhost/");
}
}
// read the files from iTunes library.xml
XDocument library = XDocument.Load(@"E:\My Music\iTunes\iTunes Music Library.xml");
var q = from node in library.Document.Descendants("string")
where node.ElementsBeforeSelf("key").Where(n => n.Parent == node.Parent).Last().Value == "Location"
select node.Value;
// do the set operations you are interested in
var missingInLibrary = files.Except(q, StringComparer.InvariantCultureIgnoreCase);
var missingInFileSystem = q.Except(files, StringComparer.InvariantCultureIgnoreCase);
var presentInBoth = files.Intersect(q, StringComparer.InvariantCultureIgnoreCase);
}
}
答案 3 :(得分:1)
按字母顺序对您匹配的字符串列表进行排序,然后构建一个索引数组,该数组会告诉您列表的开头位于每个字符的位置,该字符是其中一个字符串的起始字符,可能索引到第二个字符串字符取决于变化的广度,如果您的匹配区分大小写。
使用流来逐字符号读取文件以最小化内存占用,检查索引数组以查看该字符在字符串列表中的开始和结束位置,以便您可以拉出该字符页面,如果有任何以这些开头的内容字符组合。然后继续在页面内部进行过滤,直到剩下一个匹配并且下一个字符匹配为止。
从要匹配的字符串列表中删除该字符串,如果需要,将其放在另一个列表中。然后开始检查下一个字符的索引,并在每次没有匹配时继续这样做。
索引为您提供了更高效的聚合,以最大限度地减少迭代的项目数。
这可以为您提供两个字符的深度索引:
Dictionary<string,int> stringIndex = new Dictionary<char,int>();
for(int i = 0; i < sortedSearchStrings.Length; i++;)
{
if (!stringIndex.Keys.Contains(sortedSearchStrings[i][0])) stringIndex[sortedSearchStrings[i][0]] = i;
if (!stringIndex.Keys.Contains(sortedSearchStrings[i][0] + sortedSearchStrings[i][1])) stringIndex[sortedSearchStrings[i][0] + sortedSearchStrings[i][1]] = i;
}
然后在列表中查找起始索引,您只需访问:
int startOfCurrentCharPage = stringIndex[string.Format("{0}{1}", lastChar, currentChar)];
答案 4 :(得分:1)
是否可以直接使用xml文档并跳过第一步?
如果是这样,您可以使用Xml.XmlDocument,并从那里使用xpath.XmlNode.SelectNodes(字符串),使用xpath导航文档。我不知道文档中有什么样的信息,但是第二阶段措辞的方式给出了这样的想法:有时C:\上的路径和E:\上的路径都存在?如果是这样,它就像两个IO.File.Exists检查一样简单,然后是IO.File.Delete()。
我的意思是,不是搜索XML文档N次以获取字符串,而是在搜索文档时删除重复文件,以便只运行一次文档。
我不会使用iTunes或手头有一个库备份来说明它是否可行。
答案 5 :(得分:0)
从XML中读取每个字符串并将其写入HashSet<string>
。如果要查找字符串,请在HashSet中查找。成本将是O(n)读取XML,O(n)来执行HashSet的n次查找。不要尝试在XML中重复搜索(而是在HashSet中进行20,000次搜索),因为XML没有为搜索编制索引/优化。