【问题标题】:Generating a canonical path生成规范路径
【发布时间】:2011-02-10 15:19:15
【问题描述】:

有谁知道我可以用来生成规范路径的任何 Java 库(基本上删除反向引用)。

我需要一些能做以下事情的东西:

原始路径 -> 规范路径

/../foo/       -> /foo
/foo/          -> /foo
/../../../     -> /
/./foo/./      -> /foo
//foo//bar     -> /foo/bar
//foo/../bar   -> /bar

等等……

目前我懒惰地依赖使用:

 new File("/", path).getCanonicalPath();

但这会根据实际文件系统解析路径,并且是同步的。

   java.lang.Thread.State: BLOCKED (on object monitor)
        at java.io.ExpiringCache.get(ExpiringCache.java:55)
        - waiting to lock <0x93a0d180> (a java.io.ExpiringCache)
        at java.io.UnixFileSystem.canonicalize(UnixFileSystem.java:137)
        at java.io.File.getCanonicalPath(File.java:559)

我正在规范化的路径在我的文件系统上不存在,因此只需该方法的逻辑就可以了,因此不需要任何同步。我希望有一个经过良好测试的库,而不是自己编写。

【问题讨论】:

  • 是否支持相对路径作为输入?或者这是一个错误情况?
  • /foo/../bar/ 的输出应该是什么?
  • @Joachim:我假设所有路径都基于根。在大多数情况下,我只是从 url 中的路径中删除反向引用。
  • @Joachim - 不,getAbsPath 导致输入 '/foo/../bar' 的结果为 '/foo/../bar'

标签: java path


【解决方案1】:

我认为您可以使用URI 类来执行此操作;例如如果路径不包含需要在 URI 路径组件中转义的字符,则可以这样做。

String normalized = new URI(path).normalize().getPath();

如果路径包含(或可能包含)需要转义的字符,多参数构造函数将转义 path 参数,您可以为其他参数提供 null

注意事项:

  1. 以上通过将文件路径视为相对 URI 来规范化文件路径。如果您想规范化整个 URI ...包括(可选)方案、权限和其他组件,请不要调用 getPath()

  2. URI 规范化不像文件规范化那样涉及查看文件系统。但另一方面是,当路径中有符号链接时,规范化的行为与规范化不同。

【讨论】:

  • 看起来不错。它仍然需要一些调整(删除前导 /../,但它让我大部分时间都在那里,谢谢。
  • @Joel:为什么要删除领先的/../?要么它们是错误的,您应该将它们视为错误条件,或者您将所有路径指定为相对于某个点并且您应该支持它们。但默默地删除它们听起来是个坏主意。
  • 你可能是对的,但我得到了各种糟糕的数据,我只是在清理它,确保所有路径都植根于 /
【解决方案2】:

使用 Apache Commons IO(一个知名且经过良好测试的库)

public static String normalize(String filename)

将完全满足您的需求。

例子:

String result = FilenameUtils.normalize(myFile.getAbsolutePath());

【讨论】:

    【解决方案3】:

    如果你不需要路径规范化而只需要规范化,在 Java 7 中你可以使用java.nio.file.Path.normalize 方法。 根据http://docs.oracle.com/javase/7/docs/api/java/nio/file/Path.html

    此方法不访问文件系统;该路径可能找不到存在的文件。

    如果你使用 File 对象,你可以使用这样的东西:

    file.toPath().normalize().toFile()
    

    【讨论】:

      【解决方案4】:

      你可以试试这样的算法:

      String collapsePath(String path) {
          /* Split into directory parts */
          String[] directories = path.split("/");
          String[] newDirectories = new String[directories.length];
          int i, j = 0;
      
          for (i=0; i<directories.length; i++) {
              /* Ignore the previous directory if it is a double dot */
              if (directories[i].equals("..") && j > 0)
                  newDirectories[j--] = "";
              /* Completely ignore single dots */
              else if (! directories[i].equals("."))
                  newDirectories[j++] = directories[i];
          }
      
          /* Ah, what I would give for String.join() */
          String newPath = new String();
          for (i=0; i < j; i++)
              newPath = newPath + "/" + newDirectories[i];
          return newPath;
      }
      

      它并不完美;它与目录数量成线性关系,但确实会在内存中复制。

      【讨论】:

        【解决方案5】:

        哪种路径被限定为规范路径取决于操作系统。 这就是Java需要在文件系统上检查它的原因。 所以在不知道操作系统的情况下,没有简单的逻辑来测试路径。

        【讨论】:

          【解决方案6】:

          因此,虽然规范化可以解决问题,但这里有一个比简单调用 Paths.normalize() 公开更多 Java API 的过程

          假设我想在文件系统上查找不在我当前目录中的文件。 我的工作代码文件是

          myproject/src/JavaCode.java

          位于 myproject/src/.我的文件在

          ../../data/myfile.txt

          我正在测试从 JavaCode.java 运行我的代码的程序

          public static void main(String[] args) { 
              findFile("../../data","myfile.txt");
              System.out.println("Found it.");
          }
          public static File findFile(String inputPath, String inputFile) {
              File dataDir = new File("").getAbsoluteFile(); // points dataDir to working directory
              String delimiters = "" + '\\' + '/';           // dealing with different system separators
              StringTokenizer st = new StringTokenizer(inputPath, delimiters);
              while(st.hasMoreTokens()) {
                  String s = st.nextToken();
                  if(s.trim().isEmpty() || s.equals(".")) 
                      continue;
                  else if(s.equals("..")) 
                      dataDir = dataDir.getParentFile();
                  else {
                      dataDir = new File(dataDir, s);
                      if(!dataDir.exists())
                          throw new RuntimeException("Data folder does not exist.");
                  }
              }
              return new File(dataDir, inputFile);    
          }
          

          在指定位置放置文件后,应打印“找到它”。

          【讨论】:

            【解决方案7】:

            我假设您有字符串并且想要字符串,并且您现在可以使用 Java 7,并且您的默认文件系统使用“/”作为路径分隔符,因此请尝试:

            String output = FileSystems.getDefault().getPath(input).normalize().toString();
            

            你可以试试这个:

            /**
             * Input           Output
             * /../foo/     -> /foo
             * /foo/        -> /foo
             * /../../../   -> /
             * /./foo/./    -> /foo
             * //foo//bar   -> /foo/bar
             * //foo/../bar -> /bar
             */
            @Test
            public void testNormalizedPath() throws URISyntaxException, IOException {
                String[] in = new String[]{"/../foo/", "/foo/", "/../../../", "/./foo/./",
                        "//foo/bar", "//foo/../bar", "/", "/foo"};
                String[] ex = new String[]{"/foo", "/foo", "/", "/foo", "/foo/bar", "/bar", "/", "/foo"};
                FileSystem fs = FileSystems.getDefault();
                for (int i = 0; i < in.length; i++) {
                    assertEquals(ex[i], fs.getPath(in[i]).normalize().toString());
                }
            }
            

            【讨论】:

            • 我有点想知道为什么留下有关 Java 更高版本的最新答案会被否决。但是我仍然没有看到这里问题的答案有问题。
            • "文件系统可以安全地被多个并发线程使用"
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2011-04-14
            • 2012-08-19
            • 1970-01-01
            • 1970-01-01
            • 2011-04-05
            • 2013-02-13
            • 1970-01-01
            相关资源
            最近更新 更多