Java 8 list to map with stream
我有一个List 集合。
我需要将其转换为Map
地图的关键字必须是集合中项目的索引。
我无法弄清楚如何使用流来做到这一点。
就像是:
1
| items.stream().collect(Collectors.toMap(...)); |
有帮助吗?
由于这个问题被确定为可能重复,我需要补充一点,我的具体问题是 - 如何获取列表中项目的位置并将其作为键值
-
也许或许或者也许
-
EntryStream.of(items).toMap();使用我的免费StreamEx库。 JavaDoc就在这里。
-
对这个问题进行了一些研究,我了解到java 8流中没有zip。
-
@ njzk2,这只是因为你无法并行化压缩流。 有随机访问源(例如,两个ArrayList),通过IntStream.range(0,list1.size()).mapToObj(idx -> doSomethingWith(list1.get(idx), list2.get(idx)))压缩它们并不是很困难,结果将是并行友好的
您可以使用IntStream创建Stream索引,然后将它们转换为Map:
1 2 3 4
| Map <Integer,Item > map =
IntStream. range(0,items. size())
. boxed()
. collect(Collectors. toMap (i -> i, i -> items. get(i ))); |
-
一旦items不再是List,它就会停止工作。并且如果items是例如LinkedList而不是ArrayList,则效率非常低。
-
@ njzk2如果您没有List或者索引访问费用昂贵,请查看我的答案。
另一个完整性的解决方案是使用自定义收集器:
1 2 3 4 5 6 7 8
| public static < T > Collector <T, ?, Map <Integer, T >> toMap () {
return Collector. of(HashMap::new, (map, t ) -> map. put(map. size(), t ),
(m1, m2 ) -> {
int s = m1. size();
m2. forEach((k, v ) -> m1. put(k +s, v ));
return m1 ;
});
} |
用法:
1
| Map <Integer, Item > map = items. stream(). collect(toMap ()); |
此解决方案是并行友好的,不依赖于源(您可以使用列表而无需随机访问或Files.lines()或其他)。
-
如果a / a组合器保证以正确的顺序使用m1和m2调用b /每个累积映射在连续的项目序列上调用。例如,如果在地图中累积奇数值,在另一个中累积值,则会中断。我没有找到任何暗示这种情况不会发生的消息来源。
-
@ njzk2,但如果订购了您的流,则无法实现。这样所有现有的收集器(如toList())实际上都可以工作。
-
我想这是有道理的。我将研究收集器如何在并行化发生后保证流的顺序。
-
@ njzk2,收集器合同在API文档中描述。我履行合同。当正确的map和new元素传递给累加器时,它会生成新的正确映射。当两个正确的映射传递给组合器时,它会生成新的正确映射。只要履行合同,您就会得到正确的结果。这就是界面之美。
-
谢谢。似乎流被分为子串,而不是子序列,所以确实有效!
不要觉得你必须在流中做任何事情。我会这样做:
1 2
| AtomicInteger index = new AtomicInteger();
items.stream().collect(Collectors.toMap(i -> index.getAndIncrement(), i -> i)); |
只要您不对流进行并行化,这将起作用,并且它避免了潜在的昂贵和/或有问题(在重复的情况下)get()和indexOf()操作。
(你不能使用常规的int变量来代替AtomicInteger,因为从lambda表达式外部使用的变量必须是有效的。注意,当无争议时(如本例所示),AtomicInteger非常快并且赢得了'这会造成性能问题。但如果它让您担心,您可以使用非线程安全的计数器。)
-
你打电话List.get()昂贵,但建议使用AtomicInteger?
-
@Holger号码我打电话List.get()可能很贵。 AtomicInteger是O(1),List.get()可以是任何高达O(n)的值。
-
仅当您使用LinkedList时才有用。另一方面,AtomicInteger O(1)与您自己承认的操作中的线程安全隐藏成本并不真正相关,并不能并行工作。如果您开始回答"不要觉得您必须在流中/处理流中的所有内容",为什么不提供无流量替代方案,如直接循环?这比提出不鼓励的流使用更好......
-
@Holger OP没有指定List实现。你似乎对LinkedList有偏见,但实际上它没有任何问题,List很容易就是一个,或者甚至可能是另一个更昂贵的实现。为什么要猜第二呢?这种方式总是最快的。
-
@Holger流的原因是OP专门要求它:"我无法弄清楚如何用流来做这件事。"关于AtomicInteger:当无争议时,它实际上与递增常规变量一样快;没有"隐藏成本"。
-
我不会对LinkedList产生偏见,因为它已经存在超过15年了,这足以确定它在现实生活中没用。理论上的优势只是一个操作,插入一个任意索引,但由于它必须为此分配内存并更新六个节点引用,这种优势并没有真正实现。它需要非常大的列表才能超越ArrayList,但是对于大型列表,LinkedList的疯狂内存开销将反作用。 LinkedList仅在忽略记忆效应的O(…)比较中获胜
-
@Holger这只是更理论和无关的第二次猜测。关键是OP应该使用List的哪个实现并不重要,并且使用此解决方案,它不会。
-
LinkedList.size()是O(1)。
-
@ Solomonoff'sSecret你的观点是什么?
-
@PepijnSchmitz你说"List.get()可能是O(n)之前的任何东西"而Holger回答"只有你使用了LinkedList",我认为暗示他认为LinkedList.get()是O(n) 。实际上它是,但更具体地说它也是O(1)。如果我误解了,道歉。
-
@ Solomonoff'sSecret LinkedList.get()是O(n)。但你说size(),而不是get()。
-
@PepijnSchmitz当然。显然我一再说不出"大小"和"得到"这两个词的区别......
这是更新的答案,没有评论中提到的问题。
1
| Map <Integer,Item > outputMap = IntStream. range(0,inputList. size()). boxed(). collect(Collectors. toMap(Function. identity(), i ->inputList. get(i ))); |
-
如果在列表中重复Item,则会失败。
-
不要对大型列表执行此操作。除非您想通过示例学习,O(n²)意味着......
-
list.indexOf(i)很慢。我不建议采用这种方法。
-
这种方法存在太多限制和低效率,因此它是一种有用的解决方案
Eran的答案通常是随机访问列表的最佳方法。
如果您的List不是随机访问,或者您有Stream而不是List,则可以使用forEachOrdered:
1 2 3 4
| Stream <Item > stream = ... ;
Map <Integer, Item > map = new HashMap <>();
AtomicInteger index = new AtomicInteger ();
stream. forEachOrdered(item -> map. put(index. getAndIncrement(), item )); |
如果流是并行的,这是安全的,即使目标地图是线程不安全的并且作为副作用进行操作。 forEachOrdered保证按顺序一次一个地处理项目。因此,并行运行不会产生任何加速。 (如果在forEachOrdered之前管道中存在昂贵的操作,可能会有一些加速。)
-
为什么这么复杂?使用forEachOrdered,您不需要AtomicInteger,只需使用stream.forEachOrdered(item -> map.put(map.size(), item))即可。读取无论如何更新的非易失性字段HashMap.size并不比在AtomicInteger中使用CAS更糟糕。
-
@TagirValeev我想是的。我的要点是使用未提及的forEachOrdered。
-
当然这是一个很短的解决方案,尽管使用我的版本中提出的Collector在概念上更正确。实际上,最好的解决方案是使用toList()并编写一个特殊的适配器(基于AbstractMap),它适应List< T >到Map。将它们存储到HashMap只是浪费时间和记忆。
使用第三方库(例如,质子包,但还有其他库),您可以zip使用其索引和瞧的值:
1 2
| StreamUtils.zipWithIndex(items.stream())
.collect(Collectors.toMap(Indexed::getIndex, Indexed::getValue)); |
虽然getIndex返回long,但您可能需要使用类似于:
1
| i -> Integer. valueOf((int) i. getIndex()) |
-
使用@TagirValeev的库,它就像:EntryStream.of(items).toMap();一样简单。
-
@ Jean-Fran?oisSavard,更不用说zipWithIndex创建了根本无法并行化的流。
-
@TagirValeev你有信心EntryStream可以吗?
-
@ njzk2,肯定是。然而,正如文档所说,它依赖于快速随机访问。在内部,它类似于Eran解决方案(它也是可并行化的,并且仅对随机访问源以合理的速度工作)。相比之下,质子包解决方案不需要随机访问(粗略地说它更接近Pepijn Schmitz答案)。
-
@TagirValeev相同的注释适用,然后:它只适用于List,其复杂性取决于列表实现的get(i)的访问时间。