将字符串拆分为键值对

22

我有一个字符串:

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 异常。


3
你能解释一下为什么你认为这不是高效的吗?顺便说一下,这不是codereview.stackexchange.com,你应该在那里尝试。 - Tom
首先,我是Java的新手,一直认为有更简单的方法来完成我所做的事情。其次,我使用了2个字符串数组对象,并调用了split函数两次。此外,我正在使用t [0]和t [1],如果没有值可能会抛出ArrayIndexOutOfBounds异常。 - v1shnu
这个方法完全没问题。 - Uma Kanth
1
@Tom,我看到很多问题都在问有没有更好的处理方法,所以我也在这里问了同样的问题。 - v1shnu
@ViChU,你能把这个加到你的问题里吗?如果你对某个算法有疑虑,你应该解释一下,这样回答问题的人就可以针对它进行回答。 - Tom
7个回答

21

使用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更快,因为它不会为"::"创建正则表达式。

更新:它还可以正确处理来自评论的边角情况。

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}

很好!谢谢你。我不知道这是否是最有效的方法,但在处理这样的字符串时,我应该记住这个方法。 - v1shnu
我认为代码中的 withKeyValueSeparator( ':' ) 部分应该改为 withKeyValueSeparator( ":" ),因为 withKeyValueSeparator 方法需要一个字符串作为参数。 - Salman

20
你可以使用以下代码一次调用split()并一次遍历字符串。但这当然假设该字符串在第一次使用时是有效的。

你可以使用以下代码一次调用split()并一次遍历字符串。但这当然假设该字符串在第一次使用时是有效的:

    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”没有键。但输出仍然是这样的:is cool 位置是日本 食物是寿司 - v1shnu
刚刚发现,值为“cool”的键只是一个空字符串。 - v1shnu
4
简要解释一下 ::? 的含义可能是有益的(并提到明显的替代方案 :|::,适用于遇到相同问题但使用不同分隔符的人)。 - Bernhard Barker

2
您的解决方案确实有点低效。
给你要解析的字符串的人也有点小丑。行业标准的序列化格式,比如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);
}

这个解决方案是一个只有两个状态的有限状态机。它仅检查每个字符两次,一次在测试边界时,一次在将其复制到您的映射中的新字符串中。这是最少量。
它不会创建不需要的对象,如stringbuilders、strings或arrays,这可以保持集合压力低。
它保持良好的局部性。下一个字符可能总是在缓存中,因此查找是廉价的。
然而,它的代价很高,可能不值得:
  • 它更加复杂和不明显
  • 有各种各样的移动部件
  • 当您的字符串格式出现意外情况时,调试变得更加困难
  • 你的同事会讨厌你
  • 当你不得不调试某些东西时,你会讨厌自己
值得吗?也许。你需要多快才能解析那个字符串? https://ideone.com/8T7twy上的一个快速而肮脏的基准测试告诉我,对于这个字符串,这种方法大约快了4倍。对于更长的字符串,差异可能会更大。
但是你的版本仍然只有415毫秒,而这个版本只需要99毫秒就能完成10万次重复。

好的,回答你的问题。仍有许多组织尚未适应最新标准。在我的情况下,这些数据来自POS系统。美国有大量零售店,每个零售店都有许多POS柜台,柜台上的每笔交易都会发送这些数据。而且,所谓的数据并不仅仅包括这个字符串。这个字符串就像10000行XML文件中的一行。因此,有必要尽可能地保持代码的效率。因此,我才会产生这个问题 :) - v1shnu
我的基准测试显示,在非常薄弱的基础设施(ideone)上,一万行代码需要 44 毫秒。你的要求有多快?代码的其余部分是干什么的?它在将字符串解析为映射方面花费了多少时间? - Martijn
整个过程非常庞大。我可以告诉你,这行代码位于一个 3000 行的 Java 类中,该类执行了大量的 XML 读取、解密、映射操作、数据库访问和 JMS 操作。如果你购买了 iPhone,这个字符串会告诉我你使用的卡类型。 - v1shnu
我的观点是,其他2990行代码很可能对性能产生比解析字符串的10行代码更大的影响,因此找出需要优化的部分(通过基准测试和分析)比优化你认为会提高整体性能的部分更为重要。即使是非常有经验的程序员,在没有进行分析的情况下,他们猜测导致性能问题的原因几乎总是错误的。 - Martijn
当然,我会尝试进行性能分析。这部分代码有一个变更请求,我应该尽可能高效地完成它 :) - v1shnu
@ViChU,从计算资源的角度来看,最有效的方法可能是放弃Java解决方案,并在汇编语言中重新实现整个项目。但很少有人希望你这样做。更有可能的是,你需要在保持足够性能的同时完成任务。几乎总会有权衡取舍。 - Martijn

1

尝试这段代码 - 查看注释以获取解释:

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
}

0

我不知道这是否是最佳方法,但我认为这是另一种方法,可以在不使用两次分割方法的情况下完成相同的事情。

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(":"); 不管怎样,还是谢谢 :) - v1shnu
@ViChU 是的,没错 :) - Vishnu
调用replaceAll方法并不比调用split方法更高效。 - Martijn
@Martijn,我已经上传了这段代码作为另一种完成相同任务的方式,而不是一个高效的代码。 - Vishnu
@Vishnu,问题是关于效率的。 - Martijn
@Martijn,就效率而言,我同意你的看法,但问题中给出了“我使用了2个字符串数组对象并调用了split函数两次”的信息。为了解决这一部分,我已经提供了我的代码,但并没有使代码更加高效。 - Vishnu

0
这可能会有用。 *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));
                }

-1

你的程序非常好。

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

我通过使用少量变量而不是使用数组并将它们存储在其中来减少了你的内存。

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

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

1
你的代码忘记了最后一个键值对。结果中没有foo/sushi。我真的怀疑它更有效率:这段代码创建了许多需要进行垃圾回收的临时字符串对象。 - JB Nizet
比浪费已经在字符串中的数组内存更好。 - Uma Kanth
@UmaKanth 这段代码多次调用了 charAt 方法,这样做没问题吗? - v1shnu
我们可以使用字符替换。 - Uma Kanth

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接