【问题标题】:Split string into key-value pairs将字符串拆分为键值对
【发布时间】:2015-07-01 06:11:56
【问题描述】:

我有一个这样的字符串:

pet:cat::car:honda::location:Japan::food:sushi

现在: 表示键值对,而:: 分隔键值对。 我想将键值对添加到地图中。

我可以使用:

Map<String, String> map = new HashMap<String, String>();
String test = "pet:cat::car:honda::location:Japan::food:sushi";
String[] test1 = test.split("::");

for (String s : test1) {
    String[] t = s.split(":");
    map.put(t[0], t[1]);
}

for (String s : map.keySet()) {
    System.out.println(s + " is " + map.get(s));
}

但是有没有一种有效的方法来做到这一点?


我觉得代码效率低,因为我使用了 2 个String[] 对象并调用了两次split 函数。 另外,我正在使用t[0]t[1],如果没有值,它们可能会抛出ArrayIndexOutOfBoundsException

【问题讨论】:

  • 你能解释一下你认为它没有效率吗?顺便说一句,这不是 codereview.stackexchange.com,你应该去那里试试。
  • 第一件事是我是 Java 新手,我一直认为有一种更简单的方法来做我所做的事情。接下来是我使用了 2 个字符串数组对象并调用了 split 函数两次。另外我正在使用 t[0] 和 t[1] 如果没有值,可能会抛出 ArrayIndexOutOfBounds 异常。
  • 这个方法绝对没问题。
  • @Tom 我看到很多问题都要求以更好的方式做事。这就是为什么我在这里问同样的问题。
  • @ViChU 您可以将其添加到您的问题中吗?如果您对某种算法有疑虑,您应该解释它,以便回答问题的人可以解决。

标签: java hashmap


【解决方案1】:

使用 Guava 库,它是单行的:

String test = "pet:cat::car:honda::location:Japan::food:sushi";
Map<String, String> map = Splitter.on( "::" ).withKeyValueSeparator( ':' ).split( test );
System.out.println(map);

输出:

{pet=cat, car=honda, location=Japan, food=sushi}

这也可能比 JDK String.split 运行得更快,因为它不会为 "::" 创建正则表达式。

更新它甚至可以正确处理来自 cmets 的角盒:

String test = "pet:cat::car:honda::location:Japan::food:sushi:::cool";
Map<String, String> map = Splitter.on( "::" ).withKeyValueSeparator( ':' ).split( test );
System.out.println(map);

输出是:

{pet=cat, car=honda, location=Japan, food=sushi, =cool}

【讨论】:

  • 不错!谢谢。我不知道这是否是最有效的,但在处理此类字符串时,我应该牢记这一点。
  • 我认为withKeyValueSeparator( ':' )这部分代码应该改成withKeyValueSeparator( ":" ),因为withKeyValueSeparator方法是以String作为参数
【解决方案2】:

您可以使用以下代码对 split() 进行一次调用并在 String 上进行一次传递。但它当然首先假设字符串是有效的:

    Map<String, String> map = new HashMap<String, String>();
    String test = "pet:cat::car:honda::location:Japan::food:sushi";

    // split on ':' and on '::'
    String[] parts = test.split("::?");

    for (int i = 0; i < parts.length; i += 2) {
        map.put(parts[i], parts[i + 1]);
    }

    for (String s : map.keySet()) {
        System.out.println(s + " is " + map.get(s));
    }

上面可能比你的解决方案效率高一点,但是如果你发现你的代码更清晰,那就保留它,因为这种优化对性能产生重大影响的可能性几乎为零,除非您这样做数百万次。无论如何,如果它如此重要,那么你应该衡量和比较。

编辑:

对于那些想知道::? 在上述代码中的含义的人:String.split() 将正则表达式作为参数。分隔符是与正则表达式匹配的子字符串。 ::? 是一个正则表达式,表示:1 个冒号,后跟 0 或 1 个冒号。因此,它允许将 ::: 视为分隔符。

【讨论】:

  • 哇!这可以解决我认为效率低下的所有问题。但还有一件事。我将字符串更改为“location:Japan::food:sushi:::cool”,这样cool 的值就没有键了。但输出仍然是这样的:很酷的位置是日本食物是寿司
  • 刚刚发现值'cool'的键只是一个空字符串。
  • 最好简要解释一下::? 的含义(并为那些有相同问题但分隔符不同的人提一下:|:: 的明显替代方案)。
【解决方案3】:

您的解决方案确实有些低效。

给你解析字符串的人也有点像小丑。有一些行业标准的序列化格式,如 JSON 或 XML,存在快速、高效的解析。发明方轮绝不是个好主意。

第一个问题:你在乎吗?它是否足够慢以至于阻碍了您的应用程序的性能?可能不会,但只有一种方法可以找出答案。对您的代码进行基准测试。

也就是说,存在更有效的解决方案。下面是一个例子

public static void main (String[] args) throws java.lang.Exception
{
    String test = "pet:cat::car:honda::location:Japan::food:sushi";
    boolean stateiskey = true;

    Map<String, String> map = new HashMap<>();
    int keystart = 0;
    int keyend = 0;
    int valuestart = 0;
    int valueend = 0;

    for(int i = 0; i < test.length(); i++){
        char nextchar = test.charAt(i);
        if (stateiskey) {
            if (nextchar == ':') {
              keyend = i;           
              stateiskey = false;
              valuestart = i + 1;
            }
        } else {
            if (i == test.length() - 1 || (nextchar == ':' && test.charAt(i + 1) == ':')) {
                valueend = i;
                if (i + 1 == test.length()) valueend += 1; //compensate one for the end of the string
                String key = test.substring(keystart, keyend);
                String value = test.substring(valuestart, valueend);
                keystart = i + 2;
                map.put(key, value);
                i++;
                stateiskey = true;
            }
        }
    }

    System.out.println(map);
}

这个解决方案是一个只有两个状态的有限状态机。它只查看每个字符两次,一次是在测试边界时,一次是在将其复制到地图中的新字符串时。这是最低金额。

它不会创建不需要的对象,如字符串生成器、字符串或数组,这样可以降低收集压力。

它保持了良好的局部性。下一个字符可能总是在缓存中,因此查找成本很低​​。

虽然代价高昂,但可能不值得:

  • 要复杂得多,也不那么明显
  • 有各种各样的活动部件
  • 当您的字符串采用意外格式时,调试起来会更加困难
  • 你的同事会恨你
  • 当你不得不调试某些东西时你会讨厌你

值得吗?也许。您需要多快准确解析该字符串?

https://ideone.com/8T7twy 的一个快速而肮脏的基准测试告诉我,对于这个字符串,这个方法大约快 4 倍。对于较长的字符串,差异可能会更大。

但是你的版本对于 100.000 次重复仍然只有 415 毫秒,而这次是 99 毫秒。

【讨论】:

  • 好吧,回答你的问题。仍有许多组织没有适应最新的标准。就我而言,这些数据来自 POS 系统。美国有大量的零售店,每家零售店都有很多 POS 柜台,柜台中的每笔交易都会发送这些数据。通过 data ,它不仅包括这个字符串。这个字符串就像一个 10000 行的 xml 文件中的一行。因此,需要保持代码尽可能高效。因此我想到了这个问题:)
  • 嗯,我的基准测试表明,在非常脆弱的基础架构(ideone)上,10.000 行需要 44 毫秒。您的要求有多快?其余的代码在做什么?它花费了多少时间将字符串解析为映射?
  • 整个过程真的很庞大。我可以告诉你,这一行来自一个 3000 行的 java 类,它执行大量的 xml 读取、解密、映射操作、数据库访问和 JMS。假设你买了一部 iPhone,这个字符串告诉我你使用的卡类型。
  • 我的论点是,其他 2990 行可能比解析字符串的 10 行对性能的影响要大得多,而且找出 what i> 进行优化(通过基准测试和分析),而不是优化对您而言突出的部件,以期提高整体性能。即使是非常有经验的程序员,如果不进行分析,他们猜测导致性能问题的原因也几乎总是错误的。
  • 当然我会尝试分析。这部分代码中有一个更改请求。我应该尽可能高效:)
【解决方案4】:

试试这个代码 - 查看 cmets 以获得解释:

HashMap<String,String> hmap = new HashMap<>();
String str="abc:1::xyz:2::jkl:3";
String straraay[]= str.split("::?");

for(int i=0;i<straraay.length;i+=2) {
    hmap.put(straraay[i],straraay[i+1]);
}

for(String s:straraay){
    System.out.println(hmap.values()); //for Values only
    System.out.println(hmap.keySet()); //for keys only if you want to more clear
}

【讨论】:

    【解决方案5】:

    我不知道这是否是最好的方法,但我认为这是另一种不使用 split 方法的方法来做同样的事情

    Map<String, String> map = new HashMap<String, String>();
    String test = "pet:cat::car:honda::location:Japan::food:sushi";
    String[] test1 = test.replaceAll("::",":").split(":");
    for(int i=0;i<test1.length;i=i+2)
    {
         map.put(test1[i], test1[i+1]);
    }
    
    for (String s : map.keySet()) {
        System.out.println(s + " is " + map.get(s));
    }
    

    希望它会有所帮助:)

    【讨论】:

    • 我认为 new_test 不需要,因为您可以在 test1 语句本身中执行它,例如 String[] test1 = test.replaceAll("::",":").split(":") ;无论如何谢谢:)
    • @ViChU 没错 :)
    • 调用 replaceAll 并不比调用 split 更有效
    • @Martijn 我已上传此代码作为执行相同操作的替代方式,而不是执行此操作的有效代码:)
    • @Vishnu 问题是关于效率的。
    【解决方案6】:

    这可能很有用。 *utm_source=test_source&utm_medium=test_medium&utm_term=test_term& utm_content=test_content&utm_campaign=test_name&referral_code=DASDASDAS

       String str[] = referrerString.split("&");
                        HashMap<String,String> stringStringHashMap= new HashMap<>();
                        List<String> al;
                        al = Arrays.asList(str);
                        String[] strkey ;
    
                    for (String s : al) {
                        strkey= s.split("=");
                        stringStringHashMap.put(strkey[0],strkey[1]);
    
    
                    }
                    for (String s : stringStringHashMap.keySet()) {
                        System.out.println(s + " is " + stringStringHashMap.get(s));
                    }
    

    【讨论】:

      【解决方案7】:

      你的程序绝对没问题。

      只是因为您要求更优化的代码。

      我通过采用少量变量而不是采用数组并将其存储在其中来减少您的内存。

      看看你的字符串,它遵循一个模式。

      key : value :: key : value ::....

      我们可以从中做些什么?

      获取密钥直到它是 : ,一旦它到达 : 获取值直到它到达 '::'。

      package qwerty7;
      
      import java.util.HashMap;
      
      public class Demo {
      public static void main(String ar[])
      {
          StringBuilder s = new StringBuilder("pet:cat::car:honda::location:Japan::food:sushi");
          boolean isKey = true;
          String key = "", value = "";
          HashMap<String, String> hm = new HashMap();
          for(int i = 0; i < s.length(); i++)
          {
              char ch = s.charAt(i);
              char nextChar = s.charAt(i+1);
              if(ch == ':' && nextChar != ':')
              {
                  isKey = false;
                  continue;
              }
              else if(ch == ':' && nextChar == ':')
              {
                  hm.put(key, value);
                  isKey = true;
                  key = "";
                  value = "";
                  i+=1;
                  continue;
              }
              if(isKey)
              {
                  key += ch;
              }
              else
              {
                  value += ch;
              }
               if(i == s.length() - 1)
                  {
                      hm.put(key, value);
                  }
      
          }
          for (String x : hm.keySet()) {
              System.out.println(x + " is " + hm.get(x));
          }
      }
      }
      

      这样做不会在每次拆分时占用太多迭代。

      不占用太多内存。

      时间复杂度 O(n)

      输出:

      car is honda
      location is Japan
      pet is cat
      food is sushi
      

      【讨论】:

      • 您的代码忘记了最后一个键/值对。结果中没有 foo/sushi。我真的怀疑它是否更有效:这段代码创建了大量需要垃圾收集的临时字符串对象。
      • 比浪费已经在字符串中的数组内存要好。
      • @UmaKanth 这个 sn-p 多次调用 charAt。可以吗?
      • 我们可以使用字符替代
      猜你喜欢
      • 1970-01-01
      • 2016-01-18
      • 2016-12-13
      • 2021-10-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-06-22
      • 2018-11-06
      相关资源
      最近更新 更多