如何对单个流的元素执行多个不相关的操作?
说我有一个由文本组成的List<String>
。列表中的每个字符串可能包含也可能不包含某个单词,表示要执行的操作。让我们说:
List<String>
当然,我可以这样做:
List<String> strs = ...;
List<Integer> wordsInStr = strs.stream()
.filter(t -> t.contains("of"))
.map(t -> t.split(" ").length)
.collect(Collectors.toList());
List<String> linePortionAfterFor = strs.stream()
.filter(t -> t.contains("for"))
.map(t -> t.substring(t.indexOf("for")))
.collect(Collectors.toList());
但随后列表将被遍历两次,如果strs
包含大量元素,则可能导致性能下降。
是否有可能以某种方式执行这两个操作而不在列表上遍历两次?
答案 0 :(得分:5)
如果您想要一次通过.keys()
,则必须使用自定义Stream
(可能并行化)。
Collector
答案 1 :(得分:4)
以下是从不同方面解决OP的答案。首先,让我们来看看迭代列表/集合的速度有多快/慢。以下是我的机器上的测试结果:
时间:字符串列表长度= 100,线程数= 1,循环= 1000,单位=毫秒
OP:0.013
接受的答案:0.020
通过计数器功能:0.010
时间:字符串列表长度= 1000_000,线程数= 1,循环= 100,单位=毫秒
OP:99.387
接受的答案:89.848
通过计数器功能:59.183
结论:性能提升的百分比非常小甚至更慢(如果字符串列表的长度很小)。通常,减少由更复杂的收集器加载到内存中的列表/集合的迭代是错误的。你没有获得太多的性能提升。如果存在性能问题,我们应该调查其他地方。
以下是我使用工具Profiler的性能测试代码:(我不打算在此讨论如何进行性能测试。如果您怀疑测试结果,可以使用任何工具再次执行此操作你相信)
@Test
public void test_46539786() {
final int strsLength = 1000_000;
final int threadNum = 1;
final int loops = 100;
final int rounds = 3;
final List<String> strs = IntStream.range(0, strsLength).mapToObj(i -> i % 2 == 0 ? i + " of " + i : i + " for " + i).toList();
Profiler.run(threadNum, loops, rounds, "OP", () -> {
List<Integer> wordsInStr = strs.stream().filter(t -> t.contains("of")).map(t -> t.split(" ").length).collect(Collectors.toList());
List<String> linePortionAfterFor = strs.stream().filter(t -> t.contains("for")).map(t -> t.substring(t.indexOf("for")))
.collect(Collectors.toList());
assertTrue(wordsInStr.size() == linePortionAfterFor.size());
}).printResult();
Profiler.run(threadNum, loops, rounds, "Accepted answer", () -> {
Splitter collect = strs.stream().collect(Collector.of(Splitter::new, Splitter::accept, Splitter::merge));
assertTrue(collect.counts.size() == collect.words.size());
}).printResult();
final Function<String, Integer> counter = s -> {
int count = 0;
for (int i = 0, len = s.length(); i < len; i++) {
if (s.charAt(i) == ' ') {
count++;
}
}
return count;
};
Profiler.run(threadNum, loops, rounds, "By the counter function", () -> {
List<Integer> wordsInStr = strs.stream().filter(t -> t.contains("of")).map(counter).collect(Collectors.toList());
List<String> linePortionAfterFor = strs.stream().filter(t -> t.contains("for")).map(t -> t.substring(t.indexOf("for")))
.collect(Collectors.toList());
assertTrue(wordsInStr.size() == linePortionAfterFor.size());
}).printResult();
}
答案 2 :(得分:1)
您可以使用自定义收集器并仅迭代一次:
private static <T, R> Collector<String, ?, Pair<List<String>, List<Long>>> multiple() {
class Acc {
List<String> strings = new ArrayList<>();
List<Long> longs = new ArrayList<>();
void add(String elem) {
if (elem.contains("of")) {
long howMany = Arrays.stream(elem.split(" ")).count();
longs.add(howMany);
}
if (elem.contains("for")) {
String result = elem.substring(elem.indexOf("for"));
strings.add(result);
}
}
Acc merge(Acc right) {
longs.addAll(right.longs);
strings.addAll(right.strings);
return this;
}
public Pair<List<String>, List<Long>> finisher() {
return Pair.of(strings, longs);
}
}
return Collector.of(Acc::new, Acc::add, Acc::merge, Acc::finisher);
}
用法是:
Pair<List<String>, List<Long>> pair = Stream.of("t of r m", "t of r m", "nice for nice nice again")
.collect(multiple());
答案 3 :(得分:0)
如果您希望通过列表拥有1个流,则需要一种方法来管理2个不同的状态,您可以通过将Consumer实现为新类来实现。
class WordsInStr implements Consumer<String> {
ArrayList<Integer> list = new ArrayList<>();
@Override
public void accept(String s) {
Stream.of(s).filter(t -> t.contains("of")) //probably would be faster without stream here
.map(t -> t.split(" ").length)
.forEach(list::add);
}
}
class LinePortionAfterFor implements Consumer<String> {
ArrayList<String> list = new ArrayList<>();
@Override
public void accept(String s) {
Stream.of(s) //probably would be faster without stream here
.filter(t -> t.contains("for"))
.map(t -> t.substring(t.indexOf("for")))
.forEach(list::add);
}
}
WordsInStr w = new WordsInStr();
LinePortionAfterFor l = new LinePortionAfterFor();
strs.stream()//stream not needed here
.forEach(w.andThen(l));
System.out.println(w.list);
System.out.println(l.list);