如何将"a","b","c“这样的集合转换为{"a":0,"b":1,"c":2}这样的映射,值是迭代的顺序。在JDK8中有没有包含流和收集器的一体机?老办法是这样的:
Collection<String> col = apiCall();
Map<String, Integer> map = new HashMap<>();
int pos = 0;
for (String s : collection) {
map.put(s, pos++);
}
发布于 2014-07-31 00:01:11
如果你不需要并行流,你可以使用map的长度作为索引计数器:
collection.stream().forEach(i -> map.put(i, map.size() + 1));
发布于 2014-07-31 01:08:46
这是一种方法:
List<String> list = Arrays.asList("a", "b", "c");
Map<String, Integer> map =
IntStream.range(0, list.size())
.boxed()
.collect(toMap(i -> list.get(i), i -> i));
不一定是一行或比直接循环更短的循环,但如果您将toMap
更改为toConcurrentMap
,它确实可以使用并行流。
还要注意,这里假设您有一个随机访问列表,而不是一个通用的Collection
。如果您有一个无法对其进行任何假设的Collection
,那么除了顺序地迭代它并递增一个计数器之外,您没有什么可做的。
更新
OP已经澄清了输入是Collection
而不是List
,所以上面的内容不适用。似乎我们对输入Collection
的假设很少。操作已指定迭代顺序。使用顺序迭代器,元素将以某种顺序出现,尽管不能保证这一点。它可能会从一个运行到另一个运行,甚至从一个迭代到下一个迭代(尽管这在实践中是不常见的--除非底层集合被修改)。
如果需要保留确切的迭代顺序,我认为没有办法在不按顺序迭代输入Collection
的情况下将其保留到结果Map
中。
但是,如果准确的迭代顺序并不重要,并且要求输出Map
对每个输入元素都有唯一的值,则可以并行执行以下操作:
Collection<String> col = apiCall();
Iterator<String> iter = col.iterator();
Map<String, Integer> map =
IntStream.range(0, col.size())
.parallel()
.boxed()
.collect(toConcurrentMap(i -> { synchronized (iter) { return iter.next(); }},
i -> i));
现在,这远远不是一行代码。我也不清楚它有多有用。:-)但它确实证明了可以并行地做这样的事情。请注意,我们必须同步对输入集合的迭代器的访问,因为它将从多个线程调用。还要注意,这是迭代器的一种不寻常的用法,因为我们从未调用过hasNext
,并且我们假设调用next
的次数与输入集合的size()
返回的次数完全相同是安全的。
发布于 2014-07-31 23:23:46
基于maba’s answer的一般解决方案是:
collection.stream().forEachOrdered(i -> map.put(i, map.size()));
从documentation of void forEachOrdered(Consumer action)
此操作一次处理一个元素,如果存在元素,则按相遇顺序处理。
这里重要的方面是,如果存在一个顺序,例如,如果Collection
是SortedSet
或List
,它将保留顺序。这样的流被称为有序流(不要与排序流混淆)。它可以通过不同的线程调用使用者方法,但始终确保“一次一个”和线程安全保证。
当然,如果流是并行的,那么它不会从并行执行中受益。
为了完整起见,这里是一个解决方案,即使在使用并行处理的并行流上也可以工作,如果它们仍然是有序的
stream.collect(HashMap::new, (m, i) -> m.put(i, m.size()),
(a, b) -> {int offset = a.size(); b.forEach((k, v) -> a.put(k, v + offset));});
https://stackoverflow.com/questions/25041177
复制相似问题