代码之家  ›  专栏  ›  技术社区  ›  Michael

如何检查一条路径是否是另一条路径的子路径?

  •  -1
  • Michael  · 技术社区  · 4 年前

    我试图用java找出给定的路径是否可能是另一条路径的子路径。这两条路可能都不存在。

    c:\Program Files\My Company\test\My App 是一个可能的孩子 c:\Program Files .

    目前,我正在与

    boolean myCheck(File maybeChild, File possibleParent)
    {
        return maybeChild.getAbsolutePath().startsWith( possibleParent.getAbsolutePath());
    }
    
    0 回复  |  直到 9 年前
        1
  •  73
  •   xlm santhosh reddy    3 年前

    您还可以使用 java.nio.file.Path 更容易做到这一点。这个 java.nio.file.Path.startsWith 这种方法似乎能处理所有可能的情况。

    例子:

    private static void isChild(Path child, String parentText) {
        Path parent = Paths.get(parentText).toAbsolutePath();
        System.out.println(parentText + " = " + child.startsWith(parent));
    }
    
    public static void main(String[] args) {
        Path child = Paths.get("/FolderA/FolderB/File").toAbsolutePath();
        isChild(child, "/FolderA/FolderB/File");
        isChild(child, "/FolderA/FolderB/F");
        isChild(child, "/FolderA/FolderB");
        isChild(child, "/FolderA/Folder");
        isChild(child, "/FolderA");
        isChild(child, "/Folder");
        isChild(child, "/");
        isChild(child, "");
    }
    

    输出:

    /FolderA/FolderB/File = true
    /FolderA/FolderB/F = false
    /FolderA/FolderB = true
    /FolderA/Folder = false
    /FolderA = true
    /Folder = false
    / = true
     = false
    

    如果您需要更高的可靠性,可以使用 toRealPath 而不是 toAbsolutePath .

        2
  •  14
  •   NateS user1274193    7 年前
    File parent = maybeChild.getParentFile();
    while ( parent != null ) {
      if ( parent.equals( possibleParent ) )
        return true;
      parent = parent.getParentFile();
    }
    return false;
    
        3
  •  11
  •   Andrzej Doyle    14 年前

    除了路径可能不存在(正典化可能不会成功)的事实外,这似乎是一种合理的方法,应该适用于直截了当的情况。

    你可能想考虑打电话 getParentFile() 在循环中的“maybe child”上,测试它在每一步是否与父级匹配。如果父目录不是(真实的)目录,您也可以缩短比较。

    也许像下面这样:

    boolean myCheck(File maybeChild, File possibleParent) throws IOException
    {
        final File parent = possibleParent.getCanonicalFile();
        if (!parent.exists() || !parent.isDirectory()) {
            // this cannot possibly be the parent
            return false;
        }
    
        File child = maybeChild.getCanonicalFile();
        while (child != null) {
            if (child.equals(parent)) {
                return true;
            }
            child = child.getParentFile();
        }
        // No match found, and we've hit the root directory
        return false;
    }
    

    请注意,如果你希望孩子的关系是 严格的 (即目录不是其子目录)您可以更改初始 child 第9行的作业为 child.getParentFile() 因此,第一次检查发生在子目录的包含目录上。

        4
  •  8
  •   finnw    12 年前

    这将适用于你的例子。它也会回来 true 如果孩子是相对路径 (这通常是可取的。)

    boolean myCheck(File maybeChild, File possibleParent)
    {
        URI parentURI = possibleParent.toURI();
        URI childURI = maybeChild.toURI();
        return !parentURI.relativize(childURI).isAbsolute();
    }
    
        5
  •  4
  •   skaffman    14 年前

    这可能会很好地工作,尽管我会使用 getCanonicalPath() 而不是 getAbsolutePath() 。这应该会使任何奇怪的路径正常化,比如 x/../y/z 否则会搞砸匹配。

        6
  •  4
  •   Hosam Aly    7 年前
    maybeChild.getCanonicalPath().startsWith( possibleParent.getCanonicalPath() );
    
        7
  •  1
  •   Ľubomír Varga    9 年前

    注意相对路径!我认为最简单的解决方案是这样的:

    public boolean myCheck(File maybeChild, File possibleParent) {
      if (requestedFile.isAbsolute) {
        return possibleParent.resolve(maybeChild).normalize().toAbsolutePath.startsWith(possibleParent.normalize().toAbsolutePath)
      } else {
        return maybeChild.normalize().toAbsolutePath.startsWith(possibleParent.normalize().toAbsolutePath)
      }
    }
    

    在scala中,你可以有类似的方法:

    val baseDir = Paths.get("/home/luvar/tmp")
    val baseDirF = baseDir.toFile
    //val requestedFile = Paths.get("file1")
    val requestedFile = Paths.get("../.viminfo")
    val fileToBeRead = if (requestedFile.isAbsolute) {
      requestedFile
    } else {
      baseDir.resolve(requestedFile)
    }
    fileToBeRead.toAbsolutePath
    baseDir.toAbsolutePath
    fileToBeRead.normalize()
    baseDir.normalize()
    val isSubpath = fileToBeRead.normalize().toAbsolutePath.startsWith(baseDir.normalize().toAbsolutePath)
    
        8
  •  1
  •   Bass    3 年前

    在测试路径是否相等时,应考虑以下因素:

    1. 文件系统的大小写敏感性。唯一可以处理区分大小写的文件系统的API是 NIO.2 (1.7+),这就是为什么两者都没有 java.io.File 也没有 String 可以使用。
    2. 个人路径输入处理: C:\abc 不是其祖先,甚至不是其直系父母 C:\abcd ,因此 String.startsWith() 无法使用API。
    3. On 窗户 , C:\Program Files 与目录相同 C:\PROGRA~1 ,以及 Files.isSameFile() (从 NIO.2 )是唯一可以处理此权限的API。这就是 Path.startsWith() 这种方法不支持。
    4. Symlink友好性(由于实际要求可能不同,我的答案没有完全涵盖)。对于目录符号链接, Files.isSameFile() 支持此 在某种程度上 ,所以 C:\Documents and Settings 确实是一位祖先 属于 C:\Users\Public 同样,这也是自定义代码比 Path.startsWith() API(参见 this most-voted answer ).

    综上所述,解决方案可能是这样的。Java

      boolean isAncestorOf(final Path parent, final Path child) {
        final Path absoluteParent = parent.toAbsolutePath().normalize();
        final Path absoluteChild = child.toAbsolutePath().normalize();
    
        if (absoluteParent.getNameCount() >= absoluteChild.getNameCount()) {
          return false;
        }
    
        final Path immediateParent = absoluteChild.getParent();
        if (immediateParent == null) {
          return false;
        }
    
        return isSameFileAs(absoluteParent, immediateParent) || isAncestorOf(absoluteParent, immediateParent);
      }
    
      boolean isSameFileAs(final Path path, final Path path2) {
        try {
          return Files.isSameFile(path, path2);
        }
        catch (final IOException ioe) {
          return path.toAbsolutePath().normalize().equals(path2.toAbsolutePath().normalize());
        }
      }
    

    Kotlin:

    fun Path.isAncestorOf(child: Path): Boolean {
      val absoluteParent = toAbsolutePath().normalize()
      val absoluteChild = child.toAbsolutePath().normalize()
    
      if (absoluteParent.nameCount >= absoluteChild.nameCount) {
        return false
      }
    
      val immediateParent = absoluteChild.parent
                            ?: return false
    
      return absoluteParent.isSameFileAs(immediateParent) || absoluteParent.isAncestorOf(immediateParent)
    }
    
    fun Path.isSameFileAs(that: Path): Boolean =
      try {
        Files.isSameFile(this, that)
      }
      catch (_: NoSuchFileException) {
        toAbsolutePath().normalize() == that.toAbsolutePath().normalize()
      }
    
        9
  •  0
  •   Mattias Isegran Bergander    7 年前

    旧问题,但1.7之前的解决方案:

    public boolean startsWith(String possibleRoot, String possibleChildOrSame) {
            String[] possiblePath = new File(possibleRoot).getAbsolutePath().replace('\\', '/').split("/");
            String[] possibleChildOrSamePath = new File(possibleChildOrSame).getAbsolutePath().replace('\\', '/').split("/");
    
            if (possibleChildOrSamePath.length < possiblePath.length) {
                return false;
            }
    
            // not ignoring case
            for (int i = 0; i < possiblePath.length; i++) {
                if (!possiblePath[i].equals(possibleChildOrSamePath[i])) {
                    return false;
                }
            }
            return true;
    }
    

    为了完整起见,java 1.7+解决方案:

    public boolean startsWith(String possibleRoot, String possibleChildOrSame) {
            Path p1 = Paths.get(possibleChildOrSame).toAbsolutePath();
            Path p2 = Paths.get(possibleRoot).toAbsolutePath();
            return p1.startsWith(p2);
    }
    
        10
  •  0
  •   Arthur H.    6 年前

    令人惊讶的是,没有简单但实用的解决方案。

    公认的答案确实将相同的目录视为子目录,这是错误的。

    这里有一个使用 java.nio.file。路径 仅限API:

    static boolean isChildPath(Path parent, Path child){
          Path pn = parent.normalize();
          Path cn = child.normalize();
          return cn.getNameCount() > pn.getNameCount() && cn.startsWith(pn);
    }
    

    测试用例:

     @Test
    public void testChildPath() {
          assertThat(isChildPath(Paths.get("/FolderA/FolderB/F"), Paths.get("/FolderA/FolderB/F"))).isFalse();
          assertThat(isChildPath(Paths.get("/FolderA/FolderB/F"), Paths.get("/FolderA/FolderB/F/A"))).isTrue();
          assertThat(isChildPath(Paths.get("/FolderA/FolderB/F"), Paths.get("/FolderA/FolderB/F/A.txt"))).isTrue();
    
          assertThat(isChildPath(Paths.get("/FolderA/FolderB/F"), Paths.get("/FolderA/FolderB/F/../A"))).isFalse();
          assertThat(isChildPath(Paths.get("/FolderA/FolderB/F"), Paths.get("/FolderA/FolderB/FA"))).isFalse();
    
          assertThat(isChildPath(Paths.get("FolderA"), Paths.get("FolderA"))).isFalse();
          assertThat(isChildPath(Paths.get("FolderA"), Paths.get("FolderA/B"))).isTrue();
          assertThat(isChildPath(Paths.get("FolderA"), Paths.get("FolderA/B"))).isTrue();
          assertThat(isChildPath(Paths.get("FolderA"), Paths.get("FolderAB"))).isFalse();
          assertThat(isChildPath(Paths.get("/FolderA/FolderB/F"), Paths.get("/FolderA/FolderB/F/Z/X/../A"))).isTrue();
    }