【问题标题】:Convert a list of strings to a list of maps using Java streams使用 Java 流将字符串列表转换为映射列表
【发布时间】:2018-08-14 07:34:18
【问题描述】:

我有一个以下模式的字符串列表

String test ="name=john,age=28;name=paul,age=30;name=adam,age=50";
List<String> listOfStrings = Arrays.asList(test.split(";"));

我想将上面的字符串列表转换为键值对映射列表(如下所示)。

[{name=john, age=28}, {name=paul, age=30}, {name=adam, age=50}]

上面的每个条目都列出了一个映射,其中键作为名称和年龄,值作为它们的对应值。

这就是我为实现结果所做的工作。

listOfStrings.stream()
  .map(record -> Arrays.asList(record.split(",")).stream().map(field -> field.split("="))
  .collect(Collectors.toMap(keyValue -> keyValue[0].trim(), keyValue -> keyValue[1].trim())))
  .collect(Collectors.toList());

我想知道这是否有效,或者是否有更好的方法来使用 Java 流。

【问题讨论】:

  • 当你真正使用Arrays.asList(test.split(";")); 时,使用 Java 流是没有意义的,它已经全部在内存中,你可以迭代数组并手动构建映射
  • 您为什么要为此使用地图?一张地图意味着一把钥匙,一个年龄不是一把钥匙。此外,为什么要使用年龄?为什么不使用birthDate?
  • @Stultuske,我的意思是说字符串name 是一个键,而字符串age 是另一个键。
  • @Ravi 听起来您不知道密钥是什么,也不知道它是如何工作的。一个值是键(必须是唯一的,名称和年龄都不是唯一的)另一个是“值”
  • @user39950 即使完全在内存中,编写Stream 代码也比显式循环更容易、更易读。一方面:显式循环可能会做任何事情,这意味着它也可能包含任何类型的错误。 Stream 操作是众所周知且定义明确的,因此通过查看操作,您已经非常了解正在发生的事情。使用循环,您必须查看整个循环,解码每一行并确保没有任何花哨的事情发生,然后您才能很好地了解它的作用。

标签: java java-8 java-stream


【解决方案1】:

这是另一种使用模式匹配的替代方法,它不如 for 循环快,但比我测量的原始流解决方案快得多。

public static void main(String[] args) {
    String test ="name=john,age=28;name=paul,age=30;name=adam,age=50";
    String patternString = "(name)=(\\w*),(age)=(\\d*)[;]?";
    Pattern pattern = Pattern.compile(patternString);
    Matcher matcher = pattern.matcher(test);
    List<Map<String, String>> list = new ArrayList<>();

    while (matcher.find()) {
        Map<String, String> map = new HashMap<>();
        map.put(matcher.group(1), matcher.group(2));
        map.put(matcher.group(3), matcher.group(4));
        list.add(map);
    }
}

不匹配键(姓名和年龄),而是在创建地图元素时对它们进行硬编码,可能会略微提高性能。

【讨论】:

  • 使用正则表达式的有趣方法,但缺点(至少在您当前的版本中)必须知道密钥。是否有重新设计的机会,它匹配 = 前面的任何内容并将其用作键?
  • @ifloop,是的,例如,您可以用 (\\w*) 替换键(年龄/姓名)以匹配任何单词。
  • 更好的方法是定义键中允​​许的字母范围,例如不应允许=,例如[a-zA-Z0-9_]*
【解决方案2】:

如果您追求性能,请放弃 Stream API。尤其是带有子流的流对于编写高性能应用程序非常不利。

这是您的 Stream API 版本与普通旧 for 循环的比较:

public static void main(String[] args) {
  final String test = "name=john,age=28;name=paul,age=30;name=adam,age=50";

  final List<Map<String, String>> result1 = loop(test);
  final List<Map<String, String>> result2 = stream(test);

  System.out.println(result1);
  System.out.println(result2);
}


private static List<Map<String, String>> loop(String str) {
  long start = System.nanoTime();

  List<Map<String, String>> result = new ArrayList<>();
  String[] persons = str.split(";");

  for (String person : persons) {
    String[] attributes = person.split(",");
    Map<String, String> attributeMapping = new HashMap<>();

    for (String attribute : attributes) {
      String[] attributeParts = attribute.split("=");

      attributeMapping.put(attributeParts[0], attributeParts[1]);
    }

    result.add(attributeMapping);
  }

  long end = System.nanoTime();
  System.out.printf("%d nano seconds\n", (end - start));

  return result;
}

private static List<Map<String, String>> stream(final String str) {
  long start = System.nanoTime();

  List<String> listOfStrings = Arrays.asList(str.split(";"));
  List<Map<String, String>> result = listOfStrings.stream()
    .map(record -> Arrays.asList(record.split(",")).stream().map(field -> field.split("="))
    .collect(Collectors.toMap(keyValue -> keyValue[0].trim(), keyValue -> keyValue[1].trim())))
    .collect(Collectors.toList());

  long end = System.nanoTime();

  System.out.printf("%d nano seconds\n", (end - start));

  return result;
}

输出:

183887 纳秒

53722108 纳秒

[{name=john, age=28}, {name=paul, age=30}, {name=adam, age=50}]

[{name=john, age=28}, {name=paul, age=30}, {name=adam, age=50}]

【讨论】:

  • 感谢您的分析。这就是我发布这个问题的原因。我想知道我是否写了一个有效的解决方案。
  • 这里有两点:1) 我会非常谨慎对待此类基准测试 - 直到您正确地进行基准测试(参见 Java Microbenchmar Harness),结果可能是由于 JVM 没有正确预热,等等。 2)如果您还没有认识到性能瓶颈,请更喜欢清晰度/可读性而不是性能。这是因为您在未来理解/调试此类过早的优化时可能会损失更多,而不是节省响应时间。
  • @TomaszLinkowski 完全同意。 1) 绝对正确。这只是一个快速而肮脏的测量。不要求 100% 正确。但它给出了强烈的暗示。 2)使用声明式风格而不是功能风格不是过早的优化,这是一个设计决策。在这种情况下(Java + Stream API),由于 Stream API 的内部工作非常复杂,它带来了更高效的受欢迎的副作用。
  • 好吧,如果你通常使用函数式风格,只是为了性能而切换到声明式风格,那是过早的优化。但是,我同意,如果您更喜欢声明式风格(出于各种原因,包括可读性),您应该选择它。我们应该只是尝试以函数式的方式编写所有内容,因为有时它只会降低可读性。也就是说,功能样式(与问题不同,具有正确的格式并重构为方法)在这里对我来说更具可读性 - 它的冗长程度要低得多。
  • @TomaszLinkowski 在经过几天的讨论和广泛的测试之后,可能会切换到另一种范式,但仅限于需要额外性能的关键部分。每 20 行代码您只能更改 2 个函数,但这绝不是因为它与其他函数不同而为时过早。关于您的可读性问题:我同意,尤其是在谈论可维护性时。但从效率方面来看,我宁愿看到三重嵌套的 for 循环(spidersense tangling),而不是很好地对齐,而不是缩进的 flatMap().flatMap().flatMap(),它隐藏了复杂性
猜你喜欢
  • 1970-01-01
  • 2021-02-16
  • 2021-05-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-06-14
  • 1970-01-01
相关资源
最近更新 更多