在 F# 中使用 MD5

Using MD5 in F#

我目前正在尝试学习 F#。这是我第一次使用 .NET 语言,因此我对可用的 API 非常陌生。

作为初学者项目,我想实现自己的重复文件查找器。我被建议使用校验和,因为我比较的文件非常大(大部分在 1MB 到 10MB 之间)。

到目前为止,这就是我所做的:检查文件长度后,我通过将所有字节读入字节数组来比较具有相同文件长度的文件。现在我想用MD5计算每个字节数组的哈希值,然后删除具有相同哈希值的重复文件。

我有一些问题:

  1. MD5 是否适合此任务?
  2. 如果不是,我应该改用什么算法?

感谢您的帮助。我可能 post 对你的回复提出后续问题。

编辑:

let readAllBytesMD5 (tupleOfFileLengthsAndFiles) =
    let md5 = MD5.Create()
    tupleOfFileLengthsAndFiles
    |> snd
    |> Seq.map (fun eachFile -> (File.ReadAllBytes eachFile, eachFile))
    |> Seq.groupBy fst
    |> Seq.map (fun (byteArray, eachFile) -> (md5.ComputeHash(byteArray), eachFile))

我想提取具有多个值(对应文件)的键(散列字节数组),并删除重复文件。我如何改进和继续上面的代码示例?我不熟悉 MD5 的工作原理,所以我被困在这里。如有任何建议,我们将不胜感激。

使用 MD5 执行此任务并没有什么特别的错误。但是,MD5 不再被视为 "strong" 散列;确定的各方创建具有不同内容但具有相同 MD5 哈希值的文件相对简单。

一个更强大的替代方案,我会推荐的是您使用 SHA-2 哈希之一,如 SHA256。

但是,性能说明:如果缓存文件的哈希值(并随着文件 added/deleted/modified 增量更新缓存),哈希只会提高工具的性能。如果不缓存hash,每次发现冲突时,需要读取两个文件的全部内容,计算它们的hash;如果此工具仅用于偶然的重复数据删除,则可能 faster/simpler 每当找到相同大小的文件时比较文件的内容。

编辑:这是您可以使用的一些示例代码。它会检测重复项,但您需要编写另一个函数来确定如何解决冲突(例如,您可能希望保留最早创建的文件)。

open System.IO
open System.Security.Cryptography

/// Given a sequence of filenames, looks for duplicate files by comparing file lengths
/// and, if necessary, hash values calculated using the specified hash algorithm.
/// Returns a sequence of tuples; the first item in the tuple is a hash value and the
/// second item is a sequence containing the names of two or more files which have
/// the same length and hash value.
let findDuplicateFiles (algorithm : HashAlgorithm) (filenames : seq<string>) =
    filenames
    |> Seq.groupBy (fun filename ->
        (FileInfo filename).Length)
    |> Seq.collect (fun (_, sameLengthFilenames) ->
        // If there's only one file with this length, there's no duplication so don't return it.
        if Seq.length sameLengthFilenames = 1 then Seq.empty
        else
            // Possible duplication. Resolve by hashing the files and comparing the hashes.
            sameLengthFilenames
            |> Seq.groupBy (fun filename ->
                using (File.OpenRead filename) algorithm.ComputeHash)
            // Check for multiple files with the same hash value.
            // Return any such filenames so outside code can determine how to handle them.
            |> Seq.filter (fun (_, sameLengthFilenames) ->
                // Collision when two or more files have the same hash.
                Seq.length sameLengthFilenames > 2))

/// Given a sequence of filenames, looks for duplicate files by comparing file lengths
/// and, if necessary, hash values calculated using the SHA256 algorithm.
/// Returns a sequence of tuples; the first item in the tuple is a hash value and the
/// second item is a sequence containing the names of two or more files which have
/// the same length and hash value.
let findDuplicateFilesSHA256 filenames =
    // NOTE: The algorithm should be bound with 'use' or 'using' here so it can be disposed,
    //       but the F# 3.1 compiler appears to dispose the object too early.
    findDuplicateFiles (SHA256.Create()) filenames

//
let printDuplicateEntry (hash : byte[], filenames : seq<string>) =
    stdout.WriteLine ""
    stdout.Write "Hash: "
    stdout.WriteLine (System.BitConverter.ToString(hash).Replace("-", ""))
    for filename in filenames do
        printfn "    %s (Length: %i)" filename ((FileInfo filename).Length)

//
let findDuplicateFilesInDirectory path =
    Directory.EnumerateFiles (path)
    |> findDuplicateFilesSHA256
    |> Seq.iter printDuplicateEntry
;;

// Example usage:
findDuplicateFilesInDirectory @"C:\Users\Jack\Desktop";;