我有一个哈希映射:Map<String, Set<String>> myMap
我想将它拆分成包含Map
的列表:
List<Map<String,Set<String>>> listofMaps;
每个map最多只能有100个key。
我知道如何用常规方法实现这个功能..(对entrySet进行foreach,每100个项目创建新的map)。
是否有使用java 8 lambda或其他方法来实现的选项?(类似于Lists.partitions()
..)?
我有一个哈希映射:Map<String, Set<String>> myMap
我想将它拆分成包含Map
的列表:
List<Map<String,Set<String>>> listofMaps;
每个map最多只能有100个key。
我知道如何用常规方法实现这个功能..(对entrySet进行foreach,每100个项目创建新的map)。
是否有使用java 8 lambda或其他方法来实现的选项?(类似于Lists.partitions()
..)?
使用我从这个答案中提供的unorderedBatches()
收集器:
Collector<Entry<String, Set<String>>, ?, List<Map<String, Set<String>>>> batchesCollector =
unorderedBatches(100,
Collectors.toMap(Entry::getKey, Entry::getValue), Collectors.toList());
List<Map<String, Set<String>>> listofMaps = myMap.entrySet().stream()
.collect(batchesCollector);
将流分成有序的固定大小块(例如Lists.partition
)是不可能的,因为在并行执行中,每个块都必须等待其左侧空间块完全处理。
然而,如果您不关心结果子映射中键的顺序(就像Map#iterator
方法返回的那样),则可以使用自定义收集器。
private static <K, V> Collector<Map.Entry<K, V>, ?, List<Map<K, V>>> mapSize(int limit) {
return Collector.of(ArrayList::new,
(l, e) -> {
if (l.isEmpty() || l.get(l.size() - 1).size() == limit) {
l.add(new HashMap<>());
}
l.get(l.size() - 1).put(e.getKey(), e.getValue());
},
(l1, l2) -> {
if (l1.isEmpty()) {
return l2;
}
if (l2.isEmpty()) {
return l1;
}
if (l1.get(l1.size() - 1).size() < limit) {
Map<K, V> map = l1.get(l1.size() - 1);
ListIterator<Map<K, V>> mapsIte = l2.listIterator(l2.size());
while (mapsIte.hasPrevious() && map.size() < limit) {
Iterator<Map.Entry<K, V>> ite = mapsIte.previous().entrySet().iterator();
while (ite.hasNext() && map.size() < limit) {
Map.Entry<K, V> entry = ite.next();
map.put(entry.getKey(), entry.getValue());
ite.remove();
}
if (!ite.hasNext()) {
mapsIte.remove();
}
}
}
l1.addAll(l2);
return l1;
}
);
}
这个方法将map条目作为值,放入一个List<Map<K,V>>
中。
累加器会检查当前列表是否为空或者上一个map的大小是否已达到限制。如果是这种情况,就会添加一个新的map。然后将当前处理的条目的新映射添加到map中。
组合器需要组合并行构建的两个列表。如果其中一个列表为空,则返回另一个列表。如果不是这种情况,您需要检查第一个列表的最后一个map是否具有所需数量的元素。如果不是这种情况,我们获取第二个列表的最后一个map,并将元素添加到第一个列表的最后一个map中。如果达到限制或者没有更多来自第二个列表的元素可以添加,则停止。请不要忘记在所有元素都被消耗完毕时删除空map。
这样一个收集器的用途之一是:
List<Map<String, Set<String>>> listofMaps =
myMap.entrySet().stream().collect(mapSize(2));
以下是包含并行和串行流的一些示例,初始映射包含13个键值映射:
Size of maps 2
{11=[11a, 11b], 12=[12a, 12b]}
{13=[13b, 13a], 8=[8a, 8b]}
{1=[1a, 1b], 2=[2b, 2a]}
{3=[3a, 3b], 6=[6a, 6b]}
{4=[4a, 4b], 5=[5a, 5b]}
{7=[7a, 7b], 10=[10a, 10b]}
{9=[9a, 9b]}
=============================
Size of maps 5
{11=[11a, 11b], 12=[12a, 12b], 13=[13b, 13a], 6=[6a, 6b], 7=[7a, 7b]}
{1=[1a, 1b], 2=[2b, 2a], 3=[3a, 3b], 4=[4a, 4b], 5=[5a, 5b]}
{8=[8a, 8b], 9=[9a, 9b], 10=[10a, 10b]}
=============================
Size of maps 12
{11=[11a, 11b], 12=[12a, 12b], 1=[1a, 1b], 13=[13b, 13a], 2=[2b, 2a], 3=[3a, 3b], 4=[4a, 4b], 5=[5a, 5b], 6=[6a, 6b], 7=[7a, 7b], 8=[8a, 8b], 9=[9a, 9b]}
{10=[10a, 10b]}
=============================
Size of maps 15
{11=[11a, 11b], 12=[12a, 12b], 13=[13b, 13a], 1=[1a, 1b], 2=[2b, 2a], 3=[3a, 3b], 4=[4a, 4b], 5=[5a, 5b], 6=[6a, 6b], 7=[7a, 7b], 8=[8a, 8b], 9=[9a, 9b], 10=[10a, 10b]}
我尚未进行全面测试。此外,我认为您可以对其进行修改,使其更加通用。
例如,您可以接受任意对象,并使用两个函数为要处理的每个实例生成键和值。
private static <T, K, V> Collector<T, ?, List<Map<K, V>>> mapSize(Function<T, K> keyFunc, Function<T, V> mapFunc, int limit) {
使用
l.get(l.size() - 1).put(keyFunc.apply(e), mapFunc.apply(e));
然后像这样调用:
.collect(mapSize(Map.Entry::getKey, Map.Entry::getValue, size));
Lists.partition
,那是一个可行的方法,可能结合Java 8一起使用,不过你也可以使用Iterables.partition
。 - Louis Wasserman