Title: A simple File Comparison Utility
Question: Sometimes you are only interested in knowing if two files are the same- you might have hit return a few times in the editor so one looks bigger than the other and has a later save date but it might be the same otherwise...
Answer:
The utility listed below (both .pas and .dfm source) accepts as input two file names. For convenience these filenames (with associated paths) are saved out between runs and you can copy the filename from the first box to the second (click the red down arrow) - it combines the first filename with the existing 2nd path. Both edit boxes allow you to browse for files.
File comparison is simple and fast. Each file is read into a memory stream and then a count of each of the 256 possible characters is made. You could argue that by cutting and moving text elsewhere in a text file file that this would break my method (as char counts would be unaffected) and you'd be right but I think for most purposes this method is probably sufficient and it works with binary as well as text files. I realise a CRC calculation could also be added- feel free to do so.
When there are differences the output is a string showing each character value (0-255) followed by the count in brackets.
For something thrown together quickly in an hour or so, it has served me well and compares files of a few megabytes pretty quickly.
Pascal Source
unit viewdiff;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, Buttons;
type
TDBDiff = class
Ffilename1 : string;
FfileName2 : string;
FBuff1 : TmemoryStream;
FBuff2 : TmemoryStream;
FCounts1 : array[0..255] of integer;
FCounts2 : array[0..255] of integer;
FProcessed : boolean;
fDifferenceStr : string;
FDifferent: boolean;
private
function GetDiffCount(ch: char): integer;
function GetDifferences: boolean;
procedure Clear;
procedure BuildDiffTable(Mem1,Mem2 : pointer;size1,size2:integer);
procedure BuildDifferenceStr;
function CheckIfSame: boolean;
public
Constructor Create;
Destructor Destroy;override;
property Different : boolean read GetDifferences;
property DifferenceStr : string read fDifferenceStr;
property DiffCount[ch : char] : integer read GetDiffCount;
property Filename1 : string read FFilename1 write FFilename1;
property Filename2 : string read FFilename2 write FFilename2;
end; // TdbDiff
TForm1 = class(TForm)
Button1: TButton;
Button2: TButton;
fileopen: TOpenDialog;
Edit1: TEdit;
Edit2: TEdit;
GoBtn: TButton;
btnCopyDown: TBitBtn;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure GoBtnClick(Sender: TObject);
procedure FormActivate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure btnCopyDownClick(Sender: TObject);
private
procedure CheckGoBtn;
procedure LoadEditBoxes;
procedure SaveEditBoxes;
{ Private declarations }
public
{ Public declarations }
Aftercreate : boolean;
Diff : tDbDiff;
StartPath : string;
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
Const
editsavefilename = 'diff.ini';
CrLf = #13#10;
procedure TForm1.Button1Click(Sender: TObject);
begin
if Edit1.Text '' then
FileOpen.Initialdir := ExtractFileDir(Edit1.Text);
if FileOpen.execute then
edit1.Text := FileOpen.Filename;
CheckGobtn;
end;
procedure Tform1.checkGoBtn;
var filename :string;
begin
Gobtn.enabled := false;
Filename := trim(Edit1.Text);
if (Filename '') and fileexists(Filename) then
begin
Filename := trim(Edit2.Text);
if (Filename '') and fileexists(Filename) then
GoBtn.Enabled := True;
end;
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
if Edit2.Text '' then
FileOpen.Initialdir := ExtractFileDir(Edit2.Text);
if FileOpen.execute then
edit2.Text := FileOpen.Filename;
CheckGoBtn;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
Aftercreate := true;
end;
{ TDBDiff }
procedure TdbDiff.Clear;
begin
fFilename1 := '';
fFilename2 := '';
fillchar(fCounts1,sizeof(fcounts1),0);
fillchar(fCounts2,sizeof(fcounts2),0);
fDifferenceStr := '';
fProcessed := false;
end;
constructor TDBDiff.Create;
begin
fBuff1 := TmemoryStream.Create;
fBuff2 := TmemoryStream.Create;
Clear;
end;
destructor TDBDiff.Destroy;
begin
FBuff2.Free;
FBuff1.Free;
end;
function TDBDiff.GetDiffCount(ch: char): integer;
begin
result := fCounts1[ord(ch)]-Fcounts2[ord(ch)];
end;
procedure TdbDiff.BuildDifferenceStr;
var Index : integer;
begin
fDifferenceStr := '';
for Index := 0 to 255 do
if fcounts1[Index] fCounts2[Index] then
begin
fDifferenceStr := fDifferencestr +
' #'+inttostr(Index)+'('+inttostr(fcounts1[Index]-Fcounts2[Index])+')';
end;
end;
function TDBDiff.CheckIfSame: boolean;
var Index : integer;
begin
result := true;
for Index := 0 to 255 do
if fcounts1[Index]fcounts2[Index] then
begin
Result := false;
exit;
end;
end;
procedure TDBDiff.BuildDiffTable(mem1,mem2 : pointer;size1,size2:integer);
type Bytemap = array[0..2000000000] of byte;
BytemapPtr = ^ByteMap;
var MapPtr : ByteMapPtr;
Index : integer;
begin
MapPtr := ByteMapPtr(mem1);
for Index := 0 to size1-1 do
inc(fcounts1[MapPtr^[Index]]);
MapPtr := ByteMapPtr(mem2);
for Index := 0 to size2-1 do
inc(fcounts2[MapPtr^[Index]]);
end;
function TDBDiff.GetDifferences: boolean;
var fs : TFileStream;
begin
if fProcessed then
Result := Fdifferent
else
begin
Result := false;
if (trim(Ffilename1) ='') or (trim(FFilename2) = '') then
exit;
fProcessed := true;
fs := TfileStream.Create(fFilename1,fmOpenRead);
fbuff1.LoadFromStream(fs);
fs.free;
fs := TfileStream.Create(fFilename2,fmOpenRead);
fbuff2.LoadFromStream(fs);
fs.free;
BuildDiffTable(fbuff1.memory,fbuff2.memory,fbuff1.size,fbuff2.size);
BuildDifferenceStr;
Result := not CheckIfSame;
end;
end;
procedure TForm1.GoBtnClick(Sender: TObject);
begin
diff.Clear;
diff.Filename1 := edit1.text;
diff.Filename2 := edit2.text;
if diff.Different then
ShowMessage(
'Differences between '+ Crlf +
diff.Filename1 + Crlf +
diff.Filename2 + Crlf + Crlf +
diff.DifferenceStr)
else
ShowMessage('Files identical');
end;
procedure TForm1.FormActivate(Sender: TObject);
begin
if AfterCreate then
begin
AFterCreate := false;
diff := tdbdiff.Create;
GetDir(0,StartPath);
if StartPath[Length(StartPath)] '\' then
StartPath := StartPath + '\';
LoadEditBoxes;
end;
end;
procedure Tform1.LoadEditBoxes;
var tf : textfile;
s : string;
begin
if fileexists(StartPath+EditSaveFilename) then
begin
assignfile(tf,StartPath+EditSavefilename);
reset(tf);
try
readln(tf,s);
edit1.text := s;
readln(tf,s);
edit2.text := s;
finally
Closefile(Tf);
CheckGoBtn;
end;
end;
end;
procedure Tform1.SaveEditBoxes;
var tf : textfile;
s : string;
begin
assignfile(tf,StartPath+EditSavefilename);
rewrite(tf);
try
s := edit1.text;
writeln(tf,s);
s := edit2.text;
writeln(tf,s);
finally
Closefile(Tf);
end;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
SaveEditBoxes;
end;
procedure TForm1.btnCopyDownClick(Sender: TObject);
begin
edit2.text := ExtractFileDir(Edit2.Text)+ '\' +
ExtractFileName(Edit1.Text);
end;
end.
.DFM Source
object Form1: TForm1
Left = 338
Top = 555
Width = 462
Height = 172
Caption = 'Difference Utility'
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'MS Sans Serif'
Font.Style = []
OldCreateOrder = False
OnActivate = FormActivate
OnCreate = FormCreate
OnDestroy = FormDestroy
PixelsPerInch = 96
TextHeight = 13
object Button1: TButton
Left = 12
Top = 30
Width = 75
Height = 25
Caption = '1st File'
TabOrder = 0
OnClick = Button1Click
end
object Button2: TButton
Left = 12
Top = 78
Width = 75
Height = 25
Caption = '2nd File'
TabOrder = 1
OnClick = Button2Click
end
object Edit1: TEdit
Left = 96
Top = 30
Width = 343
Height = 21
Anchors = [akLeft, akTop, akRight]
TabOrder = 2
end
object Edit2: TEdit
Left = 96
Top = 78
Width = 343
Height = 21
Anchors = [akLeft, akTop, akRight]
TabOrder = 3
end
object GoBtn: TButton
Left = 96
Top = 114
Width = 75
Height = 25
Caption = 'Compare'
Enabled = False
TabOrder = 4
OnClick = GoBtnClick
end
object btnCopyDown: TBitBtn
Left = 240
Top = 54
Width = 26
Height = 23
TabOrder = 5
OnClick = btnCopyDownClick
Glyph.Data = {
76010000424D7601000000000000760000002800000020000000100000000100
0400000000000001000000000000000000001000000010000000000000000000
800000800000008080008000000080008000808000007F7F7F00BFBFBF000000
FF0000FF000000FFFF00FF000000FF00FF00FFFF0000FFFFFF00333333303333
333333333337F33333333333333033333333333333373F333333333333090333
33333333337F7F33333333333309033333333333337373F33333333330999033
3333333337F337F33333333330999033333333333733373F3333333309999903
333333337F33337F33333333099999033333333373333373F333333099999990
33333337FFFF3FF7F33333300009000033333337777F77773333333333090333
33333333337F7F33333333333309033333333333337F7F333333333333090333
33333333337F7F33333333333309033333333333337F7F333333333333090333
33333333337F7F33333333333300033333333333337773333333}
NumGlyphs = 2
end
object fileopen: TOpenDialog
DefaultExt = '*'
Filter = 'Any File|*.*'
Left = 354
end
end