67

说我有一个Map<? extends Object, List<String>>

我可以很容易地获取地图的值,并对其进行迭代以生成单个List<String>.

   for (List<String> list : someMap.values()) {
        someList.addAll(list);
    }

有没有办法一次性把它弄平?

  List<String> someList = SomeMap.values().flatten();
4

9 回答 9

79

使用 Java 8,如果您不想自己实例化一个List实例,就像在建议的(和接受的)解决方案中一样

someMap.values().forEach(someList::addAll);

您可以通过使用以下语句进行流式传输来完成所有操作:

List<String> someList = map.values().stream().flatMap(c -> c.stream()).collect(Collectors.toList());

顺便说一句,应该很有趣的是,在 Java 8 上,接受的版本似乎确实是最快的。它的时间与

for (List<String> item : someMap.values()) ...

并且比纯流式解决方案更快。这是我的小测试代码。我明确不将其命名为基准,以避免由此产生的基准缺陷讨论。;) 我每次测试都做了两次,希望能得到一个完整的编译版本。

    Map<String, List<String>> map = new HashMap<>();
    long millis;

    map.put("test", Arrays.asList("1", "2", "3", "4"));
    map.put("test2", Arrays.asList("10", "20", "30", "40"));
    map.put("test3", Arrays.asList("100", "200", "300", "400"));

    int maxcounter = 1000000;
    
    System.out.println("1 stream flatmap");
    millis = System.currentTimeMillis();
    for (int i = 0; i < maxcounter; i++) {
        List<String> someList = map.values().stream().flatMap(c -> c.stream()).collect(Collectors.toList());
    }
    System.out.println(System.currentTimeMillis() - millis);
    
    System.out.println("1 parallel stream flatmap");
    millis = System.currentTimeMillis();
    for (int i = 0; i < maxcounter; i++) {
        List<String> someList = map.values().parallelStream().flatMap(c -> c.stream()).collect(Collectors.toList());
    }
    System.out.println(System.currentTimeMillis() - millis);

    System.out.println("1 foreach");
    millis = System.currentTimeMillis();
    for (int i = 0; i < maxcounter; i++) {
        List<String> mylist = new ArrayList<String>();
        map.values().forEach(mylist::addAll);
    }
    System.out.println(System.currentTimeMillis() - millis);        

    System.out.println("1 for");
    millis = System.currentTimeMillis();
    for (int i = 0; i < maxcounter; i++) {
        List<String> mylist = new ArrayList<String>();
        for (List<String> item : map.values()) {
            mylist.addAll(item);
        }
    }
    System.out.println(System.currentTimeMillis() - millis);
    
    
    System.out.println("2 stream flatmap");
    millis = System.currentTimeMillis();
    for (int i = 0; i < maxcounter; i++) {
        List<String> someList = map.values().stream().flatMap(c -> c.stream()).collect(Collectors.toList());
    }
    System.out.println(System.currentTimeMillis() - millis);
    
    System.out.println("2 parallel stream flatmap");
    millis = System.currentTimeMillis();
    for (int i = 0; i < maxcounter; i++) {
        List<String> someList = map.values().parallelStream().flatMap(c -> c.stream()).collect(Collectors.toList());
    }
    System.out.println(System.currentTimeMillis() - millis);
    
    System.out.println("2 foreach");
    millis = System.currentTimeMillis();
    for (int i = 0; i < maxcounter; i++) {
        List<String> mylist = new ArrayList<String>();
        map.values().forEach(mylist::addAll);
    }
    System.out.println(System.currentTimeMillis() - millis);        

    System.out.println("2 for");
    millis = System.currentTimeMillis();
    for (int i = 0; i < maxcounter; i++) {
        List<String> mylist = new ArrayList<String>();
        for (List<String> item : map.values()) {
            mylist.addAll(item);
        }
    }
    System.out.println(System.currentTimeMillis() - millis);

结果如下:

1 stream flatmap
468
1 parallel stream flatmap
1529
1 foreach
140
1 for
172
2 stream flatmap
296
2 parallel stream flatmap
1482
2 foreach
156
2 for
141

编辑 2016-05-24(两年后):

在同一台机器上使用实际的 Java 8 版本 (U92) 运行相同的测试:

1 stream flatmap
313
1 parallel stream flatmap
3257
1 foreach
109
1 for
141
2 stream flatmap
219
2 parallel stream flatmap
3830
2 foreach
125
2 for
140

流的顺序处理似乎有加速,并行流的开销更大。

编辑 2018-10-18(四年后):

现在在同一台机器上使用 Java 10 版本(10.0.2):

1 stream flatmap
393
1 parallel stream flatmap
3683
1 foreach
157
1 for
175
2 stream flatmap
243
2 parallel stream flatmap
5945
2 foreach
128
2 for
187

并行流的开销似乎更大。

编辑 2020-05-22(六年后):

现在在另一台机器上使用 Java 14 版本(14.0.0.36):

1 stream flatmap
299
1 parallel stream flatmap
3209
1 foreach
202
1 for
170
2 stream flatmap
178
2 parallel stream flatmap
3270
2 foreach
138
2 for
167

确实应该指出,这是在另一台机器上完成的(但我认为是可比的)。并行流的开销似乎比以前小得多。

于 2014-04-30T13:52:14.407 回答
58

如果您使用的是 Java 8,则可以执行以下操作:

someMap.values().forEach(someList::addAll);
于 2013-08-17T16:28:51.247 回答
38

当搜索“java 8 flatten”时,这是唯一的提及。这也不是关于扁平化流。所以为了很好,我把它留在这里

.flatMap(Collection::stream)

我也很惊讶没有人对原始问题给出并发 java 8 答案,即

.collect(ArrayList::new, ArrayList::addAll, ArrayList::addAll);
于 2015-05-04T22:58:22.113 回答
8

同事推荐:

listOfLists.stream().flatMap(e -> e.stream()).collect(Lists.toList())

我比 forEach() 更喜欢它。

于 2017-01-19T21:08:01.863 回答
7

如果您使用的是Eclipse Collections,则可以使用Iterate.flatten()

MutableMap<String, MutableList<String>> map = Maps.mutable.empty();
map.put("Even", Lists.mutable.with("0", "2", "4"));
map.put("Odd", Lists.mutable.with("1", "3", "5"));
MutableList<String> flattened = Iterate.flatten(map, Lists.mutable.empty());
Assert.assertEquals(
    Lists.immutable.with("0", "1", "2", "3", "4", "5"),
    flattened.toSortedList());

flatten()是更一般的RichIterable.flatCollect()的一个特例。

MutableList<String> flattened = 
    map.flatCollect(x -> x, Lists.mutable.empty());

注意:我是 Eclipse Collections 的提交者。

于 2013-08-20T02:42:06.520 回答
6

不,没有更短的方法。您必须使用循环。

2014 年 4 月更新: Java 8 终于问世了。在新版本中,您可以使用该Iterable.forEach方法遍历集合,而无需使用显式循环。

2017 年 11 月更新:在寻找现代解决方案时偶然发现了这个问题。结束了reduce

someMap.values().stream().reduce(new ArrayList(), (accum, list) -> {
    accum.addAll(list);
    return accum;
}):

这避免了依赖于可变外部状态forEach(someList::addAll)的开销flatMap(List::stream)

于 2013-08-17T16:25:15.800 回答
0

如果您只想遍历值,则可以避免所有这些 addAll 方法。

您所要做的就是编写一个封装您的 Map 并实现 Iterator 的类:

public class ListMap<K,V> implements Iterator<V>
{
  private final Map<K,List<V>> _map;
  private Iterator<Map.Entry<K,List<V>>> _it1 = null;
  private Iterator<V> _it2 = null;

  public ListMap(Map<K,List<V>> map)
  {
    _map = map;
    _it1 = map.entrySet().iterator(); 
    nextList();
  }

  public boolean hasNext()
  {
    return _it2!=null && _it2.hasNext();
  }

  public V next()
  {
    if(_it2!=null && _it2.hasNext())
    {
      return _it2.next();
    }
    else
    {
      throw new NoSuchElementException();
    }
    nextList();
  } 

  public void remove()
  {
    throw new NotImplementedException();
  }

  private void nextList()
  {
    while(_it1.hasNext() && !_it2.hasNext())
    {
      _it2 = _it1.next().value();
    }
  }
}
于 2013-08-18T17:16:44.457 回答
0

对于 Map of Maps 的子案例,一个不错的解决方案是尽可能将数据存储在 Guava 的Table.

https://github.com/google/guava/wiki/NewCollectionTypesExplained#table

因此,例如 aMap<String,Map<String,String>>被替换Table<String,String,String>为已经变平的。事实上,文档说HashBasedTable,Table的 Hash 实现基本上是由HashMap<R, HashMap<C, V>>

于 2016-03-02T08:02:31.383 回答
0

在函数上展平:

    private <A, T> List<T> flatten(List<A> list, Function<A, List<T>> flattenFn) {
        return list
                .stream()
                .map(flattenFn)
                .flatMap(Collection::stream)
                .collect(Collectors.toUnmodifiableList());
    }
于 2020-05-22T19:31:49.510 回答