【问题标题】:N-th indexOf in String?字符串中的第 N 个 indexOf?
【发布时间】:2018-12-01 03:03:14
【问题描述】:

我需要提取一个 URL 的子字符串。

网址

  • /service1/api/v1.0/foo -> foo
  • /service1/api/v1.0/foo/{fooId} -> foo/{fooId}
  • /service1/api/v1.0/foo/{fooId}/boo -> foo/{fooId}/boo

其中一些 URL 可能有请求参数。

代码

String str = request.getRequestURI();
str = str.substring(str.indexOf("/") + 1);
str = str.substring(str.indexOf("/") + 1);
str = str.substring(str.indexOf("/") + 1);
str = str.substring(str.indexOf("/") + 1, str.indexOf("?"));

有没有更好的方法来提取子字符串而不是经常使用indexOf 方法?

【问题讨论】:

  • str.substring("/service1/api/v1.0/".length())?
  • 根据/ 字符将字符串拆分为一个数组。从元素 4 开始循环遍历数组直到结束,重新附加到一个新的 var 中,并在每个元素之间添加一个 /
  • 一项性能改进是从其他索引计算索引,而不是多次复制字符串,如index = str.indexOf("/", index) + 1
  • @JBNizet 你有一个有效的观点。我们有不止一项服务。所以不能用 URL 来比较字符串。
  • 如果所有的服务看起来都像/api/v1.0/...,那么找到公共后缀(即/api/v1.0/)的索引,加上它的长度,并从该索引中获取子字符串。否则,按照 pkpnd 的建议进行操作

标签: java regex string url split


【解决方案1】:

这个怎么样:

String s = "/service1/api/v1.0/foo/{fooId}/boo";
String[] sArray = s.split("/"); 
StringBuilder sb = new StringBuilder();

for (int i = 4; i < sArray.length; i++) {
    sb.append(sArray[i]).append("/");
}

sb.deleteCharAt(sb.length() - 1);
System.out.println(sb.toString());

输出:

foo/{fooId}/boo

如果url前缀总是/service1/api/v1.0/,你只需要s.substring("/service1/api/v1.0/".length())

【讨论】:

  • 有没有更好的办法来代替丑陋的4
【解决方案2】:

Path 对此非常有用:

public static void main(String[] args) {
    Path root = Paths.get("/service1/api/v1.0/foo");
    Path relativize = root.relativize(Paths.get("/service1/api/v1.0/foo/{fooId}/boo"));
    System.out.println(relativize);
}

输出:

{fooId}/嘘

【讨论】:

    【解决方案3】:

    有很多替代方法:

    1. 在带有\ 分隔符的拆分字符串上使用Java-Stream API:

      String str = "/service1/api/v1.0/foo/{fooId}/boo";  
      String[] split = str.split("\\/");
      String url = Arrays.stream(split).skip(4).collect(Collectors.joining("/"));
      System.out.println(url);
      

      去掉参数后,Stream 会变成这样:

      String url = Arrays.stream(split)
          .skip(4)
          .map(i -> i.replaceAll("\\?.+", ""))
          .collect(Collectors.joining("/"));
      
    2. 这也是Regex 的位置!使用 Pattern 和 Matcher 类。

      String str = "/service1/api/v1.0/foo/{fooId}/boo";
      
      Pattern pattern = Pattern.compile("\\/.*?\\/api\\/v\\d+\\.\\d+\\/(.+)");
      Matcher matcher = pattern.matcher(str);
      while (matcher.find()) {
          System.out.println(matcher.group(1));
      }
      

    如果您依赖 indexOf(..) 的用法,您可能需要使用 while 循环。

    String str = "/service1/api/v1.0/foo/{fooId}/boo?parameter=value"; 
    
    String string = str;
    while(!string.startsWith("v1.0")) {
        string = string.substring(string.indexOf("/") + 1);
    }
    
    System.out.println(string.substring(string.indexOf("/") + 1, string.indexOf("?")));
    

    其他答案包括一种方法,即如果前缀不可变,您可能只想使用一次调用 idndexOf(..) 方法 (@JB Nizet):

    string.substring("/service1/api/v1.0/".length(), string.indexOf("?"));
    

    所有这些解决方案都基于您的输入和事实,模式是已知的,或者至少以\ 或版本v1.0 作为检查点分隔的前一部分的数量 - 最佳解决方案可能不会出现在这里因为 URL 的组合是无限的。您必须知道输入 URL 的所有可能组合才能找到处理它的最佳方法。

    【讨论】:

      【解决方案4】:

      这里有一些不错的选择。

      1) 如果您知道“foo”将始终是第 4 个标记,那么您已经有了正确的想法。您的方式的唯一问题是您拥有高效所需的信息,但您没有使用它。无需多次复制字符串并从新字符串的开头重新循环,您可以从上次中断的地方继续 4 次,以找到您想要的起点。

      String str = "/service1/api/v1.0/foo/{fooId}/boo";
      
      // start at the beginning
      int start = 0;
      // get the 4th index of '/' in the string
      for (int i = 0; i != 4; i++) {
          // get the next index of '/' after the index 'start'
          start = str.indexOf('/',start);
          // increase the pointer to the next character after this slash
          start++;
      }
      // get the substring
      str = str.substring(start);
      

      这将比任何正则表达式模式高效得多。

      2) 正则表达式:(java.util.regex.*)。如果您想要的总是以"service1/api/v1.0/" 开头,这将起作用。在它之前可能有其他目录,例如"one/two/three/service1/api/v1.0/".

      // \Q \E will automatically escape any special chars in the path
      // (.+) will capture the matched text at that position
      // $ marks the end of the string (technically it matches just before '\n')
      Pattern pattern = Pattern.compile("/service1/api/v1\\.0/(.+)$");
      // get a matcher for it
      Matcher matcher = pattern.matcher(str);
      // if there is a match
      if (matcher.find()) {
          // get the captured text
          str = matcher.group(1);
      }
      

      如果您的路径可能有所不同,您可以使用正则表达式来解决它。例如:service/api/v3/foo/{bar}/baz/"(注意不同的数字格式和尾随'/')也可以通过将正则表达式更改为"/service\\d*/api/v\\d+(?:\\.\\d+)?/(.+)(?:/|$)"来匹配

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2013-09-26
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-05-08
        • 1970-01-01
        相关资源
        最近更新 更多