java 如何使用流将所有映射键值连接成一个字符串,并对每个值进行替换?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/31497030/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-02 18:42:16  来源:igfitidea点击:

How to concatenate all map key-values into a string, using streams, with replacements made on each value?

javajava-stream

提问by aliteralmind

I'm a Java 7 person, and have been commanded to never use a for-loop again, with only partial tongue in cheek. I need to take a Map<String, String>, and end up with a string of command-line parameters, as in the output of the following code (ignoring the "streamy" line for the moment):

我是一个 Java 7 人,并且被命令不再使用 for 循环,只有部分舌头在脸颊上。我需要使用Map<String, String>, 并以一串命令行参数结束,如以下代码的输出(暂时忽略“streamy”行):

forLoop: --name5 "value5"--name4 "value4"--name3 "value3"--name2 "{\"x\": \"y\"}"--name1 "value1"
resetAllValues: --name5 "value5"--name4 "value4"--name3 "value3"--name2 "{\"x\": \"y\"}"--name1 "value1"
streamy: --name5 "value5"--name5 "value5"--name4 "value4"--name5 "value5"--name5 "value5"--name4 "value4"--name3 "value3"--name5 "value5"--name5 "value5"--name4 "value4"--name5 "value5"--name5 "value5"--name4 "value4"--name3 "value3"--name2 "{\"x\": \"y\"}"--name5 "value5"--name5 "value5"--name4 "value4"--name5 "value5"--name5 "value5"--name4 "value4"--name3 "value3"--name5 "value5"--name5 "value5"--name4 "value4"--name5 "value5"--name5 "value5"--name4 "value4"--name3 "value3"--name2 "{\"x\": \"y\"}"--name1 "value1"--name5 "value5"--name5 "value5"--name4 "value4"--name5 "value5"--name5 "value5"--name4 "value4"--name3 "value3"--name5 "value5"--name5 "value5"--name4 "value4"--name5 "value5"--name5 "value5"--name4 "value4"--name3 "value3"--name2 "{\"x\": \"y\"}"--name5 "value5"--name5 "value5"--name4 "value4"--name5 "value5"--name5 "value5"--name4 "value4"--name3 "value3"--name5 "value5"--name5 "value5"--name4 "value4"--name5 "value5"--name5 "value5"--name4 "value4"--name3 "value3"--name2 "{\"x\": \"y\"}"--name1 "value1"

The names/keys remain unchanged. The values need to be placed in double quotes, and all existing double quotes need to be escaped (\"). The close-quotes shouldbe followed by a space.

名称/键保持不变。值需要放在双引号中,并且所有现有的双引号都需要转义 ( \")。引号后面应该跟一个空格。

I've done it the pre-java-8 way in forLoop, and partially with streams in resetAllValues, which actually alters each entry. The streamyone is way wrong, as it's repeatedly appending allcurrent output onto the next element...

我已经forLoopresetAllValues. 的streamy一个方法是错误的,因为它一再追加所有电流输出到下一个元素...

How can this be done efficiently with streams? And how can it be done in a way that doesn't alter the map entries or use a builder? I'm not seeing it yet.

如何使用流有效地做到这一点?以及如何以不改变地图条目或使用构建器的方式完成?我还没有看到。

package working;

import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

public class Temp {
   public static void main(String[] cmd_lineParams) {
      Map<String, String> map = new HashMap<>(5);
      map.put("name1", "value1");
      map.put("name2", "{\"x\": \"y\"}");
      map.put("name3", "value3");
      map.put("name4", "value4");
      map.put("name5", "value5");
      forLoop(map);
      resetAllValues(new HashMap<String, String>(map));
      streamy(new HashMap<String, String>(map));
   }
   private static final Matcher matcher = Pattern.compile("\"").matcher("ignored input");
   private static final void forLoop(Map<String, String> map) {
      StringBuilder builder = new StringBuilder();
      for(Map.Entry<String, String> entry : map.entrySet()) {
         String value = matcher.reset(entry.getValue()).replaceAll("\\\"");
         builder.append("--").append(entry.getKey()).append(" \"").append(value).append("\"");
      }
      System.out.println("forLoop: " + builder.toString());
   }

Continued...

继续...

   private static final void resetAllValues(Map<String, String> map) {
      map = map.entrySet().stream()
         .collect(Collectors.toMap(entry -> entry.getKey(),
                                   entry -> matcher.reset(entry.getValue()).replaceAll("\\\\"")));
      StringBuilder builder = new StringBuilder();
      for(Map.Entry<String, String> entry : map.entrySet()) {
         builder.append("--").append(entry.getKey()).append(" \"").append(entry.getValue()).append("\"");
      }
      System.out.println("resetAllValues: " + builder.toString());
   }
   private static final void streamy(Map<String, String> map) {
      StringBuilder builder = new StringBuilder();
      map.forEach((k,v) -> builder.append(
         builder.append("--").append(k).append(" \"").append(
            matcher.reset(v).replaceAll("\\\"")).append("\"")));
      System.out.println("streamy: " + builder.toString());
   }
}

(My nine-year-old says I need to say "difficulty" somewhere in this question. So: difficulty.)

(我九岁的孩子说我需要在这个问题的某个地方说“困难”。所以:困难。)

回答by Aarjav

I guess since you said difficulty, I'll have to answer this! :)

我想既然你说困难,我就必须回答这个!:)

map.entrySet().stream().map((entry) -> //stream each entry, map it to string value
            "--" + entry.getKey() + " \"" + entry.getValue().replaceAll("\"", "\\\"") + "\"")
            .collect(Collectors.joining(" ")); //and 'collect' or put them together by joining

I personally don't like using streams because it gets ugly pretty quick, but its useful for simpler loops. (join all values with some character for example) With this however you can easily parallelize it by using parallelStream()instead of stream()

我个人不喜欢使用流,因为它很快就会变得丑陋,但它对于更简单的循环很有用。(例如,将所有值与某个字符连接起来)但是,您可以通过使用parallelStream()而不是轻松地将其并行化stream()

If you wanted the values in some sort of order so its not so random (as it will be with HashMap) you can do a sort before map:

如果您希望值按某种顺序排列,因此它不是那么随机(就像使用 一样HashMap),您可以在映射之前进行排序:

.stream().sort((e1, e2) -> e1.getValue().compareTo(e2.getValue()))
.map(...)...

Just remember that those are Map.Entryobjects.

请记住,那些是Map.Entry对象。

UPDATE:Tagir Valeev's answer below is better, as it shows the best practice instead of just making it work. It also remedies the initial gripe I had with streams + lambdas couple years back when I wrote this answer (getting too ugly).

更新:下面 Tagir Valeev 的回答更好,因为它展示了最佳实践,而不仅仅是让它发挥作用。它还解决了几年前我写这个答案时对流 + lambdas 的最初抱怨(变得太丑了)。

回答by Tagir Valeev

Always try to decompose your complex problem into simple and independent parts. Here (both for stream and non-stream solution) it's better to put the escaping code into the separate method:

始终尝试将您的复杂问题分解为简单且独立的部分。这里(对于流和非流解决方案)最好将转义代码放入单独的方法中:

static String quoteValue(String value) {
    return "\"" + value.replaceAll("\"", "\\\"") + "\"";
}

This way you have a separate piece of code with clear semantic which can be reused and tested separately. For example, in future you may need to escape a back-slash symbol as well, because currently you may have problems decoding the string which originally had the mix of back-slashes and quotes. If would be much easier to debug this problem if you have a separate quoteValuemethod (this way you should not create the test map, just test string).

这样你就有了一段单独的、语义清晰的代码,可以单独重用和测试。例如,将来您可能还需要转义反斜杠符号,因为目前您可能在解码最初混合了反斜杠和引号的字符串时遇到问题。如果你有一个单独的quoteValue方法(这样你不应该创建测试映射,只创建测试字符串),调试这个问题会容易得多。

After that the stream solution becomes less confusing:

之后,流解决方案变得不那么令人困惑:

map.entrySet().stream().map(entry ->
        "--" + entry.getKey() + " "+ quoteValue(entry.getValue()))
        .collect(joining(" "));

You can go further and add one more method to format the whole entry as well:

您还可以进一步添加一种方法来格式化整个条目:

static String formatEntry(String key, String value) {
    return "--" + key + " " + quoteValue(value);
}

map.entrySet().stream().map(e -> formatEntry(e.getKey(), e.getValue()))
                       .collect(joining(" "));