第一章 Thinking Reactively(响应式的思考)

假定您相当熟悉Java并且知道如何使用类,接口,方法,属性,变量,静态/非静态作用域和集合。 如果您还没有完成并发或多线程,那就可以了。 RxJava使这些高级主题更加易于访问。

准备好您喜欢的Java开发环境,无论是Intellij IDEA,Eclipse,NetBeans还是您选择的任何其他环境。 我将使用Intellij IDEA,它不会影响本书中的示例。 我建议您也拥有一个构建自动化系统,例如Gradle或Maven,我们将在不久的将来进行逐步介绍。

在深入研究RxJava之前,我们将首先介绍一些核心主题:

1、响应式扩展和RxJava的简要历史

2、响应式的思考

3、利用RxJava

4、设置您的第一个RxJava项目

5、构建您的第一个响应式应用程序

6、RxJava 1.0和RxJava 2.0之间的区别

响应式扩展和RxJava的简要历史

作为开发人员,我们倾向于训练自己以违反直觉的方式思考。用代码为我们的世界建模从来都不乏挑战。不久前,面向对象编程被视为解决此问题的灵丹妙药。对我们在现实生活中与之互动的事物进行蓝图是一个革命性的想法,而类和对象的核心概念仍然影响着我们今天的编码方式。但是,业务和用户需求继续变得越来越复杂。随着2010年的临近,很明显,面向对象的编程只能解决部分问题。

类和对象在用属性和方法表示实体方面做得很好,但是当它们需要以越来越复杂(且常常是计划外的)的方式相互交互时,它们就会变得混乱。出现了去耦模式和范例,但这产生了越来越多的样板代码的不良副作用。针对这些问题,函数式编程开始卷土重来,不是取代面向对象的编程,而是对其进行补充并填补了这一空白。响应式编程(一种功能性的事件驱动编程方法)开始引起人们的特别关注。

最终出现了两个响应式框架,包括Akka和Sodium。但是在Microsoft,一位名叫Erik Meijer的计算机科学家为.NET创建了一个名为Reactive Extensions的响应式编程框架。几年之内,Reactive Extensions(也称为ReactiveX或Rx)已移植到多种语言和平台,包括JavaScript,Python,C,Swift和Java。 ReactiveX迅速成为一种跨语言标准,将反应式编程带入了行业。

RxJava是Java的ReactiveX端口,大部分是由Netflix和David Karnok的Ben Christensen创建的。 RxJava 1.0于2014年11月发布,其后于2016年11月发布RxJava2.0。RxJava是其他ReactiveX JVM端口(例如RxScala,RxKotlin和RxGroovy)的骨干。它已经成为Android开发的核心技术,并且也已经进入Java后端开发。许多RxJava适配器库,例如RxAndroid(https://github.com/ReactiveX/RxAndroid)、RxJava-JDBC(https://github.com/davidmoten/rxjava-jdbc)、RxNetty(https://github.com/ReactiveX/RxNetty)和RxJavaFX(https://github.com/ReactiveX/RxJavaFX)适应了多个Java框架,使其变得可响应并可以立即使用RxJava,这一切都表明RxJava不仅仅是一个库。它是更大的ReactiveX生态系统的一部分,该生态系统代表了整个编程方法。 ReactiveX的基本思想是事件是数据,数据是事件。这是一个非常有力的概念,我们将在本章后面探讨,但首先,让我们退一步,通过响应式镜头来审视世界。

响应式的思考

将您对Java(以及一般而言的编程)所掌握的所有知识暂停一下,让我们对我们的世界进行一些观察。这些听起来似乎很明显,但是作为开发人员,我们可以轻松地忽略它们。让您注意一切都在运动的事实。交通,天气,人,对话,财务交易等都在移动。从技术上讲,由于地球的自转和轨道运动,即使是像岩石一样静止的东西也正在运动。当您考虑将所有事物建模为动态模型的可能性时,作为开发人员,您可能会觉得有些不知所措。

要注意的另一个观察结果是这些不同的事件是同时发生的,同时有多个活动在发生。有时,他们可以独立行动,而在其他时候,他们可以在某个时候聚合以进行交互。例如,一辆汽车可以行驶而不会影响慢跑的人。它们是两个独立的事件流。但是,它们可能会在某个时间汇合,并且汽车在遇到慢跑者时将停止。

如果这是我们的世界运作的方式,为什么我们不这样对代码建模?为什么我们不将代码建模为同时发生的多个并发事件或数据流?对于开发人员来说,花费更多的时间来管理对象的状态并以命令和顺序的方式进行操作并不少见。您可以构造代码以执行进程1,进程2,然后执行进程3,这取决于进程1和进程2。为什么不同时启动进程1和进程2,然后完成这两个事件就立即启动进程3。 ?当然,您可以使用回调和Javaconcurrency工具,但是RxJava使表达起来更加容易和安全。

让我们做最后一个观察。一本书或音乐CD是静态的。书是单词的不变序列,而CD是曲目的集合。它们没有动态关系,但是,当我们读一本书时,我们一次读一个单词。那些话语被我们的眼睛吞没了,有效地动了起来。对于音乐CD轨道而言,这没有什么不同,在CD轨道中,每个轨道都随着声波而运动,而您的耳朵正在消耗每个轨道。实际上,静态项目也可以移动。这是一个抽象的但有力的想法,因为我们使这些静态项目中的每一个成为一系列事件。当我们通过平等对待数据和事件之间的竞争环境时,我们释放了功能编程的能力,并释放了您以前可能认为不切实际的功能。

响应式编程背后的基本思想是事件是数据,数据是事件,这看似抽象,但在考虑现实世界中的示例时并不需要很长时间。 跑步者和赛车都有属性和状态,但它们也在运动中。 书籍和CD消耗后会立即移动。 将事件和数据合并为一体,可使代码自然而有机地代表世界磨损模型。

为什么要学习RxJava?

ReactiveX和RxJava广泛地解决了程序员日常面临的许多问题,使您可以表达业务逻辑并减少工程代码的时间。您是否曾经为并发,事件处理,过时的数据状态和异常恢复而苦苦挣扎?如何使您的代码更具可维护性,可重用性和可扩展性,使其可以跟上您的业务发展呢?对于这些问题,调用响应式编程无处不在可能是冒昧的,但是在解决这些问题时,这无疑是一个进步。

用户对使应用程序实时且响应迅速的需求也不断增长。借助反应式编程,您可以快速分析和处理实时数据源,例如Twitter提要或股票价格。它还可以取消和重定向工作,并发扩展,并处理快速发送的数据。将事件和数据组合为可以混合,合并,过滤,拆分和转换的流,为组合和演化代码提供了根本有效的方法。

总之,响应式编程使许多艰巨的任务变得容易,使您能够以您以前认为不切实际的方式增加价值。如果您有一个响应式的进程,并且发现需要在不同的线程上运行它的一部分,则可以在几秒钟内实现此更改。如果您发现网络连接问题间歇性地导致应用程序崩溃,则可以正常使用响应式恢复策略,然后再试一次。如果您需要在流程的中间插入操作,则就像插入新的运算符一样简单。响应式编程被分解成可以添加或删除的模块化链链接,这可以帮助快速克服所有上述问题。本质上,RxJava允许应用程序在保持生产稳定性的同时具有战术性和可扩展性。

我们将在这本书中学到什么?

如前所述,RxJava是Java的ReactiveX端口。在本书中,我们将主要关注RxJava 2.0,但我会指出RxJava 1.0的显着差异。我们将把学习放在第一位,以进行反应性思考并利用RxJava的实用功能。从高层次的理解开始,我们将逐步深入了解RxJava的工作方式。在此过程中,我们将学习反应模式和技巧,以解决程序员遇到的常见问题。

在第2章“观察和订阅者”,第3章“基本运算符”和第4章“结合可观察对象”中,我们将用Observable,Observer和Operator涵盖Rx的核心概念。这是组成RxJava应用程序的三个核心实体。您将立即开始编写响应式程序,并具有扎实的知识基础,可以在本书的其余部分继续进行。

第5章,多播,重放和缓存,以及第6章,并发和并行化,将探讨RxJava的更多细微差别以及如何有效利用并发性。

在第7章“切换,节流,窗口化和缓冲”以及第8章“可流动性和背压”中,我们将学习应对反应性流的各种方法,这些响应流产生数据/事件的速度快于消耗。

最后,第9章,《变形和自定义运算符》,第10章,测试和调试,第11章,Android上的RxJava,以及第12章,将RxJava与Kotlin New结合使用,将涉及多个其他(但必不可少的)主题,包括自定义运算符以及如何使用。将RxJava与测试框架,Android和Kotlin语言结合使用。

配置

当前有两种共存的RxJava版本:1.0和2.0。 稍后,我们将介绍一些主要差异,并讨论您应该使用哪个版本。

RxJava 2.0是一个相当轻量级的库,大小刚好超过2 MB,这使得它对于需要低依赖项开销的Android和其他项目非常实用。 RxJava 2.0只有一个依赖项,称为Reactive Streams(http://www.reactive-streams.org/),它是一个核心库(由RxJava的创建者制造),为异步流实现设置了标准,其中之一是 RxJava 2.0。

它可以在RxJava之外的其他库中使用,并且是Java平台上的响应式编程标准化的关键工作。请注意,RxJava 1.0没有任何依赖关系,包括Reactive Streams,这是在1.0之后实现的。

如果您是从头开始的项目,请尝试使用RxJava 2.0。这是我们将在本书中介绍的版本,但我会指出1.0版中的显着差异。尽管由于使用RxJava 1.0的项目众多,将长期支持RxJava 1.0,但创新可能只会在RxJava 2.0中继续进行。 RxJava 1.0将仅获得维护和错误修复。

RxJava 1.0和2.0都可以在Java 1.6上运行。在本书中,我们将使用Java 8,建议您至少使用Java 8,以便可以直接使用lambda。对于Android,有多种方法可以在较早的Java版本中利用lambda。但是自2014年以来,就一直在权衡Android Nougat使用Java 8和Java 8的事实,希望您不必采取任何变通办法来利用lambda。

中央仓库

要引入RxJava作为依赖项,您有一些选择。 最好的起点是转到中央存储库(搜索http://search.maven.org/)并搜索rxjava。 您应该在搜索结果的顶部看到RxJava 2.0和RxJava 1.0作为单独的存储库,如以下屏幕截图所示:

在撰写本文时,RxJava 2.0.2是RxJava 2.0的最新版本,而RxJava 1.2.3是RxJava 1.0的最新版本。 您可以通过单击下载列下面最右边的JARlinks下载最新的JAR文件。 然后,您可以将项目配置为使用JAR文件。

但是,您可能要考虑使用Gradle或Maven将这些库自动导入到项目中。 这样,您可以轻松地(通过GIT或其他版本控制系统)共享和存储您的代码项目,而不必每次都手动将RxJava下载和配置到该项目中。 要查看Maven,Gradle和其他几个构建自动化系统的最新配置,请单击任一存储库的版本号,如以下屏幕快照中所示:

使用Gradle

有几种自动构建系统可用,但最主流的两个选择是Gradle和Maven。 Gradle在某种程度上是Maven的继任者,尤其是针对Android开发的构建自动化解决方案。 如果您不熟悉Gradle并且想学习如何使用它,请查看Gradle入门指南(https://gradle.org/getting-started-gradle-java/)。

也有几本体面的书以不同程度的深度介绍了Gradle,您可以在https://gradle.org/books/中找到它们。 以下屏幕快照显示TheCentral Repository页面,其中显示了如何为Gradle设置RxJava 2.0.2:

在build.gradle脚本中,确保已将mavenCentral()声明为您的存储库之一。 输入或粘贴依赖项行 compile ‘io.reactivex.rxjava2:rxjava:x.y.z’,其中x.y.z是要使用的版本号,如以下代码片段所示:

apply plugin: 'java'
sourceCompatibility = 1.8
repositories {mavenCentral()
}
dependencies {      compile 'io.reactivex.rxjava2:rxjava:x.y.z'
}

构建您的Gradle项目,您应该一切顺利! 然后,您将可以在项目中使用RxJava及其类型。

使用Maven

您还可以选择使用Maven,并且可以通过选择Apache Maven配置信息在中央存储库中查看适当的配置,如以下屏幕快照所示:

然后,您可以复制并粘贴包含RxJava配置的块,并将其粘贴到pom.xml文件中的块内。 重建您的项目,现在应该将RxJava设置为依赖项。 x.y.z版本号与您要使用的所需RxJava版本相对应:

<project>  <modelVersion>4.0.0</modelVersion><groupId>org.nield</groupId><artifactId>mavenrxtest</artifactId><version>1.0</version>  <dependencies>    <dependency><groupId>io.reactivex.rxjava2</groupId>     <artifactId>rxjava</artifactId>     <version>x.y.z</version>    </dependency>  </dependencies>
</project>

快速接触RxJava

在我们深入研究RxJava的反应式世界之前,这里有个快速入门,可以让您先入为主。在ReactiveX中,您将使用的核心类型是Observable。 在本书的其余部分中,我们将学习更多有关Observable的知识。 但是本质上,一个Observable可以推动事物。 给定的Observable 通过一系列运算符推类型为T的事物,直到它到达使用该项的观察者为止。

import io.reactivex.Observable;public class Ch1_1 {public static void main(String[] args) {Observable<String> myStrings =Observable.just("Alpha", "Beta", "Gamma", "Delta","Epsilon");}
}

例如,在您的项目中创建一个新的Ch1_1.java文件,并输入以下代码:
在main()方法中,我们有一个Observable ,它将推送五个字符串对象。 一个Observable几乎可以从任何来源推送数据或事件,无论是数据库查询还是实时Twitter提要。 在这种情况下,我们将使用Observable.just()快速创建一个Observable,它将发出一组固定的项。

在RxJava 2.0中,您将使用的大多数类型都包含在io.reactivex包中。 在RxJava1.0中,类型包含在rx包中。

但是,运行main()方法除了声明Observable 之外不会做任何其他事情。 为了使此Observable实际上推动这五个字符串(称为发射),我们需要一个Observer来订阅它并接收项目。 我们可以通过传递一个lambda表达式来快速创建并连接一个Observer,该表达式指定如何处理接收到的每个字符串:

import io.reactivex.Observable;public class Ch1_1 {public static void main(String[] args) {Observable<String> myStrings =Observable.just("Alpha", "Beta", "Gamma", "Delta","Epsilon");myStrings.subscribe(s -> System.out.println(s));}
}

运行此代码时,我们将获得以下输出:

这里发生的是,我们的Observable 一次将每个字符串对象一次推送到我们的Observer,我们使用lambda表达式s-> System.out.println(s)来简化它。我们通过参数s(我随意命名)传递每个字符串,并指示它打印每个字符串。 Lambda本质上是mini函数,使我们能够快速传递有关每个传入项目要采取的操作的指令。箭头左侧的所有参数->是参数(在这种情况下,我们称为s的字符串),而右侧的所有参数都是操作(是System.out.println(s))。

如果您不熟悉lambda表达式,请转至附录,以了解有关它们如何工作的更多信息。如果您想花更多的时间来理解lambda表达式,我强烈建议您至少阅读Java 8 Lambdas(O’Reilly)的前几​​章(http://shop.oreilly.com/product/0636920030713.do),作者是Richard Warburton。 。 Lambda表达式是现代编程中的一个关键主题,自从Java 8被Java开发人员使用以来,它就变得尤为重要。我们将在本书中不断使用lambdas,因此一定要花一些时间来熟悉它们。

我们还可以在Observable和Observer之间使用多个运算符来转换每个推送的项目或以某种方式对其进行操作。每个运算符都返回一个新的Observable(继承自上一个),但反映该转换。例如,我们可以使用map()将每个字符串发射转换为它的length(),然后每个长度整数将被推送到Observer,如以下代码片段所示:

import io.reactivex.Observable;public class Ch1_2 {public static void main(String[] args) {Observable<String> myStrings =Observable.just("Alpha", "Beta", "Gamma", "Delta","Epsilon");myStrings.map(s -> s.length()).subscribe(s ->System.out.println(s));}
}

运行此代码时,我们将获得以下输出:

如果您使用过Java 8 Streams或Kotlin序列,您可能想知道Observable有何不同。 关键的区别是Observable会推送项目,而Streams和sequence会拉取项目,这似乎很微妙,但是基于push的迭代的影响要比基于pull的迭代强大得多。 不仅是数据,还有事件。 例如,Observable.interval()将在每个指定的时间间隔内推送一个连续的Long,如以下代码片段所示。 Long发射不仅是数据,而且是事件! 让我们来看看:

import io.reactivex.Observable;import java.util.concurrent.TimeUnit;public class Ch1_3 {public static void main(String[] args) {Observable<Long> secondIntervals =Observable.interval(1, TimeUnit.SECONDS);secondIntervals.subscribe(s -> System.out.println(s));
/* Hold main thread for 5 secondsso Observable above has chance to fire */sleep(5000);}public static void sleep(long millis) {try {Thread.sleep(millis);} catch (InterruptedException e) {e.printStackTrace();}}
}

运行此代码时,我们将获得以下输出:

当您运行前面的代码时,您将看到每秒发射一次连续发射。 该应用程序在退出之前将运行约五秒钟,您可能会看到发射了0到4个排放物,每个排放物之间的间隔只有一秒钟。 这个简单的想法是数据是随着时间流逝而发生的一系列事件,这将为我们解决编程问题带来新的可能性。

附带说明一下,我们稍后会更多地涉及并发性,但是我们必须创建一个sleep()方法,因为此Observable订阅后会在计算线程上触发发射。 用于启动应用程序的主线程不会等待此Observable,因为它是在计算线程而非主线程上触发的。 因此,我们使用sleep()暂停主线程5000毫秒,然后允许它到达main()方法的末尾(这将导致应用程序终止)。 这使Observable.interval()有机会在应用程序退出之前触发五秒钟的窗口。

在整本书中,我们将发现许多有关可观察的奥秘以及它为我们服务的强大抽象。 如果您从概念上了解了到目前为止发生的一切,那么恭喜! 您已经熟悉了响应式代码的工作方式。 再次强调一下,每次将排放一路推送到Observer.Emission代表数据和事件,可以随时间推移而排放。 当然,除了map()之外,RxJava中还有数百个运算符,我们将在本书中学习关键点。 了解哪种操作员用于某种情况以及如何将其结合起来是掌握RxJava的关键。 在下一章中,我们将更全面地介绍Observable和Observer。 我们还将揭开Observable中所代表的事件和数据的神秘面纱。

RxJava 1.0与RxJava 2.0-我使用哪一个?

如前所述,建议您尽可能使用RxJava 2.0。它将继续增长并获得新功能,同时将维护RxJava 1.0进行错误修复。但是,还有其他一些考虑因素可能会导致您使用RxJava 1.0。如果您继承了已经使用RxJava 1.0的项目,则可能会继续使用该方法,直到将其重构为2.0成为可能。您还可以签出David Akarnokd的RxJava2Interop项目(https://github.com/akarnokd/RxJava2Interop),该项目将Rx类型从RxJava 1.0转换为RxJava 2.0,反之亦然。读完本书后,即使您拥有RxJava 1.0旧版代码,也可以考虑使用该库来利用RxJava 2.0。

在RxJava中,有几个库可以使多个Java API进行响应并无缝地插入RxJava。仅举几例,这些库包括RxJava-JDBC,RxAndroid,RxJava-Extras,RxNetty和RxJavaFX。在撰写本文时,只有RxAndroid和RxJavaFX已完全移植到RxJava 2.0(尽管以下许多其他库)。到您阅读本文时,所有主要的RxJava扩展库都有望移植到RxJava 2.0。

您还将希望使用RxJava 2.0,因为它是从RxJava 1.0获得的许多后见之明和智慧之上构建的。它具有更好的性能,更简单的API,更清洁的背压方式,以及在与您自己的操作员一起砍伐时更安全。

何时使用RxJava

ReactiveX新手会问的一个常见问题是什么情况下需要使用响应式方法?我们是否一直想使用RxJava?作为从事实时响应式编程和呼吸一段时间的人,我了解到此问题有两个答案:

第一个答案是您首次开始时:yes!您总是想采取被动的方法。真正成为响应式编程大师的唯一方法是从头开始构建响应式应用程序。将一切视为可观察的事物,并始终根据数据和事件流对程序进行建模。当您这样做时,您将利用响应式编程所提供的一切,并看到您的应用程序质量显着提高。

第二个答案是,当您熟悉RxJava时,会发现RxJava不合适的情况。有时响应式方法可能不是最佳方法,但是通常,此异常仅适用于部分代码。您的整个项目本身应该是响应式的。可能有些部分没有响应,这是有充分理由的。这些异常仅对训练有素的Rx老手特别突出,他认为返回List<String>可能比返回Observable <String>更好。

Rx使用者们不应该担心什么时候应该做出反应而是有些事情应该做出怎么样的反应。随着时间的流逝,他们将开始看到将Rx的收益微不足道的情况,而这只是经验带来的。

因此,到目前为止,没有妥协。一路响应!

总结

在本章中,我们学习了如何以被动方式看待世界。作为开发人员,您可能必须从传统的命令式思维定型中重新训练自己,并开发一个反应堆。尤其是如果您长时间进行了命令式,面向对象的编程,这可能会很困难。但是,随着您的应用程序变得更加可维护,可扩展和优化,投资回报将非常可观。您还将获得更快的周转时间和更清晰的代码。

第一章 Thinking Reactively(响应式的思考)相关推荐

  1. 第一章,06-行列式的降阶计算-余子式和代数余子式

    第一章,06-行列式的降阶计算-余子式和代数余子式 简介 余子式 代数余子式 代数余子式相关定理 行列式按行(列)展开法则 证明 展开定理的推论 简介 这是<玩转线性代数>的学习笔记. 示 ...

  2. 小菜鸡的html初步教程(第十二章 初步构建响应式网站)

    小菜鸡的第三篇博客  今天是3/19,天气不错,跑到自习室来更新博客. 本系列文章仅仅是对基础的HTML5以及CSS进行讲解,更加详细的内容均会附上链接,以便查阅和版权保护.  昨晚我思考了下,决定对 ...

  3. Bootstrap第一章初识

    Bootstrap第一章 一.响应式web设计技术栈 和html5+css3互相配合与支持的: html5+css3 基本网页设计 html5中的viewport 提供可以配置视口的属性 css3媒体 ...

  4. springboot+基于vue的响应式代购商城APP的设计与实现 毕业设计-附源码191654

    Springboot响应式代购商城APP 摘 要 近年来,随着移动互联网的快速发展,电子商务越来越受到网民们的欢迎,电子商务对国家经济的发展也起着越来越重要的作用.简单的流程.便捷可靠的支付方式.快捷 ...

  5. Springboot响应式代购商城APP毕业设计-附源码191654

    摘 要 近年来,随着移动互联网的快速发展,电子商务越来越受到网民们的欢迎,电子商务对国家经济的发展也起着越来越重要的作用.简单的流程.便捷可靠的支付方式.快捷畅通的物流快递.安全的信息保护都使得电子商 ...

  6. 判断两个图片的特征向量_响应式布局提高篇 图片正确的打开方式

    作者 | Brilliant Open Web团队 编辑 | Aaron 本文承接上一章的内容,接着介绍响应式布局设计,主要讲如何实现响应式图片.通过对图片适配问题的说明,加深对响应式图片的理解,并分 ...

  7. (19)Reactor Processors——响应式Spring的道法术器

    本系列文章索引<响应式Spring的道法术器> 前情提要 响应式流 | Reactor 3快速上手 | 响应式流规范 2.9 Processor Processor既是一种特别的发布者(P ...

  8. 第一章 神经网络如何工作(附Python神经网络编程.pdf)

    Python神经网络编程.pdf 链接: https://pan.baidu.com/s/1RkNfeNgT3Qtt_sEqRhw5Bg 提取码: 98ma 第一章 神经网络如何工作 神经网络的思考模 ...

  9. [书籍精读]《响应式Web设计 HTML5和CSS3实战(第二版)》精读笔记分享

    写在前面 书籍介绍:本书主要讲解了如何运用HTML5和CSS3来进行响应式Web设计,使页面的设计与开发根据用户行为以及设备环境(系统平台.屏幕尺寸.屏幕定向等)来进行相应的响应和调整. 我的简评:响 ...

最新文章

  1. 赛思互动:初次上线CRM的企业如何将系统用起来、用好?
  2. 框架和库有什么区别? [关闭]
  3. c 调用java的dll_Windows下java调用c的dll动态库--Dev_Cpp编译c生成dll
  4. 非存储过程分页- 前台分页样式和控件
  5. PHP给后台管理系统加安全防护机制的一些方案
  6. MSSQL TCP/IP服务无法启动的解决方法
  7. js初化加载页面时ajax会调用两次的原因_在前端开发中,有哪些因素会导致页面卡顿
  8. 探讨SQL Server中Case 的不同用法
  9. mac 启用与关闭root账户
  10. php中url传递中文字符,特殊危险字符的解决方法
  11. 骆驼壳修改服务器,【电视直播】发烧友TV,骆驼壳导入直播源教程,打造个人专属永久盒子软件,你们想看的这都有!...
  12. 【笔记】代码整洁之道
  13. 在线超市系统-PythonGUI Tkinter 图片界面设计案例
  14. 第九届蓝桥杯快速排序java
  15. ​stp文件转wrl
  16. pandas表格-拆分Excel的单元格为多行,将多行数据汇聚到一行用分隔符号分开
  17. 基于深度学习的花卉检测与识别系统(YOLOv5清新界面版,Python代码)
  18. 如何用 CSS 实现三角形
  19. 预防甲型流感病毒的注意事项和方法
  20. SwitchyOmega使用

热门文章

  1. 3GPP R15 5GNR 协议概述
  2. excel模板文件下载
  3. 14.根据以下代码段,下列说法中正确的是( )。
  4. Android学习——碎片(fragment)
  5. 无风扇DIN导轨计算机
  6. 死磕 java线程系列之终篇
  7. 露大腿女孩与千手观音
  8. 瞎子 阿炳 喜欢其音乐
  9. stm32 同一个定时器输入捕获测量双通道PWM占空比
  10. PKCS1签名PKCS7签名PKCS7信封格式