java 测试两个目录树是否相等

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/14522239/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-31 16:32:33  来源:igfitidea点击:

Test two directory trees for equality

javaunit-testingjunit

提问by adarshr

I am working on integration testing some part of my code that creates directory trees under SVN. This requires me to test if the directory structure and the files within are what I expect them to be.

我正在对在 SVN 下创建目录树的代码的某些部分进行集成测试。这需要我测试目录结构和其中的文件是否符合我的预期。

On one hand I have the expected directory tree with the files I want and on the other, an export of the files from SVN (prefer svn exportover svn coto avoid the .svnnoise).

一方面我有我想要的文件,并在其他预期的目录树中的文件,从SVN出口(喜欢svn exportsvn co以避免.svn噪音)。

However, is there any library that can assert two directory trees? The last resort I have in mind is to do an iterative comparison myself.

但是,有没有可以断言两个目录树的库?我想到的最后一招是自己进行迭代比较。

Basically I am looking for an API that can just accept two directories and tell me if they are equal or not.

基本上,我正在寻找一个只能接受两个目录并告诉我它们是否相等的 API。

Something on the lines of

一些关于

boolean areDirectoriesEqual(File dir1, File dir2)

回答by Patrick

I am not using a third party lib but standard jdk lib.

我没有使用第三方库,而是使用标准的 jdk 库。

private static void verifyDirsAreEqual(Path one, Path other) throws IOException {
    Files.walkFileTree(one, new SimpleFileVisitor<Path>() {
        @Override
        public FileVisitResult visitFile(Path file,
                BasicFileAttributes attrs)
                throws IOException {
            FileVisitResult result = super.visitFile(file, attrs);

            // get the relative file name from path "one"
            Path relativize = one.relativize(file);
            // construct the path for the counterpart file in "other"
            Path fileInOther = other.resolve(relativize);
            log.debug("=== comparing: {} to {}", file, fileInOther);

            byte[] otherBytes = Files.readAllBytes(fileInOther);
            byte[] theseBytes = Files.readAllBytes(file);
            if (!Arrays.equals(otherBytes, theseBytes)) {
                throw new AssertionFailedError(file + " is not equal to " + fileInOther);
            }  
            return result;
        }
    });
}

Note: this is just comparing actual files under two folders. If you have empty folders etc you want to compare too, you may need to do some extra things.

注意:这只是比较两个文件夹下的实际文件。如果您有空文件夹等也想比较,则可能需要做一些额外的事情。

回答by Lety

I had same problem and following Patrick and Lorenzo Dematté I found a solution that works for me. The following code walks through folder and:

我遇到了同样的问题,跟随 Patrick 和 Lorenzo Dematté,我找到了一个适合我的解决方案。以下代码遍历文件夹和:

  • for each sub-folder checks if file lists are the same
  • for each file compares the contents (in my case I have to compare two folders that contain csv files)
  • 对于每个子文件夹检查文件列表是否相同
  • 对于每个文件比较内容(在我的情况下,我必须比较两个包含 csv 文件的文件夹)

I tested it on linux.

我在linux上测试过。

  private static void verifyDirsAreEqual(File expected, File generated) 
                throws IOException {

    // Checks parameters 
    assertTrue("Generated Folder doesn't exist: " + generated,generated.exists());
    assertTrue("Generated is not a folder?!?!: " + generated,generated.isDirectory());

    assertTrue("Expected Folder doesn't exist: " + expected,expected.exists());
    assertTrue("Expected is not a folder?!?!: " + expected,expected.isDirectory());     

    Files.walkFileTree(expected.toPath(), new SimpleFileVisitor<Path>() {
        @Override
        public FileVisitResult preVisitDirectory(Path dir,
                BasicFileAttributes attrs)
                  throws IOException {
            FileVisitResult result = super.preVisitDirectory(dir, attrs);

            // get the relative file name from path "expected"
            Path relativize = expected.toPath().relativize(dir);
            // construct the path for the counterpart file in "generated"
            File otherDir = generated.toPath().resolve(relativize).toFile();
            log.debug("=== preVisitDirectory === compare " + dir + " to " + otherDir);
            assertEquals("Folders doesn't contain same file!?!?",
                    Arrays.toString(dir.toFile().list()),
                    Arrays.toString(otherDir.list()));
            return result;
        }
        @Override
        public FileVisitResult visitFile(Path file,
                BasicFileAttributes attrs)
                throws IOException {
            FileVisitResult result = super.visitFile(file, attrs);

            // get the relative file name from path "expected"
            Path relativize = expected.toPath().relativize(file);
            // construct the path for the counterpart file in "generated"
            File fileInOther = generated.toPath().resolve(relativize).toFile();
            log.debug("=== comparing: " + file + " to " + fileInOther);
            String expectedContents = FileUtils.readFileToString(file.toFile());
            String generatedContents = FileUtils.readFileToString(fileInOther);
            assertEquals("("+fileInOther+")  csv standard doesn't match expected ("+file+")!", expectedContents, generatedContents);                    
            return result;
        }
    });
}

回答by Lorenzo Dematté

I am not aware of any areDirsEquallibrary; the closest I can think of is the listFiles method in Commons FileUtils.

我不知道有任何areDirsEqual图书馆;我能想到的最接近的是 Commons 中的 listFiles 方法FileUtils

If you put the resulting collections in a HashSet, you should be able to compare the two sets efficiently. And it can be done in 2 lines, maybe even a one-liner.

如果将结果集合放在 a 中HashSet,您应该能够有效地比较这两个集合。它可以在 2 行中完成,甚至可以是单行。

Something on this line:

这条线上的东西:

public static boolean areDirsEqual(File dir, File dir2) {
  return (new HashSet<File>(FileUtils.listFiles(dir1,..))).
          containsAll(FileUtils.listFiles(dir2, ..))
}

回答by Moataz Elmasry

OK so I don't know of any ready piece of code that does that and search didn't help either. So here is how I would implement it

好的,所以我不知道有任何现成的代码可以做到这一点,搜索也无济于事。所以这是我将如何实现它

  1. iterate recursively on all folders and files
  2. save all file names with relative path from root in a hashset, where the relative path is the key/value
  3. iterate recursively on the second directory structure and create a from each path to match the keys in the hashet (if the folder/file exists)
  1. 递归迭代所有文件夹和文件
  2. 将所有文件名从 root 的相对路径保存在一个哈希集中,其中相对路径是键/值
  3. 在第二个目录结构上递归迭代并从每个路径创建一个以匹配哈希中的键(如果文件夹/文件存在)

If you just want to mark the tree as changed/not changed, you can save the hash of each file, then you need a hashmap instead of hashset, where the hash of the contents of each file is the value of the hashmap

如果只想将树标记为已更改/未更改,则可以保存每个文件的哈希值,那么您需要一个哈希图而不是哈希集,其中每个文件的内容的哈希值就是哈希图的值

hope this helps

希望这可以帮助

回答by jfx

This is a simple iterative solution using the Java NIO package (without using the Visitor pattern, so it can be adapted for earlier Java versions as well).

这是一个使用 Java NIO 包的简单迭代解决方案(不使用访问者模式,因此它也可以适用于早期的 Java 版本)。

Of course it could be tuned, but for now this is an easy solution checking from the view of both directories if every file occurs and optionally compare the file content using the Apache Commons FileUtils.

当然它可以被调整,但现在这是一个简单的解决方案,从两个目录的角度检查是否每个文件都出现,并有选择地使用 Apache Commons FileUtils 比较文件内容。

/**
 * checks if the directory file lists and file content is equal
 * 
 * @param directory
 *            the directory
 * @param compareDirectory
 *            the directory to compare with
 * @param checkFileContent
 *            also compare file content
 * @return true if directory and compareDirectory are equal
 * @throws IOException
 */
public static boolean isEqualDirectories(Path directory, Path compareDirectory, boolean checkFileContent) throws IOException {
    boolean check = isEverythingInCompareDirectory(directory, compareDirectory, checkFileContent);
    boolean checkOpposite = check && isEverythingInCompareDirectory(directory, compareDirectory, checkFileContent);
    return check && checkOpposite;

}

/**
 * checks if the directory file lists and file content is equal
 * 
 * @param directory
 *            the directory
 * @param compareDirectory
 *            the directory to compare with
 * @param checkFileContent
 *            also compare file content
 * @return true if directory and compareDirectory are equal
 * @throws IOException
 */
public static boolean isEverythingInCompareDirectory(Path directory, Path compareDirectory, boolean checkFileContent)
        throws IOException {

    try {
        LOGGER.info("checking directory " + directory);

        File directoryFile = directory.toFile();
        File compareFile = compareDirectory.toFile();

        // check, if there is the same number of files/subdirectories
        File[] directoryFiles = directoryFile.listFiles();
        File[] compareFiles = compareFile.listFiles();

        if (directoryFiles.length == compareFiles.length) {
            return compareDirectoryContents(directory, compareDirectory, checkFileContent);

        } else {
            LOGGER.info("number of files in directory are different " + directoryFiles.length + " vs compareDirectory: " + compareFiles.length);
            return false;
        }

    } catch (IOException e) {
        throw new RuntimeException("Failed to assert that all files are equal", e);
    }
}

public static boolean compareDirectoryContents(Path directory, Path compareDirectory, boolean checkFileContent) throws IOException {
    try (DirectoryStream<Path> directoryStream = Files.newDirectoryStream(directory)) {

        for (Path directoryFilePath : directoryStream) {

            // search for directoryFile in the compareDirectory
            Path compareFilePath = compareDirectory.resolve(directoryFilePath.getFileName());

            if (compareFilePath != null) {

                File directoryFile = directoryFilePath.toFile();
                if (directoryFile.isFile()) {
                    LOGGER.info("checking file " + directoryFilePath);
                    if (checkFileContent && !FileUtils.contentEquals(compareFilePath.toFile(), directoryFile)) {
                        LOGGER.info("files not equal: compare: " + compareFilePath.toFile() + ", directory: " + directoryFilePath.getFileName() + "!");
                        return false;
                    }

                } else {
                    LOGGER.info("going into recursion with directory " + directoryFilePath);
                    boolean result = isEverythingInCompareDirectory(directoryFilePath, compareFilePath, checkFileContent);
                    // cancel if not equal, otherwise continue processing
                    if (!result) {
                        return false;
                    }

                }
            } else {
                LOGGER.info(directoryFilePath.toString() + ": compareFilepath not found");
                return false;
            }

        }
    }

    return true;
}

回答by Lorenzo Dimitri Bono

for me the solution of Patrick seems to be a good solution, but in combination with camel (Fuse ESB) I had the problem that the last parent folder was still blocked by Fuse process => for me the follow solutions semms to be better way. I iterate over ther directories by SimpleVistor and made a comparable Set of booth directories

对我来说,Patrick 的解决方案似乎是一个很好的解决方案,但是与骆驼(Fuse ESB)结合使用时,我遇到了最后一个父文件夹仍然被 Fuse 进程阻塞的问题 => 对我而言,以下解决方案似乎是更好的方法。我通过 SimpleVistor 遍历这些目录并制作了一组可比较的展位目录

public boolean compareFolders(final Path pathOne, final Path pathSecond) throws IOException {

    // get content of first directory
    final TreeSet<String> treeOne = new TreeSet();
    Files.walkFileTree(pathOne, new SimpleFileVisitor<Path>() {
        @Override
        public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
            Path relPath = pathOne.relativize(file);
            String entry = relPath.toString();
            treeOne.add(entry);
            return FileVisitResult.CONTINUE;
        }
    });

    // get content of second directory
    final TreeSet<String> treeSecond = new TreeSet();
    Files.walkFileTree(pathSecond, new SimpleFileVisitor<Path>() {
        @Override
        public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
            Path relPath = pathSecond.relativize(file);
            String entry = relPath.toString();
            treeSecond.add(entry);
            return FileVisitResult.CONTINUE;
        }
    });
    return treeOne.equals(treeSecond);
}

回答by le Mandarin

I know this is an old question but I will try to solve it. Here is a code that opens 2 directories and compares their contents. It returns false if the files found in one are missing in the other or if their contents are distinct. To do this, I browse the content of the 2 directories and I recursively compare the files and directories found (in alphabetical order).

我知道这是一个老问题,但我会尝试解决它。这是一个打开 2 个目录并比较它们内容的代码。如果在一个文件中找到的文件在另一个文件中丢失或者它们的内容不同,则返回 false。为此,我浏览了 2 个目录的内容,并递归地比较找到的文件和目录(按字母顺序)。

// Comparison of 2 files
private static boolean compareFiles(File f1, File f2) {
    long i1 = f1.length();
    long i2 = f2.length();
    if (i1 != i2) {
        return false;
    }
    try {
        byte b1, b2;
        DataInputStream dis = new DataInputStream(
                new BufferedInputStream(
                        new FileInputStream(f1)));
        DataInputStream dis1 = new DataInputStream(
                new BufferedInputStream(
                        new FileInputStream(f2)));
        while (true) {
            b1 = dis.readByte();
            b2 = dis1.readByte();
            if (b1 != b2) {
                return false;
            }
        }
    } catch (IOException ex) {
        return true;
    }
}


// Recursive comparison of 2 files.
// The idea is that if 2 directories are the same, 
// they will have the same files and the same under directories
private static boolean areDirsEqual(File dir1, File dir2) {
    File[] list1 = dir1.listFiles(), list2 = dir2.listFiles();
    if (list1.length != list2.length) {
        return false;
    }
    for (int i = 0; i < list2.length; i++) {
        if (list1[i].isFile() && list2[i].isFile()) {
            if (!compareFiles(list1[i], list2[i])) {
                return false;
            }
        } else if (list1[i].isDirectory() && list2[i].isDirectory()) {
            if (!areDirsEqual(list1[i], list2[i])) {
                return false;
            }
        } else {
            return false;
        }
    }
    return true;
}

回答by Rob Yart

I wrote this small code in Kotlin. It does not check the contents of the files, but relies completely on md5 from apache.

我用 Kotlin 写了这个小代码。它不检查文件的内容,而是完全依赖 apache 的 md5。

import org.apache.commons.codec.digest.DigestUtils

fun File.calcMD5() = DigestUtils.md5Hex(FileUtils.readFileToByteArray(this))

fun compareTwoDirs(dir1: File, dir2: File): Boolean {
    val files1 = dir1.listFiles().sorted()
    val files2 = dir2.listFiles().sorted()
    if (files1.size != files2.size) return false
    return files1.zip(files2).all { equate(it.first, it.second) }
}

fun equate(fl: File, fl2: File): Boolean {
    if (fl.isFile && fl2.isFile) return fl.calcMD5() == fl2.calcMD5()
    if (fl.isDirectory && fl2.isDirectory) return compareTwoDirs(fl, fl2)
    return false
}

回答by Aakash Anuj

import java.io.File;

/**
 * 
 * FileUtils is a collection of routines for common file system operations.
 * 
 * @author Dan Jemiolo (danj)
 * 
 */

public final class FileUtils {

  /**
   * 
   * This is a convenience method that calls find(File, String, boolean) with
   * the last parameter set to "false" (does not match directories).
   * 
   * @see #find(File, String, boolean)
   * 
   */
  public static File find(File contextRoot, String fileName) {
    return find(contextRoot, fileName, false);
  }

  /**
   * 
   * Searches through the directory tree under the given context directory and
   * finds the first file that matches the file name. If the third parameter is
   * true, the method will also try to match directories, not just "regular"
   * files.
   * 
   * @param contextRoot
   *          The directory to start the search from.
   * 
   * @param fileName
   *          The name of the file (or directory) to search for.
   * 
   * @param matchDirectories
   *          True if the method should try and match the name against directory
   *          names, not just file names.
   * 
   * @return The java.io.File representing the <em>first</em> file or
   *         directory with the given name, or null if it was not found.
   * 
   */
  public static File find(File contextRoot, String fileName, boolean matchDirectories) {
    if (contextRoot == null)
      throw new NullPointerException("NullContextRoot");

    if (fileName == null)
      throw new NullPointerException("NullFileName");

    if (!contextRoot.isDirectory()) {
      Object[] filler = { contextRoot.getAbsolutePath() };
      String message = "NotDirectory";
      throw new IllegalArgumentException(message);
    }

    File[] files = contextRoot.listFiles();

    //
    // for all children of the current directory...
    //
    for (int n = 0; n < files.length; ++n) {
      String nextName = files[n].getName();

      //
      // if we find a directory, there are two possibilities:
      //
      // 1. the names match, AND we are told to match directories.
      // in this case we're done
      //
      // 2. not told to match directories, so recurse
      //
      if (files[n].isDirectory()) {
        if (nextName.equals(fileName) && matchDirectories)
          return files[n];

        File match = find(files[n], fileName);

        if (match != null)
          return match;
      }

      //
      // in the case of regular files, just check the names
      //
      else if (nextName.equals(fileName))
        return files[n];
    }

    return null;
  }

}