想要有关这些文件的信息

时间:2021-04-28 22:57:46

标签: java file nio

我写了一个递归遍历目录的程序;也就是说,它有一个遍历目录中所有文件的方法;如果这些文件中的任何一个是(子)目录,它也会通过子目录的文件等。一旦我的程序确定一个文件不是一个目录,它就会为它创建一个 File 对象并从中获取某些信息,例如如何占用多少空间等

我想知道 NIO Path 对象是否能更快地完成这项工作;我不需要文件数据本身,我不需要打开文件。我需要名称、扩展名和长度。所以我开始编写一个新方法,它接受一个字符串并创建一个路径而不是一个文件。

我遇到了一组令我困惑的文件。在 c:\Users\AppData\Local\Microsoft\WindowsApps\ 中有 5 个这些文件;它们都是 EXE 文件,它们的长度都是 0。执行 DOS 目录列表,包括带有 /AL 限定符的目录列表,不会显示它们是连接点或符号链接。但是,如果我使用其中之一创建一个 Path 对象,然后尝试读取文件的基本属性:

    Path path = Paths.get(folderPath.toString(), fileName);
    BasicFileAttributes bfa = null;
    try {  bfa = Files.readAttributes(path, BasicFileAttributes.class); }
    catch (IOException ioe)
    {
      File testFile = new File(String.format("%s%s%s", folderPath.toString(), File.separator, fileName));
      System.out.println(String.format("Got %s even though IOException thrown: %s", testFile.getName(), ioe.getMessage()));
    }

我明白了:

C:\Users\ralph\AppData\Local\Microsoft\WindowsApps\GameBarElevatedFT_Alias.exe: The file cannot be accessed by the system.
  at sun.nio.fs.WindowsException.translateToIOException(WindowsException.java:86)
  at sun.nio.fs.WindowsException.rethrowAsIOException(WindowsException.java:97)
  at sun.nio.fs.WindowsException.rethrowAsIOException(WindowsException.java:102)
  at sun.nio.fs.WindowsFileAttributeViews$Basic.readAttributes(WindowsFileAttributeViews.java:53)
  at sun.nio.fs.WindowsFileAttributeViews$Basic.readAttributes(WindowsFileAttributeViews.java:38)
  at sun.nio.fs.WindowsFileSystemProvider.readAttributes(WindowsFileSystemProvider.java:193)
  at java.nio.file.Files.readAttributes(Files.java:1737)
  at rcutil.file.FolderWalker.walkTree(FolderWalker.java:80)
  at rcutil.file.FolderWalker.walkTree(FolderWalker.java:89)
  at rcutil.file.FolderWalker.walkTree(FolderWalker.java:89)
  at rcutil.file.FolderWalker.walkTree(FolderWalker.java:89)
  at rcutil.file.FolderWalker.walkFolder(FolderWalker.java:60)
  at rcutil.file.FolderWalker.main(FolderWalker.java:178)

(我将在下面提供完整代码)

我创建了一个带有 EXE 扩展名的 0 长度文件;它显示在目录列表中,但如果我执行 dir/al 则不会。 attrib 命令适用于我自己创建的测试文件,但不适用于这些文件。我的程序对这个文件没有问题。

那么这些动物是什么?如果我在结点和符号链接上执行 dir/al,它们通常会与 SYMLINK 和 JUNCTION 一起显示。我找到了关于硬链接的文档,它似乎类似于符号链接;似乎没有 DOS(或其他)命令在目录文件夹中显示这些。

有谁有更多的信息吗?我总是可以回到使用 java.io.File;不知道 java.nio.file.Path 是否会更快。但现在我很好奇这些野兽是什么......

package rcutil.file;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.BasicFileAttributes;

import rcutil.system.OperatingSystemUtil;

/**
 * Provides the walking of all files in a folder, recursively; instantiation requires
 * a parameter class implementing a callback to receive a method call for each file 
 * and directory encountered in the tree walk.
 * <P>The walkFolder() method in this class walks an entire directory subtree recursively,
 * making a call to the given callback class for each file and directory giving information
 * about the file or directory found. 
 * 
 * <P> TODO: convert this to the java.nio version, which I didn't know existed when
 * I wrote this.
 * @author rcook
 *
 */
public class FolderWalker
{
  FolderWalkerUser user;
  
  /**
   * Instantiate the folder walker to call back the given user for each file and directory encountered.
   * 
   * @param user
   */
  public FolderWalker(FolderWalkerUser user)
  {
    this.user = user;
  }
  
  /**
   * Start a treewalk at the given folder. Cannot be a non-directory file or a symbolic link.
   * @param givenFolderName
   * @throws Exception
   */
  public void walkFolder(String givenFolderName) throws Exception
  {
    try
    {
      if (isStringEmpty(givenFolderName)) { throwIOException((givenFolderName == null ? "Null" : "Empty") + " folderPath given to FolderWalker"); }
      
      Path path = Paths.get(givenFolderName);
      File topFile = path.toFile(); // new File(givenFolderName);
      
      if (!(topFile.exists()))  { throwIOException("File <" + givenFolderName + "> given to FolderWalker does not exist"); }
      
      BasicFileAttributes bfa = Files.readAttributes(path, BasicFileAttributes.class);
      if (!bfa.isDirectory()) { throwIllegalArgumentException("Folder path <" + givenFolderName + "> given to FolderWalker is not a directory"); }
      if (bfa.isSymbolicLink()) { throwIllegalArgumentException("Folder path <" + givenFolderName + "> given to FolderWalker is a symbolic link"); }

//      walkTree(topFile);
      walkTree(path);
      user.afterFolderWalk(givenFolderName);
      
    } catch (IOException e)
    {
      throw new IOException("Exception during FolderWalker.walkFolder() for <" + givenFolderName + ">", e);
    }
  }
  
  private void walkTree(Path folderPath) throws Exception
  {
    File folderFile = folderPath.toFile();
    user.foundFile(folderFile, true);
    String[] fileNameList = folderFile.list();
    if (fileNameList != null && fileNameList.length > 0)
    {
      for (String fileName : fileNameList)
      {
        Path path = Paths.get(folderPath.toString(), fileName);
        BasicFileAttributes bfa = null;
        try {  bfa = Files.readAttributes(path, BasicFileAttributes.class); }
        catch (IOException ioe)
        {
          File testFile = new File(String.format("%s%s%s", folderPath.toString(), File.separator, fileName));
          System.out.println(String.format("Got %s even though IOException thrown: %s", testFile.getName(), ioe.getMessage()));
//          ioe.printStackTrace();
        }
        if (bfa != null)
        {
          if (bfa.isDirectory()) { walkTree(path);               }
          else                   { user.foundFile(null, false);  }      
            // BIG TODO: passing null; if we switch to Paths, do we need to change the 
            // FolderWalkerUser interface to use paths?
            // trying to avoid File when possible, on theory that doing so saves runtime
        }
      }
    }
  }
  
  private void walkTree(File folder) throws Exception
  {
//    System.out.println("walkTree thread name: " + Thread.currentThread().getName());
    user.foundFile(folder, true);               // users are notified of folders at the top of their subtree 
    String[] fileNameList = folder.list();
    if (fileNameList != null && fileNameList.length > 0)
    {
      for (String fileName : fileNameList)
      {
        File currentFile = new File(folder, fileName);
//        if (isSymbolicLink(currentFile)) { System.out.printf("Symbolic link: %s%n"); }
        if (currentFile.isDirectory())
        {
          walkTree(currentFile);
        }
        else
        {
          user.foundFile(currentFile, false);     // user notified of non-folder files in sequence, after their containing folder
        }
      }
    }
  }
  
  private boolean isStringEmpty(String givenFolderName)
  {
    return (givenFolderName == null || givenFolderName.length() <= 0);
  }
  
  private void throwIOException(String msg) throws IOException
  {
    throw new IOException(msg);
  }

  private void throwIllegalArgumentException(String msg) throws IOException
  {
    throw new IllegalArgumentException(msg);
  }

  private boolean isSymbolicLink(File file) throws IOException
  {
    String absolutePath = file.getAbsolutePath();
    String canonicalPath = file.getCanonicalPath();
    if (OperatingSystemUtil.isWindows()) 
    {
      absolutePath = absolutePath.toLowerCase();
      canonicalPath = canonicalPath.toLowerCase();
    }
    return (!(absolutePath.equals(canonicalPath)));
  }
  
  /**
   * for testing purposes -- echo the folder tree as we walk it.
   * @param arguments
   */
  public static void main(String ... arguments)
  {
    String startFolderName = "C:\\Users\\ralph\\AppData";
    if (arguments != null && arguments.length > 0)
    {
      startFolderName = arguments[0];
    }
    
    System.out.println("Starting walk...");
    long startTime = System.currentTimeMillis();
    FolderWalker walker = new FolderWalker(new FolderWalkerUser() 
                                            {
                                              int fileCount, folderCount;
                                              public void foundFile(File file, boolean isDirectory)
                                              {
//                                                System.out.println(file.getAbsolutePath());
                                                fileCount++;
                                              }
                                              public void afterFolderWalk(String folderName)
                                              {
//                                                System.out.println("all done, folder name " + folderName);
                                                folderCount++;
                                              }
                                            }
                                          );
    try { walker.walkFolder(startFolderName); }
    catch (Exception ioe) { ioe.printStackTrace(); }
    long elapsedTime = System.currentTimeMillis() - startTime;
    System.out.println(String.format("Walk done, %5.2f seconds", elapsedTime/1000f));
    
  }
}

1 个答案:

答案 0 :(得分:2)

这些文件与 Windows 应用商店应用的别名有关。这些允许从命令行启动 Windows 应用商店程序。您可以在此处查看别名并打开/关闭别名:

   Settings > Apps > App execution aliases

任何编辑都会更改文件夹中报告的 EXE 数量:

dir c:\Users\{userid}\AppData\Local\Microsoft\WindowsApps\

尽管使用 Files.readAttributes 查询这些文件会返回异常,但您可以使用 Files.find 扫描它们,这会以正确的权限获得相同的信息。

这是一个示例 - 只需按上述方式传入您的目录,此查找将返回一个 Map.Entry<Path, BasicFileAttributes> 对流,以便您可以读取每个 EXE 的路径及其属性:

public static Stream<Map.Entry<Path, BasicFileAttributes>>
find(Path dir, int maxDepth, BiPredicate<Path, BasicFileAttributes> matcher, FileVisitOption... options) throws IOException {

    HashMap <Path,BasicFileAttributes> attrs = new HashMap<>();
    BiPredicate<Path, BasicFileAttributes> predicate = (p,a) -> (matcher == null || matcher.test(p, a)) && attrs.put(p, a) == null;

    return Files.find(dir, maxDepth, predicate, options).map(p -> Map.entry(p, attrs.remove(p)));
}

public static void main(String[] args) throws IOException {
    Path dir = Path.of(args[0]);

    // replace Integer.MAX_VALUE by 1 for current dir only:
    try(var x = find(dir, Integer.MAX_VALUE, (p,a)-> p.toString().toLowerCase().endsWith(".exe"))) {
        x.forEach(entry -> System.out.println(entry.getKey().toString()+" => "
                          +(entry.getValue().isDirectory() ? "DIR" : String.valueOf(entry.getValue().size()))));
    }
}

使用 Files.find/walk / NIO 进行目录扫描,对于更大的目录树,NIO 比重复的 File.list() 调用要快得多。

相关问题