当前位置: 首页 > news >正文

MVVM 架构进阶:MVI 架构详解

前言

Android开发发展到今天已经相当成熟了,各种架构大家也都耳熟能详,如MVC,MVP,MVVM等,其中MVVM更是被官方推荐,成为Android开发中的显学。

不过软件开发中没有银弹,MVVM架构也不是尽善尽美的,在使用过程中也会有一些不太方便之处,而MVI可以很好的解决一部分MVVM的痛点。

本文主要包括以下内容:

1. MVC,MVP,MVVM等经典架构介绍。

2. MVI架构到底是什么?

3. MVI架构实战。

需要重点指出的是,标题中说MVI架构是MVVM的进阶版是指MVI在MVVM非常相似,并在其基础上做了一定的改良,并不是说MVI架构一定比MVVM适合你的项目。

各位同学可以在分析比较各个架构后,选择合适项目场景的架构。

经典架构介绍

MVC架构介绍

MVC是个古老的Android开发架构,随着MVP与MVVM的流行已经逐渐退出历史舞台,我们在这里做一个简单的介绍,其架构图如下所示:

MVC架构主要分为以下几部分:

1.视图层(View):对应于xml布局文件和java代码动态view部分。

2.控制层(Controller):主要负责业务逻辑,在android中由Activity承担,同时因为XML视图功能太弱,所以Activity既要负责视图的显示又要加入控制逻辑,承担的功能过多。

3.模型层(Model):主要负责网络请求,数据库处理,I/O的操作,即页面的数据来源。

由于android中xml布局的功能性太弱,Activity实际上负责了View层与Controller层两者的工作,所以在android中mvc更像是这种形式:

因此MVC架构在android平台上的主要存在以下问题:

1. Activity同时负责View与Controller层的工作,违背了单一职责原则。

2. Model层与View层存在耦合,存在互相依赖,违背了最小知识原则。

MVP架构介绍

由于MVC架构在Android平台上的一些缺陷,MVP也就应运而生了,其架构图如下所示:

MVP架构主要分为以下几个部分:

1. View层:对应于Activity与XML,只负责显示UI,只与Presenter层交互,与Model层没有耦合。

2. Presenter层:主要负责处理业务逻辑,通过接口回调View层。

3. Model层:主要负责网络请求,数据库处理等操作,这个没有什么变化。

我们可以看到,MVP解决了MVC的两个问题,即Activity承担了两层职责与View层与Model层耦合的问题。

但MVP架构同样有自己的问题:

1. Presenter层通过接口与View通信,实际上持有了View的引用。

2. 但是随着业务逻辑的增加,一个页面可能会非常复杂,这样就会造成View的接口会很庞大。

MVVM架构介绍

MVVM 模式将 Presenter 改名为 ViewModel,基本上与 MVP 模式完全一致。

唯一的区别是,它采用双向数据绑定(data-binding):View的变动,自动反映在 ViewModel,反之亦然。

MVVM架构图如下所示:

可以看出MVVM与MVP的主要区别在于,你不用去主动去刷新UI了,只要Model数据变了,会自动反映到UI上。换句话说,MVVM更像是自动化的MVP。

MVVM的双向数据绑定主要通过DataBinding实现,不过相信有很多人跟我一样,是不喜欢用DataBinding的,这样架构就变成了下面这样:

1. View观察ViewModle的数据变化并自我更新,这其实是单一数据源而不是双向数据绑定,所以其实MVVM的这一大特性我其实并没有用到。

2. View通过调用ViewModel提供的方法来与ViewMdoel交互。

小结

1. MVC架构的主要问题在于Activity承担了View与Controller两层的职责,同时View层与Model层存在耦合。

2. MVP引入Presenter层解决了MVC架构的两个问题,View只能与Presenter层交互,业务逻辑放在Presenter层。

3. MVP的问题在于随着业务逻辑的增加,View的接口会很庞大,MVVM架构通过双向数据绑定可以解决这个问题。

4. MVVM与MVP的主要区别在于,你不用去主动去刷新UI了,只要Model数据变了,会自动反映到UI上。换句话说,MVVM更像是自动化的MVP。

5. MVVM的双向数据绑定主要通过DataBinding实现,但有很多人(比如我)不喜欢用DataBinding,而是View通过LiveData等观察ViewModle的数据变化并自我更新,这其实是单一数据源而不是双向数据绑定。

MVI架构到底是什么?

MVVM架构有什么不足?

要了解MVI架构,我们首先来了解下MVVM架构有什么不足。

相信使用MVVM架构的同学都有如下经验,为了保证数据流的单向流动,LiveData向外暴露时需要转化成immutable的,这需要添加不少模板代码并且容易遗忘,如下所示:

class TestViewModel : ViewModel() {//为保证对外暴露的LiveData不可变,增加一个状态就要添加两个LiveData变量private val _pageState: MutableLiveData<PageState> = MutableLiveData()val pageState: LiveData<PageState> = _pageStateprivate val _state1: MutableLiveData<String> = MutableLiveData()val state1: LiveData<String> = _state1private val _state2: MutableLiveData<String> = MutableLiveData()val state2: LiveData<String> = _state2//...
}

如上所示,如果页面逻辑比较复杂,ViewModel中将会有许多全局变量的LiveData,并且每个LiveData都必须定义两遍,一个可变的,一个不可变的。这其实就是我通过MVVM架构写比较复杂页面时最难受的点。

其次就是View层通过调用ViewModel层的方法来交互的,View层与ViewModel的交互比较分散,不成体系。

小结一下,在我的使用中,MVVM架构主要有以下不足:

1. 为保证对外暴露的LiveData是不可变的,需要添加不少模板代码并且容易遗忘。

2. View层与ViewModel层的交互比较分散零乱,不成体系。

MVI架构是什么?

MVI 与 MVVM 很相似,其借鉴了前端框架的思想,更加强调数据的单向流动和唯一数据源,架构图如下所示:

其主要分为以下几部分:

1. Model: 与MVVM中的Model不同的是,MVI的Model主要指UI状态(State)。例如页面加载状态、控件位置等都是一种UI状态。

2. View: 与其他MVX中的View一致,可能是一个Activity或者任意UI承载单元。MVI中的View通过订阅Intent的变化实现界面刷新。(注意:这里不是Activity的Intent)

3. Intent: 此Intent不是Activity的Intent,用户的任何操作都被包装成Intent后发送给Model层进行数据请求。

单向数据流

MVI强调数据的单向流动,主要分为以下几步:

1. 用户操作以Intent的形式通知Model。

2. Model基于Intent更新State。

3. View接收到State变化刷新UI。

数据永远在一个环形结构中单向流动,不能反向流动:

上面简单的介绍了下MVI架构,下面我们一起来看下具体是怎么使用MVI架构的。

MVI架构实战

总体架构图

我们使用ViewModel来承载MVI的Model层,总体结构也与MVVM类似,主要区别在于Model与View层交互的部分。

1. Model层承载UI状态,并暴露出ViewState供View订阅,ViewState是个data class,包含所有页面状态。

2. View层通过Action更新ViewState,替代MVVM通过调用ViewModel方法交互的方式。

MVI实例介绍

添加ViewState与ViewEvent

ViewState承载页面的所有状态,ViewEvent则是一次性事件,如Toast等,如下所示:

data class MainViewState(val fetchStatus: FetchStatus, val newsList: List<NewsItem>)  sealed class MainViewEvent {data class ShowSnackbar(val message: String) : MainViewEvent()data class ShowToast(val message: String) : MainViewEvent()
}

1. 我们这里ViewState只定义了两个,一个是请求状态,一个是页面数据。

2. ViewEvent也很简单,一个简单的密封类,显示Toast与Snackbar。

ViewState更新

class MainViewModel : ViewModel() {private val _viewStates: MutableLiveData<MainViewState> = MutableLiveData()val viewStates = _viewStates.asLiveData()private val _viewEvents: SingleLiveEvent<MainViewEvent> = SingleLiveEvent()val viewEvents = _viewEvents.asLiveData()init {emit(MainViewState(fetchStatus = FetchStatus.NotFetched, newsList = emptyList()))}private fun fabClicked() {count++emit(MainViewEvent.ShowToast(message = "Fab clicked count $count"))}private fun emit(state: MainViewState?) {_viewStates.value = state}private fun emit(event: MainViewEvent?) {_viewEvents.value = event}
}

如上所示:

1. 我们只需定义ViewState与ViewEvent两个State,后续增加状态时在data class中添加即可,不需要再写模板代码。

2. ViewEvents是一次性的,通过SingleLiveEvent实现,当然你也可以用Channel当来实现。

3. 当状态更新时,通过emit来更新状态。

View监听ViewState

private fun initViewModel() {viewModel.viewStates.observe(this) {renderViewState(it)}viewModel.viewEvents.observe(this) {renderViewEvent(it)}
}

如上所示,MVI 使用 ViewState 对 State 集中管理,只需要订阅一个 ViewState 便可获取页面的所有状态,相对 MVVM 减少了不少模板代码。

View通过Action更新State

class MainActivity : AppCompatActivity() {private fun initView() {fabStar.setOnClickListener {viewModel.dispatch(MainViewAction.FabClicked)}}
}
class MainViewModel : ViewModel() {fun dispatch(action: MainViewAction) =reduce(viewStates.value, action)private fun reduce(state: MainViewState?, viewAction: MainViewAction) {when (viewAction) {is MainViewAction.NewsItemClicked -> newsItemClicked(viewAction.newsItem)MainViewAction.FabClicked -> fabClicked()MainViewAction.OnSwipeRefresh -> fetchNews(state)MainViewAction.FetchNews -> fetchNews(state)}}
}

如上所示,View通过Action与ViewModel交互,通过 Action 通信,有利于 View 与 ViewModel 之间的进一步解耦,同时所有调用以 Action 的形式汇总到一处,也有利于对行为的集中分析和监控。

总结

本文主要介绍了MVC,MVP,MVVM与MVI架构,目前MVVM是官方推荐的架构,但仍然有以下几个痛点:

1、MVVM与MVP的主要区别在于双向数据绑定,但由于很多人(比如我)并不喜欢使用DataBindg,其实并没有使用MVVM双向绑定的特性,而是单一数据源。

2. 当页面复杂时,需要定义很多State,并且需要定义可变与不可变两种,状态会以双倍的速度膨胀,模板代码较多且容易遗忘。

3. View与ViewModel通过ViewModel暴露的方法交互,比较凌乱难以维护。

而MVI可以比较好的解决以上痛点,它主要有以下优势:

1 强调数据单向流动,很容易对状态变化进行跟踪和回溯。

2 使用ViewState对State集中管理,只需要订阅一个 ViewState 便可获取页面的所有状态,相对 MVVM 减少了不少模板代码。

3. ViewModel通过ViewState与Action通信,通过浏览ViewState 和 Aciton 定义就可以理清 ViewModel 的职责,可以直接拿来作为接口文档使用。

当然MVI也有一些缺点,比如:

1. 所有的操作最终都会转换成State,所以当复杂页面的State容易膨胀。

2. state是不变的,因此每当state需要更新时都要创建新对象替代老对象,这会带来一定内存开销。

软件开发中没有银弹,所有架构都不是完美的,有自己的适用场景,读者可根据自己的需求选择使用。

但通过以上的分析与介绍,我相信使用MVI架构代替没有使用DataBinding的MVVM是一个比较好的选择。

源码地址:

https://github.com/shenzhen2017/android-architecture

相关文章:

MVVM 架构进阶:MVI 架构详解

前言Android开发发展到今天已经相当成熟了&#xff0c;各种架构大家也都耳熟能详&#xff0c;如MVC,MVP,MVVM等&#xff0c;其中MVVM更是被官方推荐&#xff0c;成为Android开发中的显学。不过软件开发中没有银弹&#xff0c;MVVM架构也不是尽善尽美的&#xff0c;在使用过程中…...

有没有必要考PMP证书?

其实针对有没有必要考试吗&#xff0c;这个可以根本不同行业的人来决定的。 1.高等教育项目管理专业科班出身的人员。 在我国本科学历和硕士研究生学历中&#xff0c;项目管理也有开设。不管以后从事的工作是否为项目管理或其他管理&#xff0c;作为本专业的同学&#xff0c;…...

1 机器学习基础

1 机器学习概述 1.1 数据驱动的问题求解 大数据-Big Data 大数据的多面性 1.2 数据分析 机器学习&#xff1a;海量的数据&#xff0c;获取有用的信息 专门研究计算机怎样模拟或实现人类的学习行为&#xff0c;以获取新的知识或技能&#xff0c;重新组织已有的知识结构使之…...

java基础系列(六) sleep()和wait() 区别

一.前言 关于并发编程这块, 线程的一些基础知识我们得搞明白, 本篇文章来说一下这两个方法的区别,对Android中的HandlerThread机制原理可以有更深的理解, HandlerThread源码理解,请查看笔者的这篇博客: HandlerThread源码理解_handlerthread 源码_broadview_java的博客-CSDN博…...

Urho3D序列化

从Serializable派生的类可以通过定义属性将其自动序列化为二进制或XML格式。属性存储到每个类的上下文中。场景加载/保存和网络复制都是通过从Serializable派生Node和Component类来实现的。 支持的属性类型是Variant支持的所有属性类型&#xff0c;不包括指针和自定义值。 属性…...

企业级信息系统开发学习1.3——利用注解配置取代Spring配置文件

文章目录一、利用注解配置类取代Spring配置文件&#xff08;一&#xff09;打开项目&#xff08;二&#xff09;创建新包&#xff08;三&#xff09;拷贝类与接口&#xff08;四&#xff09;创建注解配置类&#xff08;五&#xff09;创建测试类&#xff08;六&#xff09;运行…...

VUE DIFF算法之快速DIFF

VUE DIFF算法系列讲解 VUE 简单DIFF算法 VUE 双端DIFF算法 文章目录VUE DIFF算法系列讲解前言一、快速DIFF的代码实现二、实践练习1练习2总结前言 本节我们来写一下VUE3中新的DIFF算法-快速DIFF&#xff0c;顾名思义&#xff0c;也就是目前最快的DIFF算法&#xff08;在VUE中&…...

一文掌握如何轻松稿定项目风险管理【静说】

风险管理对于每个项目经理和PMO都非常重要&#xff0c;如果管理不当会出现很多问题&#xff0c;咱们以前分享过很多风险管理的内容&#xff1a; 风险无处不在&#xff0c;一旦发生&#xff0c;会对一个或多个项目目标产生积极或消极影响的确定事件或条件。那么接下来介绍下五大…...

操作系统权限提升(十四)之绕过UAC提权-基于白名单AutoElevate绕过UAC提权

系列文章 操作系统权限提升(十二)之绕过UAC提权-Windows UAC概述 操作系统权限提升(十三)之绕过UAC提权-MSF和CS绕过UAC提权 注&#xff1a;阅读本编文章前&#xff0c;请先阅读系列文章&#xff0c;以免造成看不懂的情况&#xff01;&#xff01; 基于白名单AutoElevate绕过…...

ecology9-谷歌浏览器下-pdf.js在渲染时部分发票丢失文字 问题定位及解决

问题 问题描述 &#xff1a; 在谷歌浏览器下&#xff0c;pdf.js在渲染时部分发票丢失文字&#xff1b;360浏览器兼容模式不存在此问题 排查思路&#xff1a;1、对比谷歌浏览器的css样式和360浏览器兼容模式下的样式&#xff0c;没有发现关键差别 2、✔使用Fiddler修改网页js D…...

JavaScript Window Navigator

文章目录JavaScript Window NavigatorWindow Navigator警告!!!浏览器检测JavaScript Window Navigator window.navigator 对象包含有关访问者浏览器的信息。 Window Navigator window.navigator 对象在编写时可不使用 window 这个前缀。 实例 <div id"example"…...

Linux基础命令-du查看文件的大小

文章目录 du 命令介绍 语法格式 基本参数 参考实例 1&#xff09;以人类可读形式显示指定的文件大小 2&#xff09;显示当前目录下所有文件大小 3&#xff09;只显示目录的大小 4&#xff09;显示根下哪个目录文件最大 5&#xff09;显示所有文件的大小 6&#xff0…...

文献计量分析方法:Citespace安装教程

Citespace是一款由陈超美教授开发的可用于海量文献可视化分析的软件&#xff0c;可对Web of Science&#xff0c;Scopus&#xff0c;Pubmed&#xff0c;CNKI等数据库的海量文献进行主题、关键词&#xff0c;作者单位、合作网络&#xff0c;期刊、发表时间&#xff0c;文献被引等…...

MVI 架构更佳实践:支持 LiveData 属性监听

前言MVI架构为了解决MVVM在逻辑复杂时需要写多个LiveData(可变不可变)的问题,使用ViewState对State集中管理&#xff0c;只需要订阅一个 ViewState 便可获取页面的所有状态通过集中管理ViewState&#xff0c;只需对外暴露一个LiveData&#xff0c;解决了MVVM模式下LiveData膨胀…...

LeetCode438 找到字符串中所有字母异位词 带输入和输出

题目&#xff1a; 给定两个字符串 s 和 p&#xff0c;找到 s 中所有 p 的 异位词 的子串&#xff0c;返回这些子串的起始索引。不考虑答案输出的顺序。 异位词 指由相同字母重排列形成的字符串&#xff08;包括相同的字符串&#xff09;。 示例 1: 输入: s “cbaebabacd”, …...

ACSC 2023 比赛复现

Admin Dashboard 在 index.php 中可以看到需要访问者是 admin 权限&#xff0c;才可以看到 flag。 report.php 中可以让 admin bot 访问我们输入的 url&#xff0c;那么也就是说可以访问 addadmin.php 添加用户。 在 addadmin.php 中可以添加 admin 用户&#xff0c;但是需…...

【Linux驱动开发100问】什么是模块?如何编写和使用模块?

&#x1f947;今日学习目标&#xff1a;什么是Linux内核&#xff1f; &#x1f935;‍♂️ 创作者&#xff1a;JamesBin ⏰预计时间&#xff1a;10分钟 &#x1f389;个人主页&#xff1a;嵌入式悦翔园个人主页 &#x1f341;专栏介绍&#xff1a;Linux驱动开发100问 什么是模块…...

Android 9.0 Recent列表不显示某个app

1.概述 在9.0的系统产品rom定制化开发中,在一些产品定制化需求中,也是有很多重要的功能实现的,比如在某些app的开发中 由于不想被杀掉,所以就不想出现在recent的列表中,因此就需要从recent的列表中,去掉这个app的显示,然后这里有 两种方法实现这个功能,一种是在app中就…...

深度学习之卷积神经网络学习笔记一

1. 引言深度学习是一系列算法的统称&#xff0c;包括卷积神经网络&#xff08;CNN&#xff09;&#xff0c;循环神经网络&#xff08;RNN&#xff09;&#xff0c;自编码器&#xff08;AE&#xff09;&#xff0c;深度置信网络&#xff08;DBN&#xff09;&#xff0c;生成对抗…...

黑盒测试的常用方法

这里我们先设置一个示例,后面的文章中会根据示例来进行讲解 假设有一个程序是判断一个整形数字是否属于1-100 目录 1.等价类法 2.边界值法 3.判定表法 4.场景设计法 5.错误猜测法 6.正交法 1.等价类法 概念:系统性的确定要输入的测试条件的方法可以看出概念非常抽象,那…...

操作系统笔记-第一章

文章目录操作系统概述1. 操作系统的概念1.1 操作系统的地位1.2 操作系统的作用1.3 操作系统的定义2. 操作系统的历史2.1 操作系统的产生2.1.1 手动操作阶段&#xff08;20世纪40年代&#xff09;2.1.2 批处理阶段&#xff08;20世纪50年代&#xff09;2.1.3 执行系统阶段&#…...

daillist

daillist #重要说明&#xff1a; #[1]任意两个配置参数之间必须以空格隔开&#xff0c;否则&#xff0c;拨号脚本无法识别。 #[2]Info格式说明:厂商名简称_制式_频段 #VID #PID #PORT_M #PORT_A #PORT_G #script_*99# #script_#777 #Info 05c6 9025 /dev/ttyUSB1 /dev/ttyUSB2 …...

vue中render函数的作用和参数(vue2中render函数用法)

render 函数是 Vue2.x 新增的一个函数、主要用来提升节点的性能&#xff0c;它是基于 JavaScript 计算。使用 Render 函数将 Template 里面的节点解析成虚拟的 Dom 。Vue 推荐在绝大多数情况下使用模板来创建 HTML。然而在一些场景中&#xff0c;需要 JavaScript 的完全编程能力…...

基于Istio的高级流量管理二(Envoy流量劫持、Istio架构、高级流量管理)

文章目录一、Envoy流量劫持机制&#xff08;Iptables规则流转&#xff09;1、流量出向劫持流程&#xff08;1&#xff09;envoy怎样劫持入向流量&#xff1f;&#xff08;2&#xff09;Envoy劫持到流量之后&#xff0c;干什么&#xff1f;&#xff08;查询目的地&#xff09;&a…...

Sharding-Springboot-mybatis-plus整合(三)-inline策略

Sharding-Springboot-mybatis-plus整合&#xff08;三&#xff09; 1.简介 本节目标&#xff0c;使用SpringBoot整合Sharding和Mybatis-Plus验证上节分片策略 从配置文件上看策略包括&#xff08; inline、standard、complex、hint&#xff09; 环境搭建以inline策略演示 …...

编码的基本概念

本专栏包含信息论与编码的核心知识&#xff0c;按知识点组织&#xff0c;可作为教学或学习的参考。markdown版本已归档至【Github仓库&#xff1a;information-theory】&#xff0c;需要的朋友们自取。或者公众号【AIShareLab】回复 信息论 也可获取。 文章目录信源编码分类前缀…...

函数指针与指针函数的区别

目录&#xff1a;一、函数指针1 函数类型2 函数指针(指向函数的指针)3 函数指针数组二.函数指针和指针函数比较1 定义不同2 写法不同3.用法不同三.函数指针做函数参数(回调函数)1 利用回调函数实现打印任意类型数据2 提供能够打印任意类型数组函数3 利用回调函数 提供查找功能四…...

死锁的四个必要条件以及如何避免死锁

死锁的四个必要条件以及如何避免死锁 一.什么是死锁&#xff1f;二.死锁的四个必要条件 1.互斥条件&#xff1a;2.请求与保持条件&#xff1a;3.不剥夺条件:4.循环等待条件: 三.如何避免死锁 1.破坏请求保持条件2.破坏不剥夺条件3.破坏循环等待条件 死锁的四个必要条件以及如…...

浏览器多线程到事件循环机制

浏览器与js运行机制 进程与线程 进程 进程是CPU分配资源的最小单位&#xff0c;它是一个可以自己独立运行且拥有自己资源空间的任务程序&#xff1b;包括程序以及程序所使用的内存及系统资源 线程 线程是CPU调度的最小单位&#xff0c;它就是程序中的一个执行流&#xff1…...

Lambda表达式的本质

一直想写一篇文章&#xff0c;来总结lambda表达式&#xff0c;但是之前感觉总结的不是特别到位&#xff0c;现在看了几篇文章和视频后&#xff0c;感觉对lambda表达式有了比较深刻的认识&#xff0c;现在进行记录总结如下&#xff1a; lambda表达式又叫做匿名函数&#xff0c;…...

双柏县住房和城乡建设局网站/百度地图网页版进入

为什么80%的码农都做不了架构师&#xff1f;>>> 在第一篇介绍Hazelcast的文章已经提到&#xff0c;Hazelcast为Java中绝大部分数据结构提供了分布式实现。我们常用的Map、List、Queue等数据结构可以用Hazelcast的实现类在多个集群节点之间共享数据。本篇将介绍Map的…...

阿旗建设局举报网站/学电脑培训班

今天在做搜索框的时候、遇到需要获取焦点之后做一些事情、实现方法也很简单、那就是绑定OnFocusChangeListener事件、实现 onFocusChange(View v, boolean hasFocus) 方法、第二个参数就是判断得到焦点或失去焦点、从而实现我得想要的效果、代码如下&#xff1a; EditText sear…...

做新媒体广告的网站/seo深度解析

Word是我们学习、办公必备的一个工具&#xff0c;不过想要提高效率&#xff0c;我们还需要掌握一些技巧&#xff0c;不少小伙伴在Word里最头疼的就是排版的问题了&#xff0c;有时候排版真的耗费了我们很多的时间&#xff0c;而且还不一定排得好&#xff0c;那么你知道其实Word…...

德州公司做网站/seo搜索引擎优化平台

计算机科学与技术学科为甘肃省省级重点学科&#xff0c;为硕士一级学科授权点&#xff0c;拥有国家级计算机实验教学示范中心、国家级交通信息类创新人才培养模式实验区。专业师资力量雄厚&#xff0c;2009年被授予国家级“计算机软件技术基础课程教学团队”。近年来承担科研项…...

wordpress 主题推荐/宁波网站建设与维护

1.深度优先搜索-dfs 深度优先搜索算法&#xff08;英语&#xff1a;Depth-First-Search&#xff0c;DFS&#xff09;是一种用于遍历或搜索树或图的算法。沿着树的深度遍历树的节点&#xff0c;尽可能深的搜索树的分支。当节点v的所在边都己被探寻过&#xff0c;搜索将回溯到发…...

专业科技网站建设/百度框架户开户渠道

vi的使用--使用vi查找和替换 vi提供了几种定位查找一个指定的字符串在文件中位置的方法。同时还提供一种功能强大的全局替换功能。 1. 查找一个字符串 一个字符串是一行上的一个或几个字符。 为查找一个字符串&#xff0c;在vi命令模式下键入“/”&#xff0c;后面跟要查找的…...