switchMap
类似于flatMap
,但它只会从新的可观察对象中发出项目,直到从源可观察对象发出新事件。以下是该操作符的示意图:switchMap
的示意图中,第二个原始发射(绿色大理石)没有发出第二个映射发射(绿色方块),因为第三个原始发射(蓝色大理石)已经开始并发出了其第一个映射发射(蓝色钻石)。换句话说,只有第一个映射的绿色发射发生;没有发出绿色方块,因为蓝色钻石超过了它。flatMap
,所有映射的结果都会被发出,即使它们是“过时”的。换句话说,第一个和第二个映射的绿色发射都会发生,如果它们使用一致的映射函数,则会发出绿色方块;由于它们没有使用一致的映射函数,因此你看到第二个绿色钻石,即使它是在第一个蓝色钻石之后发出的。flatMap
操作符示意图如下:.map(func).switch
,但这与 .switchMap(func)
是相同的。 - Samuel Gruetter我在实现“即时搜索”时遇到了这个问题 - 即当用户在文本框中输入时,结果会随着每次按键而几乎即时地出现。解决方法似乎是:
使用flatMap时,搜索结果可能会过期,因为搜索响应可能无序返回。为了解决这个问题,应该使用switchMap,因为它确保一旦提供新的可观察对象,旧的可观察对象就会取消订阅。
因此,总之,当所有结果都很重要且不考虑时间时应使用flatMap,并且只有最后一个Observale的结果很重要时才应使用switchMap。
没有 flatMap 的讨论是不完整的,需要与 switchMap、concatMap 和 concatMapEager 进行比较和对比。
所有这些方法都接受一个 Func1,将流转换为 Observable,并发出它们;区别在于返回的 Observable 何时被订阅和取消订阅,以及这些 Observable 的发射是否由相关的 ____Map 操作符发出。
flatMap
订阅尽可能多的发射的 Observable
(这是一个平台相关的数字,例如在 Android 上较低),当顺序不重要且您希望尽快发出时使用。concatMap
订阅第一个 Observable
,并仅在前一个完成后才订阅下一个 Observable
。当顺序很重要且您想节省资源时使用。一个完美的例子是通过首先检查缓存来延迟网络调用。这通常可以跟随一个 .first()
或 .takeFirst()
以避免做不必要的工作。
http://blog.danlew.net/2015/06/22/loading-data-from-multiple-sources-with-rxjava/
concatMapEager
的工作方式与之相似,但会订阅尽可能多的 Observable
(取决于平台),但只有在前一个 Observable
完成后才会发出。非常适合需要大量并行处理的情况,但是(与 flatMap 不同)您希望保持原始顺序。
switchMap
将订阅它遇到的最后一个 Observable
,并取消订阅所有先前的 Observable
。这非常适合类似搜索建议的情况:一旦用户更改了搜索查询,旧请求就不再有任何兴趣,因此它被取消订阅,并且良好的 API 端点将取消网络请求。switchMap 在 RxJS 4 中曾经被称为 flatMapLatest。
它基本上只是从 最新 的 Observable 中传递事件并取消订阅先前的 Observable。
Map(映射)、FlatMap(扁平映射)、ConcatMap(连接映射)和SwitchMap(切换映射)都会对Observable发出的数据应用一个函数或修改数据。
Map 修改源Observable发出的每个项目,并发出修改后的项目。
FlatMap、SwitchMap和ConcatMap也会在每个发出的项目上应用一个函数,但不同于返回已修改的项目,它们返回可再次发出数据的Observable本身。
FlatMap 和 ConcatMap 的工作基本相同。它们合并多个Observables发出的项目并返回单个Observable。
这里有一个长达101行的例子,它为我解释了这个事情。
就像所说的那样:它获取最后一个observable(如果你愿意的话,就是最慢的observable),并忽略其余的observable。
因此:
Time | scheduler | state
----------------------------
0 | main | Starting
84 | main | Created
103 | main | Subscribed
118 | Sched-C-0 | Going to emmit: A
119 | Sched-C-1 | Going to emmit: B
119 | Sched-C-0 | Sleep for 1 seconds for A
119 | Sched-C-1 | Sleep for 2 seconds for B
1123 | Sched-C-0 | Emitted (A) in 1000 milliseconds
2122 | Sched-C-1 | Emitted (B) in 2000 milliseconds
2128 | Sched-C-1 | Got B processed
2128 | Sched-C-1 | Completed
你会发现 A 被忽略了。
代码示例
import io.reactivex.rxjava3.core.Observable;
import io.reactivex.rxjava3.schedulers.Schedulers;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.Test;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.stream.IntStream;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class SwitchMapTest {
Logger logger = LogManager.getLogger();
@Test
public void main() throws InterruptedException {
log("main thread");
CountDownLatch latch = new CountDownLatch(1);
var disposable = Observable
.create(emitter -> {
IntStream.range(0, 4)
.peek(i -> {
log("sleep emit");
sleep(TimeUnit.SECONDS, 1);
})
.forEach(emitter::onNext);
emitter.onComplete();
})
.subscribeOn(Schedulers.io())
.switchMap(o ->
Observable.create(emitter -> {
IntStream.range(0, 2).forEach(value -> {
log("sleep switch");
sleep(TimeUnit.MILLISECONDS, 900);
emitter.onNext("original " + o + " | switchMap " + value);
});
emitter.onComplete();
})
.subscribeOn(Schedulers.from(Executors.newSingleThreadExecutor(r -> {
Thread thread = new Thread(r);
thread.setDaemon(true);
return thread;
})))
)
.observeOn(Schedulers.newThread())
.subscribe(this::log, throwable -> logger.throwing(throwable), () -> {
log("complete");
latch.countDown();
});
boolean await = latch.await(10, TimeUnit.SECONDS);
assertTrue(await);
disposable.dispose();
}
private void sleep(@NotNull TimeUnit timeUnit, int timeout) {
try {
timeUnit.sleep(timeout);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
void log(Object message) {
logger.debug(message);
}
}
log4j2.xml
<Configuration status="WARN">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%-8r %d{HH:mm:ss.SSS} [%-32t] %-5level %logger{36} - %msg%n"/>
</Console>
</Appenders>
<Loggers>
<Root level="ALL">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>
控制台
720 21:44:46.566 [Test worker ] DEBUG SwitchMapTest - main thread
787 21:44:46.633 [RxCachedThreadScheduler-1 ] DEBUG SwitchMapTest - sleep emit
1789 21:44:47.635 [RxCachedThreadScheduler-1 ] DEBUG SwitchMapTest - sleep emit
1790 21:44:47.636 [Thread-3 ] DEBUG SwitchMapTest - sleep switch
2695 21:44:48.541 [Thread-3 ] DEBUG SwitchMapTest - sleep switch
2695 21:44:48.541 [RxNewThreadScheduler-1 ] DEBUG SwitchMapTest - original 0 | switchMap 0
2792 21:44:48.638 [RxCachedThreadScheduler-1 ] DEBUG SwitchMapTest - sleep emit
2792 21:44:48.638 [Thread-4 ] DEBUG SwitchMapTest - sleep switch
3693 21:44:49.539 [Thread-4 ] DEBUG SwitchMapTest - sleep switch
3693 21:44:49.539 [RxNewThreadScheduler-1 ] DEBUG SwitchMapTest - original 1 | switchMap 0
3796 21:44:49.642 [RxCachedThreadScheduler-1 ] DEBUG SwitchMapTest - sleep emit
3797 21:44:49.643 [Thread-5 ] DEBUG SwitchMapTest - sleep switch
4699 21:44:50.545 [Thread-5 ] DEBUG SwitchMapTest - sleep switch
4699 21:44:50.545 [RxNewThreadScheduler-1 ] DEBUG SwitchMapTest - original 2 | switchMap 0
4802 21:44:50.648 [Thread-6 ] DEBUG SwitchMapTest - sleep switch
5706 21:44:51.552 [Thread-6 ] DEBUG SwitchMapTest - sleep switch
5706 21:44:51.552 [RxNewThreadScheduler-1 ] DEBUG SwitchMapTest - original 3 | switchMap 0
6612 21:44:52.458 [RxNewThreadScheduler-1 ] DEBUG SwitchMapTest - original 3 | switchMap 1
6612 21:44:52.458 [RxNewThreadScheduler-1 ] DEBUG SwitchMapTest - complete
如果你正在寻找示例代码
/**
* We switch from original item to a new observable just using switchMap.
* It´s a way to replace the Observable instead just the item as map does
* Emitted:Person{name='Pablo', age=0, sex='no_sex'}
*/
@Test
public void testSwitchMap() {
Observable.just(new Person("Pablo", 34, "male"))
.switchMap(person -> Observable.just(new Person("Pablo", 0, "no_sex")))
.subscribe(System.out::println);
}
您可以在这里查看更多示例 https://github.com/politrons/reactive
switchMap
替换成 flatMap
时,它会完全相同地工作。 - Piotr Wittchen