2つのファイルやフォルダーを比較して相違点をみつけるファイルコンペアツールを作成します。これもフリーで使えるものがたくさんありますが、勉強用で作ってみました。
外観はこんな感じです。
ファイルサイズが大きい場合、時間がかかるので進行状態がわかるようにプログレスバーをつけました。上がファイルの個数、下がひとつのファイルのコンペアの進行状況を知るためのものです。
最初に比較元と比較先のパスを入力する必要がありますが、ドラッグ&ドロップでできるようにしました。
1 2 |
textBox1.AllowDrop = true; textBox2.AllowDrop = true; |
なにかがドラッグオーバーされたらファイルがどうか調べます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
public partial class Form1 : Form { private void textBox1_DragOver(object sender, DragEventArgs e) { if (e.Data.GetDataPresent(DataFormats.FileDrop)) { e.Effect = DragDropEffects.Copy; } } private void textBox2_DragOver(object sender, DragEventArgs e) { if (e.Data.GetDataPresent(DataFormats.FileDrop)) { e.Effect = DragDropEffects.Copy; } } } |
そしてドロップされたらファイルパスを取得してテキストボックスに表示させます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
public partial class Form1 : Form { private void textBox1_DragDrop(object sender, DragEventArgs e) { if (e.Data.GetDataPresent(DataFormats.FileDrop)) { string[] files = (string[])e.Data.GetData(DataFormats.FileDrop); textBox1.Text = files[0]; } } private void textBox2_DragDrop(object sender, DragEventArgs e) { if (e.Data.GetDataPresent(DataFormats.FileDrop)) { string[] files = (string[])e.Data.GetData(DataFormats.FileDrop); textBox2.Text = files[0]; } } } |
[比較]ボタンがクリックされたらコンペアを開始します。
1 2 3 4 5 6 7 |
public partial class Form1 : Form { private void button1_Click(object sender, EventArgs e) { Task.Run(() => { button1_Click(); }); } } |
テキストボックスに入力されている文字列は適切な内容でしょうか? ファイル名かフォルダ名か調べます。また入力内容が同じ場合はエラーを返します。
両方ともファイル、または両方ともフォルダの場合はコンペアを実行します。一方がフォルダでもう片方がファイルの場合は当然「両者の内容は異なる」という結果になります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
public partial class Form1 : Form { private void button1_Click() { string path1 = textBox1.Text; string path2 = textBox2.Text; if ((path1 == path2) || (!File.Exists(path1) && !Directory.Exists(path1)) || (!File.Exists(path2) && !Directory.Exists(path2))) { MessageBox.Show("有効なパスが設定されていません。"); return; } FileInfo info1 = new FileInfo(path1); bool isFolder1 = info1.Attributes.HasFlag(FileAttributes.Directory); FileInfo info2 = new FileInfo(path2); bool isFolder2 = info2.Attributes.HasFlag(FileAttributes.Directory); if (isFolder1 != isFolder2) { MessageBox.Show("一方がフォルダでもう片方がファイルです"); return; } if (!isFolder1) { // ファイル同士の比較 ShowFileConpareResult(path1, path2); } else { // フォルダ同士の比較 ShowFolderConpareResult(path1, path2); } } } |
ファイル同士の比較が簡単なので先にやってしまいましょう。
このメソッドが呼ばれるときは引数がファイルかフォルダのパスであることははっきりしています。ただファイルなのかフォルダなのかはわかりません。両方とも同じ名前のフォルダである場合は「同じ」と判断しています。
ファイルの場合はまずサイズを調べます。同じでないならふたつのファイルの内容は異なることがわかります。同じ場合は FileStream.ReadByte()で1バイトずつ読み取って同じ値かどうか調べています。いつまで繰り返すのかというと違いが見つかるか、ファイルの終端にたどり着くまでです。最後に取得された値を比較することでファイルの内容が同じかどうかがわかります。
ファイルサイズが大きい場合、時間がかかります。ユーザーとしてはどれくらい待てばいいかわかるようにプログレスバーと文字情報で進行状態を示します。ただ頻繁にこれを繰り返すと処理が遅くなるだけなので、
ファイルサイズが20MBより大きい場合で、1MBぶんの処理が終わったときだけ表示を変更させています。
違いがみつかったらどこが違うのかわかるようにしています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 |
public partial class Form1 : Form { bool FileConpare(string path1, string path2, out long index) { // path1,2が存在することはすでに確認されている // ファイルではなくフォルダかもしれない if (Directory.Exists(path1) && Directory.Exists(path2)) { index = 0; return true; } if (File.Exists(path1) && Directory.Exists(path2)) { index = 0; return false; } if (Directory.Exists(path1) && File.Exists(path2)) { index = 0; return false; } FileStream fs1 = new FileStream(path1, FileMode.Open); FileStream fs2 = new FileStream(path2, FileMode.Open); // ファイルサイズが異なれば内容も違う long len1 = fs1.Length; long len2 = fs1.Length; if (len1 != len2) { fs1.Close(); fs2.Close(); if (len1 > len2) index = len1; else index = len2; return false; } int file1byte; int file2byte; int count = 0; int bunbo = 1024 * 1024; int longsize = 1024 * 1024 * 20; if (len1 > longsize) { progressBar2.Value = 0; progressBar2.Maximum = (int)(len1 / bunbo + 1); labelFilePath.Text = path1; labelSize.Text = String.Format("{0:#,0}/{1:#,0}", 0, len1); } do { file1byte = fs1.ReadByte(); file2byte = fs2.ReadByte(); count++; if (len1 > longsize) { if (count % bunbo == 0) { progressBar2.Value = count / bunbo; labelSize.Text = String.Format("{0:#,0}/{1:#,0}", count, len1); } } } while ((file1byte == file2byte) && (file1byte != -1)); if (len1 > longsize) { progressBar2.Value = 0; labelFilePath.Text = ""; labelSize.Text = ""; } fs1.Close(); fs2.Close(); index = count; return ((file1byte - file2byte) == 0); } } |
フォルダ同士の比較はどうすればいいでしょうか?
まずフォルダ内のフォルダとファイルのパスを調べます。そしてそれぞれの結果からフォルダのパスを取り除きます。
フォルダ C:\aaaを調べたら C:\aaa\xxx\abc.txtがみつかり、D:\bbbを調べたら D:\bbb\xxx\abc.txtが見つかったときは、C:\aaaとD:\bbbを取り除くと\xxx\abc.txtが残ります。この場合、調査対象のフォルダ内には同じ名前のフォルダとファイルが存在することがわかります。
比較元のフォルダには存在するけれども比較先のフォルダに存在しない場合、これらを表示させます。
両方に存在するファイルがわかったら本当に内容が同じなのか比較します。
比較が終わったらリスト内に結果を格納して、処理が終わったら結果を表示させます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
public partial class Form1 : Form { void ShowFolderConpareResult(string path1, string path2) { FileInfo info1 = new FileInfo(path1); FileInfo info2 = new FileInfo(path2); // まずフォルダとファイルを列挙する string[] vs1 = GetFolderFiles(path1); string[] vs2 = GetFolderFiles(path2); string parent1 = info1.DirectoryName + "\\" + info1.Name; string parent2 = info2.DirectoryName + "\\" + info2.Name; string[] temp1 = vs1.Select(x => x.Replace(parent1, "")).ToArray(); string[] temp2 = vs2.Select(x => x.Replace(parent2, "")).ToArray(); // 1にはあるが2にないもの string[] not2s = temp1.Except(temp2).Select(x => parent2 + x).ToArray(); richTextBox1.Text = String.Format("{0}\n", not2s.Length) + String.Join("\n", not2s); // 両方にあるもの var boths = temp1.Intersect(temp2); List<string> paths1 = new List<string>(); List<string> paths2 = new List<string>(); progressBar1.Value = 0; int total = boths.Count(); progressBar1.Maximum = total; labelCount.Text = String.Format("{0} / {1}", 0, boths.Count()); foreach (var path in boths) { long size; bool ret = FileConpare(parent1 + path, parent2 + path, out size); if (ret) paths1.Add(parent2 + path); else paths2.Add(parent2 + path + String.Format(" ({0}byte以降)", size)); progressBar1.Value++; labelCount.Text = String.Format("{0} / {1}", progressBar1.Value, total); } richTextBox2.Text = String.Format("{0}\n", paths2.Count) + String.Join("\n", paths2.ToArray()); richTextBox3.Text = String.Format("{0}\n", paths1.Count) + String.Join("\n", paths1.ToArray()); progressBar1.Value = 0; labelCount.Text = ""; } } |
実際にこれまでに作成したコードとバックアップしているものを比較してみました。
最後にちょっとだけ自慢。
こいつと勝負したら勝った♪
高速ファイル比較ツール
決定版! 高速に2つのファイル(フォルダ,ドライブ単位も)のコンペア(比較),軽い使い勝手良い,PC1台に1つ便利
赤枠部分の表示で時間がかかっているものと思われます。
(ここのコメント欄は雑談でもよいのでしょうか?)
重複ファイル削除のプログラムを考えていて、自分の欲しい、あるいは知りたいコードを探していたのですが、ネットでC#では、なぜか全くヒットせず、またまたここにたどり着きました。(マイクロソフトのコードはヒットするがとりあえずパス)
今は気が向いたのでプログラミングしていますが、一旦熱が冷めたらもうやらないかもしれないいのですが、HDDタイプの私のPCではVS2022が全く動かないので、今からSSDのPCを買いに行きます。
ところで、管理人の物乞いリストって何でしょう。まあ、言葉からして管理人様が欲しいものってことですよね。
でも、どうやって届けるのでしょうか?
(私もお金ないのであれですが、一応聞いておきたいです)
管理人の物乞いリストはamazonほしい物リストです。
欲しいものリストから直接商品をカートに入れて購入する場合、注文した人でなく、リストを作成した人の元に商品届けることができます。
つまり「よかったら私にプレゼントをください」という文字通りの物乞いをしているわけです。
“bool FileConpare(string path1, string path2, out long index)”は何処から呼ばれるのでしょうか? ”ShowFileConpareResult(path1, path2); ” なら “private void button1_Click()”
の中にあるのですが?