對於相關的實現前人已經做得很好了,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;
}
}
}