美团客户端响应式框架EasyReact开源啦

时间:2023-01-14 20:35:37

前言

EasyReact 是一款基于响应式编程范式的客户端开发框架,开发者可以使用此框架轻松地解决客户端的异步问题。

美团客户端响应式框架EasyReact开源啦

目前 EasyReact 已在美团和大众点评客户端的部分业务中进行了实践,并且持续迭代了一年多的时间。近日,我们决定开源这个项目的 iOS Objective-C 语言部分,希望能够帮助更多的开发者不断探索更广泛的业务场景,也欢迎更多的社区开发者跟我们一起加强 EasyReact 的功能。

GitHub 的项目地址,参见 https://github.com/meituan/EasyReact

背景

美团 iOS 客户端团队在业界比较早地使用响应式来解决项目问题,为此我们引入了 ReactiveCocoa 这个函数响应式框架(相关实践,参考之前的系列博客)。随着业务的急速扩张和团队的拆分、变更,ReactiveCocoa 在解决异步问题的同时也带来了新的挑战,总结起来有以下几点:

  1. 高学习门槛

  2. 易出错

  3. 调试困难

  4. 风格不统一

既然响应式编程带来了这么多的麻烦,是否我们应该摒弃响应式编程,用更通俗易懂的面向对象编程来解决问题呢?这要从移动端开发的特点说起。

移动端开发特点

客户端程序本身充满异步的场景,客户端的主要逻辑就是从视图中处理控件事件,通过网络获取后端内容再展示到视图上。这其中事件的处理和网络的处理都是异步行为。

一般客户端程序发起网络请求后,程序会异步的继续执行,等待网络资源的获取。通常我们还会需要设置一定的标志位和显示一些加载指示器来让视图进行等待。但是当网络进行获取的时候,通知、UI 事件、定时器都对状态产生改变,进而会导致状态错乱。我们是否也遇到过:忙碌指示器没有正确隐藏掉,页面的显示的字段被错误的显示成旧值,甚至一个页面几个部分信息不同步的情况?

单个的问题看似简单,但是客户端飞速发展的今天,很多公司包括美团在内的代码行数早已突破百万。业务逻辑愈发复杂,使得维护状态本身就成了一个大问题。响应式编程正是解决这个问题的一种手段。

响应式编程的相关概念

响应式编程是基于数据流动编程的一种编程范式。做过 iOS 客户端开发的同学,一定了解过 KVO 这一系列的 API。KVO 帮助我们将属性的变更和变更后的处理分离开,大大简化了我们的更新逻辑。响应式编程将这一优势体现得更加淋漓尽致,可以简单的理解成,一个对象的属性改变后,另外一连串对象的属性都随之发生改变。

响应式的最简单例子莫过于电子表格,Excel 和 Numbers 中单元格公式就是一个响应的例子。我们只需要关心单元格和单元格的关系,而不需要关心当一个单元格发生变化,另外的单元格需要进行怎样的处理。“程序”的书写被提前到事件发生之前,所以响应式编程是一种声明式编程。它帮助我们将更多的精力集中在描述数据流动的关系上,而不是关注数据变化时处理的动作。

单纯的响应式编程,比如电子表格中的公式和 KVO 是比较容易理解的,但是为了在 Objective-C 语言中支持响应式特性,ReactiveCocoa 使用函数响应式编程的手段实现了响应式编程框架。而函数式编程正是造成大家学习路径陡峭的主要原因。在函数式编程的世界中, 一切又复杂起来。这些复杂的概念,如 Immutable、Side effect、High-order Function、Functor、Applicative、Monad 等等,让很多开发者望而却步。

防不胜防的错误

函数式编程主要使用高阶函数来解决问题,映射到 Objective-C 语言中就是使用 Block 来进行主要的处理。由于 Objective-C 是使用自动引用计数(ARC)来管理内存,一旦出现循环引用,就需要程序员主动破除循环引用。而 Block 闭包捕获变量是最容易形成循环引用。无脑的 weakify-strongify 会引起提早释放,而无脑的不使用 weakify-strongify 则会引起循环引用。即便是“老手”在使用的过程中,也难免出错。

另外,ReactiveCocoa 框架为了方便开发者更快的使用响应式编程,Hook 了很多 Cocoa 框架中的功能,例如 KVO、Notification Center、Perform Selector。一旦其它框架在 Hook 的过程中与之形成冲突,后续问题的排查就变得十分困难。

调试的困难性

函数响应式编程使用高阶函数,还带来了另外一个问题,那就是大量的嵌套闭包函数导致的调用栈深度问题。在 ReactiveCocoa 2.5 版本中,进行简单的 5 次变换,其调用栈深度甚至达到了 50 层(见下图)。

美团客户端响应式框架EasyReact开源啦ReactiveCocoa 的调用栈

仔细观察调用栈,我们发现整个调用栈的内容极为相似,难以从中发现问题所在。

另外异步场景更是给调试增加了新的难度。很多时候,数据的变化是由其他队列派发过来的,我们甚至无法在调用栈中追溯数据变化的来源。

风格差异化

业内很多人使用 FRP 框架来解决 MVVM 架构中的绑定问题。在业务实践中很多操作是高度相似且可被泛化的,这也意味着,可以被脚手架工具自动生成。

但目前业内知名的框架并没有提供相应的工具,最佳实践也无法“模板化”地传递下去。这就导致了对于 MVVM 和响应式编程,大家有了各自不同的理解。

EasyReact的初心

EasyReact 的诞生,其初心是为了解决 iOS 工程实现 MVVM 架构但没有对应的框架支撑,而导致的风格不统一、可维护性差、开发效率低等多种问题。而 MVVM 中最重要的一个功能就是绑定,EasyReact 就是为了让绑定和响应式的代码变得 Easy 起来。

它的目标就是让开发者能够简单的理解响应式编程,并且简单的将响应式编程的优势利用起来。

EasyReact依赖库介绍

EasyReact 先是基于 Objective-C 开发。而 Objective-C 是一门古老的编程语言,在 2014 年苹果公司推出 Swift 编程语言之后,Objective-C 已经基本不再更新,而 Swift支持的 Tuple 类型和集合类型自带的 mapfilter 等方法会让代码更清晰易读。

在 EasyReact Objective-C 版本的开发中,我们还衍生了一些周边库以支持这些新的代码技巧和语法糖。这些周边库现已开源,并且可以独立于 EasyReact 使用。

EasyTuple

美团客户端响应式框架EasyReact开源啦

EasyTuple 使用宏构造出类似 Swift 的 Tuple 语法。使用 Tuple ,在需要传递一个简单的数据架构时,可以不必手动为其创建对应的类,轻松的交给框架解决。

EasySequence

美团客户端响应式框架EasyReact开源啦

EasySequence 是一个给集合类型扩展的库,可以清晰的表达对一个集合类型的迭代操作,并且通过巧妙的手法可以让这些迭代操作使用链式语法拼接起来。同时 EasySequence 也提供了一系列的 线程安全 和 weak 内存管理的集合类型用以补充系统容器无法提供的功能。

EasyFoundation

美团客户端响应式框架EasyReact开源啦

EasyFoundation 是上述 EasyTuple 和 EasySequence 以及未来底层依赖库的一个统一封装。

用EasyReact解决之前的问题

EasyReact 因业务的需要而诞生,首要的任务就是解决业务中出现的那几点问题。我们来看看建设至今,那几个问题是否已经解决:

响应式编程的学习门槛

前面已经分析过,单纯的响应式编程并不是特别的难以理解,而函数式编程才是造成高学习门槛的原因。因此 EasyReact 采用大家都熟知的面向对象编程进行设计,想要了解代码,相对于函数式编程变得容易很多。

另外响应式编程基于数据流动,流动就会产生一个有向的流动网络图。在函数式编程中,网络图是使用闭包捕获来建立的,这样做非常不利于图的查找和遍历。而 EasyReact 选择在框架中使用图的数据结构,将数据流动的有向网络图抽象成有向有环图的节点和边。这样使得框架在运行过程中可以随时查询到节点和边的关系,详细内容可以参见 框架概述

另外对于已经熟悉了 ReactiveCocoa 的同学来说,我们也在数据的流动操作上基本实现了 ReactiveCocoa API。详细内容可以参见 基本操作。更多的功能可以向我们提功能的 ISSUE,也欢迎大家能够提 Pull Request 来共同建设 EasyReact。

避免不经意的错误

前面提到过 ReactiveCocoa 易造成循环引用或者提早释放的问题,那 EasyReact 是怎样解决这个问题的呢?因为 EasyReact 中的节点和边以及监听者都不是使用闭包来进行捕获,所以刨除转换和订阅中存在的副作用(转换 block 或者订阅 block 中导致的闭包捕获),EasyReact 是可以自动管理内存的。详细内容可以参见 内存管理

除了内存问题,ReactiveCocoa 中的 Hook Cocoa 框架问题,在 EasyReact 上通过规避手段来进行处理。EasyReact 在整个计划中只是用来完成最基本的数据流驱动的部分,所以本身是与 Cocoa 和 CocoaTouch 框架无关,一定程度上避免了与系统 API 和其他库 Hook 造成冲突。这并不是指 Easy 系列不去解决相应的部分,而是 Easy 系列希望以更规范和加以约束的方式来解决相同问题,后续 Easy 系列的其他开源项目中会有更多这些特定需求的解决方案。

EasyReact 的调试

EasyReact 利用对象的持有关系和方法调用来实现响应式中的数据流动,所以可方便的在调用栈信息中找出数据的传递关系。在 EasyReact 中,进行与前面 ReactiveCocoa 同样的 5 次简单变换,其调用栈只有 15 层(见下图)。

美团客户端响应式框架EasyReact开源啦EasyReact 的调用栈

经过观察不难发现,调用栈的顺序恰好就是变换的行为。这是因为我们将每种操作定义成一个边的类型,使得调用栈可以通过类名进行简单的分析。

为了方便调试,我们提供了一个 - [EZRNode graph] 方法。任意一个节点调用这个方法都可以得到一段 GraphViz 程序的 DotDSL 描述字符串,开发者可以通过 GraphViz 工具观察节点的关系,更好的排查问题。

使用方式如下:

  1. macOS 安装 GraphViz 工具 brew install graphviz

  2. 打印 -[EZRNode graph] 返回的字符串或者 Debug 期间在 lldb 调用 -[EZRNode graph]获取结果字符串,并输出保存至文件如 test.dot

  3. 使用工具分析生成图像 circo -Tpdf test.dot -o test.pdf && open test.pdf

结果示例:

美团客户端响应式框架EasyReact开源啦节点静态图

另外我们还开发了一个带有录屏并且可以动态查看应用程序中所有节点和边的调试工具,后期也会开源。开发中的工具是这样的:

美团客户端响应式框架EasyReact开源啦节点动态图

响应式编程风格上的统一

EasyReact 帮助我们解决了不少难题,遗憾的是它也不是“银弹”。在实际的项目实施中,我们发现仅仅通过 EasyReact ,仍然很难让大家在开发风格上统一起来。当然它从写法上要比 ReactiveCocoa 统一了很多,但是构建数据流仍然有着多种多样的方式。

所以,我们想到通过一个上层的业务框架来统一风格,这也就是后续衍生项目 EasyMVVM 诞生的原因,不久后我们也会将 EasyMVVM 进行开源。

EasyReact和其他框架的对比

EasyReact 从诞生之初,就不可避免要和已有的其他响应式编程框架做对比。下表对几大响应式框架进行了一个大概的对比:

美团客户端响应式框架EasyReact开源啦

性能方面,我们也和同样是 Objective-C 语言的 ReactiveCocoa 2.5 版本做了相应的 Benchmark。

测试环境

  • 编译平台:macOS High Sierra 10.13.5

  • IDE:Xcode 9.4.1

  • 真机设备:iPhone X 256G iOS 11.4(15F79)

测试对象

  1. listener、map、filter、flattenMap 等单阶操作

  2. combine、zip、merge 等多点聚合操作

  3. 同步操作

其中测试的规模为:

  • 节点或信号个数 10 个

  • 触发操作次数 1000 次

例如 Listener 方法有 10 个监听者,重复发送值 1000 次。

统计时间单位为 ns。

测试数据

重复上面的实验 10 次,得到数据平均值如下:

美团客户端响应式框架EasyReact开源啦

美团客户端响应式框架EasyReact开源啦

结果总结

ReactiveCocoa 平均耗时是 EasyReact 的 725.41%。

EasyReact 的 Swift 版本即将开源,届时会和 RxSwift 进行 Benchmark 比较。

EasyReact的最佳实践

通常我们创建一个类,里面会包含很多的属性。在使用 EasyReact 时,我们通常会把这些属性包装为 EZRNode 并加上一个泛型。如:

// SearchService.h

#import <Foundation/Foundation.h>
#import <EasyReact/EasyReact.h> @interface SearchService : NSObject @property (nonatomic, readonly, strong) EZRMutableNode<NSString *> *param;
@property (nonatomic, readonly, strong) EZRNode<NSDictionary *> *result;
@property (nonatomic, readonly, strong) EZRNode<NSError *> *error; @end

这段代码展示了如何创建一个 WiKi查询服务,该服务接收一个 param 参数,查询后会返回 result 或者 error。以下是实现部分:

// SearchService.m

@implementation SearchService

- (instancetype)init {
    if (self = [super init]) {
        _param = [EZRMutableNode new];
        EZRNode *resultNode = [_param flattenMap:^EZRNode * _Nullable(NSString * _Nullable searchParam) {
            NSString *queryKeyWord = [searchParam stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet  URLQueryAllowedCharacterSet]];
            NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"https://en.wikipedia.org/w/api.php?action=query&titles=%@&prop=revisions&rvprop=content&format=json&formatversion=2", queryKeyWord]];
            EZRMutableNode *returnedNode = [EZRMutableNode new];
            [[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
                if (error) {
                    returnedNode.value = error;
                } else {
                    NSError *serializationError;
                    NSDictionary *resultDictionary = [NSJSONSerialization JSONObjectWithData:data options:0 error:&serializationError];
                    if (serializationError) {
                        returnedNode.value = serializationError;
                    } else if (!([resultDictionary[@"query"][@"pages"] count] && !resultDictionary[@"query"][@"pages"][0][@"missing"])) {
                        NSError *notFoundError = [NSError errorWithDomain:@"com.example.service.wiki" code:100 userInfo:@{NSLocalizedDescriptionKey: [NSString stringWithFormat:@"keyword '%@' not found.", searchParam]}];
                        returnedNode.value = notFoundError;
                    } else {
                        returnedNode.value = resultDictionary;
                    }
                }
            }];
            return returnedNode;
        }];
        EZRIFResult *resultAnalysedNode = [resultNode if:^BOOL(id  _Nullable next) {
            return [next isKindOfClass:NSDictionary.class];
        }];
        _result = resultAnalysedNode.thenNode;
        _error = resultAnalysedNode.elseNode;
    }
    return self;
} @end

在调用时,我们只需要通过 listenedBy 方法关注节点的变化:

self.service = [SearchService new];
[[self.service.result listenedBy:self] withBlock:^(NSDictionary * _Nullable next) {
    NSLog(@"Result: %@", next);
}];
[[self.service.error listenedBy:self] withBlock:^(NSError * _Nullable next) {
    NSLog(@"Error: %@", next);
}]; self.service.param.value = @"mipmap"; //should print search result
self.service.param.value = @"420v"; // should print error, keyword not found.

使用 EasyReact 后,网络请求的参数、结果和错误可以很好地被分离。不需要像命令式的写法那样,在网络请求返回的回调中写一堆判断来分离结果和错误。

因为节点的存在先于结果,我们能对暂时还没有得到的结果构建连接关系,完成整个响应链的构建。响应链构建之后,一旦有了数据,数据便会自动按照我们预期的构建来传递。

在这个例子中,我们不需要显式地来调用网络请求,只需要给响应链中的 param 节点赋值,框架就会主动触发网络请求,并且请求完成之后会根据网络返回结果来分离出 result 和 error 供上层业务直接使用。

对于开源,我们是认真的

EasyReact 项目自立项以来,就励志打造成一个通用的框架,团队也一直以开源的高标准要求自己。整个开发的过程中我们始终保证测试覆盖率在一个高的标准上,对于接口的设计也力求完美。在开源的流程,我们也学习借鉴了 GitHub 上大量优秀的开源项目,在流程、文档、规范上力求标准化、国际化。

文档

除了 中文 README 和 英文 README 以外,我们还提供了中文的说明性质文档:

和英文的说明性质文档:

后续帮助理解的文章,也会陆续上传到项目*大家学习。

另外也为开源的贡献提供了标准的 中文贡献流程 和 英文贡献流程,其中对于 ISSUE 模板、Commit 模板、Pull Requests 模板和 Apache 协议头均有提及。

如果你仍然对 EasyReact 有所不解或者流程代码上有任何问题,可以随时通过提 ISSUE 的方式与我们联系,我们都会尽快答复。

行为驱动开发

为了保证 EasyReact 的质量,我们在开发的过程中使用 行为驱动开发。当每个新功能的声明部分确定后,我们会先编写大量的测试用例,这些用例模拟使用者的行为。通过模拟使用者的行为,以更加接近使用者的想法,去设计这个新功能的 API。同时大量的测试用例也保证了新的功能完成之时,一定是稳定的。

测试覆盖率

EasyReact 系列立项之时,就以高质量、高标准的开发原则来要求开发组成员执行。开源之后所有项目使用 codecov.io 服务生成对应的测试覆盖率报告,Easy 系列的框架覆盖率均保证在 95% 以上。

美团客户端响应式框架EasyReact开源啦

持续集成

为了保证项目质量,所有的 Easy 系列框架都配有持续集成工具 Travis CI。它确保了每一次提交,每一次 Pull Request 都是可靠的。

展望

目前开源的框架组件只是建立起响应式编程的基石,Easy 系列的初心是为 MVVM 架构提供一个强有力的框架工具。下图是 Easy 系列框架的架构简图:

美团客户端响应式框架EasyReact开源啦

未来开源计划

未来我们还有提供更多框架能力,开源给大家:

名称 描述
EasyDebugToolBox 动态节点状态调试工具
EasyOperation 基于行为和操作抽象的响应式库
EasyNetwork 响应式的网络访问库
EasyMVVM MVVM 框架标准和相关工具
EasyMVVMCLI EasyMVVM 项目脚手架工具

跨平台与多语言

EasyReact 的设计基于面向对象,所以很容易在各个语言中实现。我们也正在积极的在 Swift、Java、JavaScript 等主力语言中实现 EasyReact。

另外动态化作为目前行业的趋势,Easy 系列自然不会忽视。在 EasyReact 基于图的架构下,我们可以很轻松的让一个 Objective-C 的上游节点,通过一个特殊的桥接边连接到一个 JavaScript 节点,这样就可以让部分的逻辑动态下发过来。

结语

数据传递和异步处理,是大部分业务的核心。EasyReact 从架构上用响应式的方式来很好地解决了这个问题。它有效地组织了数据和数据之间的联系,让业务的处理流程从命令式编程方式,变成以数据流为核心的响应式编程方式。用先构建数据流关系再响应触发的方法,让业务方更关心业务的本质。使广大开发者从琐碎的命令式编程的状态处理中解放出来,提高了生产力。EasyReact 不仅让业务逻辑代码更容易维护,也让出错的几率大大下降。

团队简介

成威,项目的发起人,负责美团客户端新技术调研。国内函数式编程、响应式编程的爱好者,多年宣传和布道响应式编程实践并取得一定的成绩。

姜沂,项目的主要开发者。

秦宏,项目的主要开发者。

君阳,项目的早期开发者。

思琦,Easy 系列图标设计者,文档和代码翻译者。

志宇,参与了大部分的重构设计。

恩生,文档和代码翻译者。

姝琳,文档和代码翻译者。

如果你想近距离与我们的作者沟通、交流,请来GitChat,点击“免费预订”即可参与读者交流,报名链接

招聘信息

美团平台业务研发中心诚招高级 iOS 工程师、技术专家。欢迎投递简历到 zangchengwei#meituan.com。一起写 Easy 系列。

也许你还想看 

用Vue.js开发微信小程序:开源框架mpvue解析

Android热更新方案Robust开源,新增自动化补丁工具

Shield:支撑美团点评品类最丰富业务的移动端模块化框架开源了

美团客户端响应式框架EasyReact开源啦

美团客户端响应式框架EasyReact开源啦的更多相关文章

  1. 一个响应式框架——agera

    Google在上周开源了一个响应式框架——agera,相信它会慢慢地被广大程序员所熟知.我个人对这样的技术是很感兴趣的,在这之前也研究过RxJava,所以在得知Google开源了这样的框架之后第一时间 ...

  2. 15个最好的HTML5前端响应式框架&lpar;2014&rpar;

    文中的多个框架基于SASS创建,SCSS是一种比LESS更简洁的样式表编程语言,它能够编绎成CSS,可复用CSS代码,声明变量,甚至是函数,类Ruby/Python的语法.參见: LESS vs SA ...

  3. &period;NET Core的响应式框架&comma;基于Ace Admin框架菜单导航&comma;Bootstrap布局&comma;fontAwesome图标&comma;内嵌Iframe用EasyUI做数据绑定&comma;动态配置列表&comma;动态配置表单

    netnrf 响应式框架 用于快速开发的响应式框架 演示:https://rf2.netnr.com v3.x 前端采用 jQuery + Bootstrap + EasyUI + AceAdmin ...

  4. 强大又简单的响应式框架——Foundation 网格系统

          前端框架——Foundation     简介 Foundation 用于开发响应式的 HTML, CSS and JavaScript 框架. Foundation 是一个易用.强大而且 ...

  5. Web移动前端开发-——bootstarp响应式框架

    移动端WEB开发之响应式布局 1.0 响应式开发原理 1.1 响应式开发原理 就是使用媒体查询针对不同宽度的设备进行布局和样式的设置,从而适配不同设备的目的. 设备的划分情况: 小于768的为超小屏幕 ...

  6. 响应式框架Bootstrap

    概念:Bootstrap将会根据你的屏幕的大小来调整HTML元素的大小 -- 强调 响应式设计的概念. 通过响应式设计,你无需再为你的网站设计一个手机版的.它在任何尺寸的屏幕上看起来都会不错. Boo ...

  7. fcc 响应式框架Bootstrap 练习2

    text-primary 属性值使标题直接变成了红色,text-center使标题直接居中 <h2 class="text-primary  text-center"> ...

  8. 响应式框架pure--来自雅虎

    梦想还是要有  http://www.purecss.org/ 

  9. 响应式框架中,table表头自动换行的解决办法

    最近在用bootstrap开发网站,在处理一张table的时候发现,通过PC端查看样式正常,在手机上查看时,因为屏幕小,表格被压缩的厉害,表头和数据变形如下图 后来网上找了一下,发现一个好用的CSS属 ...

随机推荐

  1. DataTable的orderby有关问题

    在网上找了一个在后台重新对DataTable排序的方法(之所以不在数据库是因为我生成的是报表,写了存储过程用的表变量,order by也要用变量,死活拼不起来,sql能力没过关,动态sql也试了) s ...

  2. JS-JQuery&lpar;JSONP&rpar;调用WebService跨域若干技术点

    1.JSONP:JSON With Padding,让网页从别的网域获取信息,也就是跨域获取信息,可以当做是一种“工具”,大多数架构Jquery.EXTjs等都支持. 由于同源策略,一般来说位于 se ...

  3. ZMMR107-批量更改PO经价值

    ************************************************************************ Title : ZMMR107 ** Applicat ...

  4. python元类分析

    刚開始接触到Python新式类中的元类的概念的时候非常是纠结了下..不知道这是个啥东西... 用下面几个定义来说明吧: (1)Python中,类也是对象..仅仅只是这样的对象比較的特殊,他用于创建别的 ...

  5. Thinkpad W540通过扩展坞连接显示器后蓝屏原因

    问题:Thinkpad W540通过扩展坞连接显示器后蓝屏 描述:直接扩展连接显示器重启电脑,一直黑屏,等待光标,进不了桌面.分离扩展,进入后,再连接显示器,蓝屏死机. 解决:用尽办法,重装显示驱动, ...

  6. 了解AutoCAD对象层次结构 —— 1 ——应用程序

    想象这样一个场景:当您开始一天的工作,坐到电脑前面,用鼠标双击桌面上的AutoCAD Civil 3D图标,这时一个AutoCAD Civil 3D应用程序将运行起来.打开Windows任务管理器,我 ...

  7. 清北合肥day2-day5

    day2:215这一天的题目相对比较模板化t1:50看错了数据范围求n个点到给出的点哈夫曼距离的最小值我想到的是一种非常zz的做法我们二分答案,然后判断是否在这个距离内有点但是这样前缀和不是很好维护于 ...

  8. 一些Vim使用的小技巧

    1. 全局替换(1) v + G + $ 选定全部,然后输入 :s/原始字符串/目标字符串/(2) :%s/原始字符串/目标字符串/ 2. 清除页面中所有行尾的空白符::%s/s+$// 3. 清除所 ...

  9. Spring batch的学习

    Spring batch是用来处理大量数据操作的一个框架,主要用来读取大量数据,然后进行一定处理后输出成指定的形式. Spring batch主要有以下部分组成: JobRepository     ...

  10. java二叉树排序实现

    原创:转载请注明出处 目的:想用java实现二叉树排序算法 思想:利用java中面向对象的思想,即: Tree:类 树根Tree:root //static所属于每一个Tree 左节点Tree:lef ...