对于相关的实现前人已经做得很好了,winmerge就是其中非常成功的软件。笔者重新实现只是因为winmerge的报表功能不能提供我需要的形式的报表。对此,也处于兴趣,按照自己的比较算法实现了一个这样的功能。
先简述一下算法思路,这个算法虽说是寻找不一致的内容,但算法的实习是以尽可能找到对应的一致内容为基础。剥除一致的内容,剩下的就是不一致的内容。算法存在这样的一个假设,比较的两个文件目标文件只有少量是不一样的,大部分都是一致的。更进一步说,文件B是在文件A的基础上进行的修改。其修内容可能是在某个位置加了一行,删除了一行,或者修改了某一行等等。这个假设并不过分,因为如果比较两个面目全非的文件,比较的结果并没有参考价值。现在用个简单的例子描述下比较过程。
字符串1:AABBCCDDEEFF
字符串2:AABBBDCDDEEFFG
步骤1:
为了得字符串1和字符串1不一致之处,第一步先匹配出最大一致的字符串。这里一致的字符串有
AABB ,CDDEEFF,暂且称为"可能对应组",这里有两个对应组。(这里的一致,表示从字符串1的某个位置开始的一段距离和字符串2完全一致。它们存在可能对应的关系,如果真的存在对应,称为"真实对应组",它表明这个组的字符串是原本没有被修改过的地方)通常"可能对应组"不仅有一个,我们认定字符串长度最长的"可能对应组",是"真实对应组",这里是CDDEEFF。将"真实对应组"分别从两个字符串取出。 我们得到一下的一组比较值。
字符串1:AABBC (CDDEEFF) null
字符串2:AABBBD (CDDEEFF) G
括号代表"真实对应组",已经去除的部分,null代表空字符串。去除后两个字符串,分裂成了四个字符串。四个字符串亮亮之间存在空间的对应关系。也就是修改前和修改后的关系。我们把字符串1.1和字符串2.1称之为"对应块"1,我们把字符串1.2和字符串2.2称为"对应块"2.
字符串1.1:AABBC 字符串1.2:null
字符串2.1:AABBBD 字符串2.2: G
步骤2:按照步骤1的方法比较每个"对应块"。分裂成四个"对应块"
......重复以上步骤。
最终结果是已经不存在"真实对应组"。现在每个"对应块"就是文件不一致的部分。这里为了说明用字符串来比较,如果是比较文件应该会用行来比较。A可能代表的是若干行.
给出.net代码的实现
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
namespace CompareFile
{
public class CompareFile
{
public string output = string.Empty;
public string dir1 = string.Empty;
public string dir2 = string.Empty;
public OutPutBase OutTool = null;
//入口函数
public void DoCompareFile()
{
DoCompareFile(null);
}
//比较的文件列表
public void DoCompareFile(string[] files)
{
if (files == null || files.Length == 0)
{
if (output != string.Empty && dir1 != string.Empty && dir2 != string.Empty)
CheckDir(new DirectoryInfo(dir1));
}
else
{
CheckFiles(files);
}
}
//比较目标文件夹
private void CheckDir(DirectoryInfo dir)
{
if (OutTool == null)
OutTool = new OutPutBase();
OutTool.outFile = output;
foreach (FileInfo file in dir.GetFiles())
{
if (file.Extension == ".cs" || file.Extension == ".aspx.cs" || file.Extension == ".aspx")
{
System.Console.WriteLine(file.Name);
string Tem = dir.FullName.Replace(dir1, dir2);
string File2 = Tem + "//" + file.Name;
CompareList(file.FullName, GetFileList(file.FullName), GetFileList(File2));
}
}
foreach (DirectoryInfo d in dir.GetDirectories())
{
CheckDir(d);
}
}
//比较文件列表
private void CheckFiles(string[] files)
{
if (OutTool == null)
OutTool = new OutPutBase();
OutTool.outFile = output;
foreach (string fileName in files)
{
FileInfo file = new FileInfo(fileName);
//这里过滤了一下文件类型,实际的应用可以根据需要定义类型
if (file.Extension == ".cs" || file.Extension == ".aspx.cs" || file.Extension == ".aspx")
{
System.Console.WriteLine(file.Name);
string File2 = file.FullName.Replace(dir1, dir2);
if (File.Exists(File2))
{
CompareList(file.FullName, GetFileList(file.FullName), GetFileList(File2));
}
}
}
}
//读取文件形成字符串行集合
List<string> GetFileList(string FileName)
{
List<string> FileList = new List<string>();
using (StreamReader sw = new StreamReader(FileName, System.Text.Encoding.Default))
{
string str = null;
while ((str = sw.ReadLine()) != null)
{
FileList.Add(str);
}
}
return FileList;
}
//比较文件行集合
void CompareList(string FileName, List<string> FileList1, List<string> FileList2)
{
List<string> SameList = new List<string>();
CompareResult cr = FindMostLen(FileList1, FileList2);
//结果输出,这里用输出CSV的方式,此处可由读者自行实现
//最大长度为0代表已经不存在真实对应组
if (cr.mostLen == 0) OutTool.WriteList(FileName, cr.file2.downFile, cr.file1.downFile);
else
{
CompareList(FileName, cr.file1.upFile, cr.file2.upFile);
CompareList(FileName, cr.file1.downFile, cr.file2.downFile);
}
}
//自定义类存放比较结果
class CompareResult
{
public int mostLen = 0;
public result file1 = new result();
public result file2 = new result();
}
//自定义类存放文件块
class result
{
public int mostStart = 0;
public int mostEnd = 0;
public List<string> upFile = new List<string>();
public List<string> downFile = new List<string>();
public void SetFile(List<string> file)
{
upFile = file.GetRange(0, mostStart);
downFile = file.GetRange(mostEnd, file.Count - mostEnd);
}
}
//寻找真实对应块的过程
CompareResult FindMostLen(List<string> FileList1, List<string> FileList2)
{
CompareResult compareResult = new CompareResult();
for (int i = 0; i < FileList1.Count; i++)
{
int len = 0;
int start = FileList2.IndexOf(FileList1[i]);
if (start == -1)
{
continue;
}
else
{
int flag = i;
int j = start;
for (; j < FileList2.Count; j++)
{
if (flag < FileList1.Count)
{
if (FileList2[j] == FileList1[flag])
{
len++;
flag++;
}
else
{
break;
}
}
}
if (i + len != flag) throw new Exception("err");
if (len > compareResult.mostLen)
{
compareResult.mostLen = len;
compareResult.file1.mostStart = i;
compareResult.file1.mostEnd = flag;
compareResult.file2.mostStart = start;
compareResult.file2.mostEnd = j;
}
i = flag;
}
}
compareResult.file1.SetFile(FileList1);
compareResult.file2.SetFile(FileList2);
return compareResult;
}
}
}