在 Groovy 中编写的字符串转换为 Map 的最佳方法是什么?

14

我有一个字符串:

def data = "session=234567893egshdjchasd&userId=12345673456&timeOut=1800000"

我想将它转换成一个映射表

 ["session", 234567893egshdjchasd]
 ["userId", 12345673456]
 ["timeout", 1800000]

这是我目前的做法,

 def map = [:]

 data.splitEachLine("&"){

   it.each{ x ->

     def object = x.split("=")
     map.put(object[0], object[1])

   }

 }

这个方法能用,但还有更高效的方式吗?

8个回答

25

我不知道这样做会更快,但从句法简洁的角度来看,它是有建议意义的:

def data = 'session=234567893egshdjchasd&userId=12345673456&timeOut=1800000'
def result = data.split('&').inject([:]) { map, token -> 
    //Split at "=" and return map with trimmed values
    token.split('=').with { 
        map[it[0].trim()] = it[1].trim() 
    }
    map 
}

个人而言,我喜欢Don的答案,因为它易读易维护,但根据不同的情境,这可能并不适用。

编辑:实际上这是一个重新格式化的一行代码。


4
赞成使用高级GDK方法(注入)和高级英文短语(句法简洁)。 - Dónal
3
如果你真的非常讨厌可读性,你可以将注入内部更改为token.split('=').with { map << [ (it[0]):it[1] ] }。 - tim_yates
我喜欢这个解决方案 - 直到我发现有一个“collectEntries()”函数可以用来构建映射。那比“inject()”更易读,详见下面的答案。 - Axel Heider

14

我不知道这是否更有效率,但在我看来,这个方法更简单一些(可能因人而异)

def data = "session=234567893egshdjchasd&userId=12345673456&timeOut=1800000"
def map = [:]

data.split("&").each {param ->
    def nameAndValue = param.split("=")
    map[nameAndValue[0]] = nameAndValue[1]
}

2
我写了一个快速而简单的Groovy脚本(可能存在缺陷),比较了提到的三种技术,结果发现Dons方法始终最快。它获取了三个不同长度的查询字符串,并计时每种方法所需时间,输出例如:Method 1 (ig0774) [124727794, 2236178, 4806756] total: 131770728 Method 2 (Don) [2546134, 1174801, 2227867] total: 5948802 Method 3 (Ted Naleid) [10447068, 1915955, 2840445] total: 15203468 对于我的目的来说已经足够了。 - Steve

7

如果你想要高效的话,正则表达式是最好的选择:

def data = "session=234567893egshdjchasd&userId=12345673456&timeOut=1800000"
def map = [:]
data.findAll(/([^&=]+)=([^&]+)/) { full, name, value ->  map[name] = value }

println map

打印:

[session:234567893egshdjchasd, userId:12345673456, timeOut:1800000]

如果您不熟悉正则表达式,它可能看起来有点陌生,但实际上并不那么复杂。它只有两个组,第一个组是除了"&"或"="之外的任何字符。第二个组是除了"="之外的任何字符。捕获组位于"="的两侧。

这是一个不错的解决方案,但可以通过将其变成一个功能性的一行代码来使其更好:data.findAll(/([^&=]+)=([^&]+)/) { full, name, value -> [name, value] }.collectEntries({ it }) - David Roussel

3

经过一番搜索,“collectEntries()”是需要使用的神奇函数,它会创建一个map元素。与创建列表的“collect()”函数非常相似。因此,考虑到

def params = "a1=b1&a2=b2&a3&a4=&a5=x=y"

一句话概括就是:
map = params.tokenize("&").collectEntries{ 
          it.split("=",2).with{ 
              [ (it[0]): (it.size()<2) ? null : it[1] ?: null ] 
          }
      }

这将创建

map = [a1:b1, a2:b2, a3:null, a4:null, a5:x=y]

根据你想如何处理 "a3" 和 "a4=" 两种情况,你也可以使用稍微更短的版本。

...
[ (it[0]): (it.size()<2) ? null : it[1] ] 
...

然后你会得到这样的结果:

map = [a1:b1, a2:b2, a3:null, a4:, a5:x=y]

1

这是我的努力,它一次性初始化和填充了地图,并避免了我个人觉得难以理解的inject方法:

    def map = data.split('&').collectEntries {
        def kvp = it.split('=').collect { string ->
            string = string.trim()
            return string
    }
    [(kvp[0]): kvp.size() > 1 ? kvp[1] ?: '' : '']
    // the null check is almost certainly overkill so you could use :-
    // [(kvp[0]): kvp.size() > 1 ? kvp[1] : '']
    // this just checks that a value was found and inserts an empty string instead of null 
}

1

1

1
我不建议使用split。
Split会创建一个新的字符串,而当创建环境变量集合时,您可能需要一个地图列表。
在初始断点(&)和嵌套断点(=)上都进行标记化。虽然大多数解释器仍将工作,但有些可能会字面运行分割,这样您就会得到一个字符串列表,而不是地图列表。
def data= "test1=val1&test2=val2"
def map = [:]

map = data.tokenize("&").collectEntries {
    it.tokenize("=").with {
        [(it[0]):it[1]]
    }
}

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