函数式编程的Java编码实践:利用惰性写出高性能且抽象的代码
转载链接
本文会以惰性加载为例一步步介绍函数式编程中各种概念,所以读者不需要任何函数式编程的基础,只需要对 Java 8 有些许了解即可。
一 抽象一定会导致代码性能降低?
程序员的梦想就是能写出 “高内聚,低耦合”的代码,但从经验上来看,越抽象的代码往往意味着越低的性能。机器可以直接执行的汇编性能最强,C 语言其次,Java 因为较高的抽象层次导致性能更低。业务系统也受到同样的规律制约,底层的数增删改查接口性能最高,上层业务接口,因为增加了各种业务校验,以及消息发送,导致性能较低。
对性能的顾虑,也制约程序员对于模块更加合理的抽象。
一起来看一个常见的系统抽象,“用户” 是系统中常见的一个实体,为了统一系统中的 “用户” 抽象,我们定义了一个通用领域模型 User,除了用户的 id 外,还含有部门信息,用户的主管等等,这些都是常常在系统中聚合在一起使用的属性:
public class User {// 用户 idprivate Long uid;// 用户的部门,为了保持示例简单,这里就用普通的字符串// 需要远程调用 通讯录系统 获得private String department;// 用户的主管,为了保持示例简单,这里就用一个 id 表示// 需要远程调用 通讯录系统 获得private Long supervisor;// 用户所持有的权限// 需要远程调用 权限系统 获得private Set< String> permission;
}
这看起来非常棒,“用户“常用的属性全部集中到了一个实体里,只要将这个 User 作为方法的参数,这个方法基本就不再需要查询其他用户信息了。但是一旦实施起来就会发现问题,部门和主管信息需要远程调用通讯录系统获得,权限需要远程调用权限系统获得,每次构造 User 都必须付出这两次远程调用的代价,即使有的信息没有用到。比如下面的方法就展示了这种情况(判断一个用户是否是另一个用户的主管):
public boolean isSupervisor(User u1, User u2) {return Objects.equals(u1.getSupervisor(), u2.getUid());
}
为了能在上面这个方法参数中使用通用 User 实体,必须付出额外的代价:远程调用获得完全用不到的权限信息,如果权限系统出现了问题,还会影响无关接口的稳定性。
想到这里我们可能就想要放弃通用实体的方案了,让裸露的 uid 弥漫在系统中,在系统各处散落用户信息查询代码。
其实稍作改进就可以继续使用上面的抽象,只需要将 department, supervisor 和 permission 全部变成惰性加载的字段,在需要的时候才进行外部调用获得,这样做有非常多的好处:
- 业务建模只需要考虑贴合业务,而不需要考虑底层的性能问题,真正实现业务层和物理层的解耦
- 业务逻辑与外部调用分离,无论外部接口如何变化,我们总是有一层适配层保证核心逻辑的稳定
- 业务逻辑看起来就是纯粹的实体操作,易于编写单元测试,保障核心逻辑的正确性
但是在实践的过程中常会遇到一些问题,本文就结合 Java 以及函数式编程的一些技巧,一起来实现一个惰性加载工具类。
二 严格与惰性:Java 8 的 Supplier 的本质
Java 8 引入了全新的函数式接口 Supplier,从老 Java 程序员的角度理解,它不过就是一个可以获取任意值的接口而已,Lambda 不过是这种接口实现类的语法糖。这是站在语言角度而不是计算角度的理解。当你了解了严格(strict)与惰性(lazy)的区别之后,可能会有更加接近计算本质的看法。
因为 Java 和 C 都是严格的编程语言,所以我们习惯了变量在定义的地方就完成了计算。事实上,还有另外一个编程语言流派,它们是在变量使用的时候才进行计算的,比如函数式编程语言 Haskell。
所以 Supplier 的本质是在 Java 语言中引入了惰性计算的机制,为了在 Java 中实现等价的惰性计算,可以这么写:
Supplier< Integer> a = () -> 10 + 1;
int b = a.get() + 1;
三 Supplier 的进一步优化:Lazy
Supplier 还存在一个问题,就是每次通过 get 获取值时都会重新进行计算,真正的惰性计算应该在第一次 get 后把值缓存下来。只要对 Supplier 稍作包装即可:
/**
* 为了方便与标准的 Java 函数式接口交互,Lazy 也实现了 Supplier
*/
public class Lazy< T> implements Supplier< T> {private final Supplier< ? extends T> supplier;// 利用 value 属性缓存 supplier 计算后的值private T value;private Lazy(Supplier< ? extends T> supplier) {this.supplier = supplier;}public static < T> Lazy< T> of(Supplier< ? extends T> supplier) {return new Lazy< >(supplier);}public T get() {if (value == null) {T newValue = supplier.get();if (newValue == null) {throw new IllegalStateException("Lazy value can not be null!");}value = newValue;}return value;}
}
通过 Lazy 来写之前的惰性计算代码:
Lazy< Integer> a = Lazy.of(() -> 10 + 1);
int b = a.get() + 1;
// get 不会再重新计算, 直接用缓存的值
int c = a.get();
通过这个惰性加载工具类来优化我们之前的通用用户实体:
public class User {// 用户 idprivate Long uid;// 用户的部门,为了保持示例简单,这里就用普通的字符串// 需要远程调用 通讯录系统 获得private Lazy< String> department;// 用户的主管,为了保持示例简单,这里就用一个 id 表示// 需要远程调用 通讯录系统 获得private Lazy< Long> supervisor;// 用户所含有的权限// 需要远程调用 权限系统 获得private Lazy< Set< String>> permission;public Long getUid() {return uid;}public void setUid(Long uid) {this.uid = uid;}public String getDepartment() {return department.get();}/*** 因为 department 是一个惰性加载的属性,所以 set 方法必须传入计算函数,而不是具体值*/public void setDepartment(Lazy< String> department) {this.department = department;}// ... 后面类似的省略
}
一个简单的构造 User 实体的例子如下:
Long uid = 1L;
User user = new User();
user.setUid(uid);
// departmentService 是一个rpc调用
user.setDepartment(Lazy.of(() -> departmentService.getDepartment(uid)));
// ....
这看起来还不错,但当你继续深入使用时会发现一些问题:用户的两个属性部门和主管是有相关性,需要通过 rpc 接口获得用户部门,然后通过另一个 rpc 接口根据部门获得主管。代码如下:
String department = departmentService.getDepartment(uid);
Long supervisor = SupervisorService.getSupervisor(department);
但是现在 department 不再是一个计算好的值了,而是一个惰性计算的 Lazy 对象,上面的代码又应该怎么写呢?“函子” 就是用来解决这个问题的
四 Lazy 实现函子(Functor)
快速理解:类似 Java 中的 stream api 或者 Optional 中的 map 方法。函子可以理解为一个接口,而 map 可以理解为接口中的方法。
1 函子的计算对象
Java 中的 Collection< T>,Optional< T>,以及我们刚刚实现 Lazy< T>,都有一个共同特点,就是他们都有且仅有一个泛型参数,我们在这篇文章中暂且称其为盒子,记做 Box< T>,因为他们都好像一个万能的容器,可以任意类型打包进去。
2 函子的定义
函子运算可以将一个 T 映射到 S 的 function 应用到 Box< T> 上,让其成为 Box< S>,一个将 Box 中的数字转换为字符串的例子如下:
在盒子中装的是类型,而不是 1 和 “1” 的原因是,盒子中不一定是单个值,比如集合,甚至是更加复杂的多值映射关系。
需要注意的是,并不是随便定义一个签名满足 Box< S> map(Function< T,S> function) 就能让 Box< T> 成为函子的,下面就是一个反例:
// 反例,不能成为函子,因为这个方法没有在盒子中如实反映 function 的映射关系
public Box< S> map(Function< T,S> function) {return new Box< >(null);
}
所以函子是比 map 方法更加严格的定义,他还要求 map 满足如下的定律,称为 函子定律(定律的本质就是保障 map 方法能如实反映参数 function 定义的映射关系):
- 单位元律:Box< T> 在应用了恒等函数后,值不会改变,即 box.equals(box.map(Function.identity()))始终成立(这里的 equals 只是想表达的一个数学上相等的含义)
- 复合律:假设有两个函数 f1 和 f2,map(x -> f2(f1(x))) 和 map(f1).map(f2) 始终等价
很显然 Lazy 是满足上面两个定律的。
3 Lazy 函子
虽然介绍了这么多理论,实现却非常简单:
public < S> Lazy< S> map(Function< ? super T, ? extends S> function) {return Lazy.of(() -> function.apply(get()));}
可以很容易地证明它是满足函子定律的。
通过 map 我们很容易解决之前遇到的难题,map 中传入的函数可以在假设部门信息已经获取到的情况下进行运算:
Lazy< String> departmentLazy = Lazy.of(() -> departmentService.getDepartment(uid));
Lazy< Long> supervisorLazy = departmentLazy.map(department -> SupervisorService.getSupervisor(department)
);
4 遇到了更加棘手的情况
我们现在不仅可以构造惰性的值,还可以用一个惰性值计算另一个惰性值,看上去很完美。但是当你进一步深入使用的时候,又发现了更加棘手的问题。
我现在需要部门和主管两个参数来调用权限系统来获得权限,而部门和主管这两个值都是惰性的值。先用嵌套 map 来试一下:
Lazy< Lazy< Set< String>>> permissions = departmentLazy.map(department ->supervisorLazy.map(supervisor -> getPermissions(department, supervisor))
);
返回值的类型好像有点奇怪,我们期待得到的是 Lazy< Set< String>>,这里得到的却多了一层变成 Lazy< Lazy< Set< String>>>。而且随着你嵌套 map 层数增加,Lazy 的泛型层次也会同样增加,三参数的例子如下:
Lazy< Long> param1Lazy = Lazy.of(() -> 2L);
Lazy< Long> param2Lazy = Lazy.of(() -> 2L);
Lazy< Long> param3Lazy = Lazy.of(() -> 2L);
Lazy< Lazy< Lazy< Long>>> result = param1Lazy.map(param1 ->param2Lazy.map(param2 ->param3Lazy.map(param3 -> param1 + param2 + param3))
);
这个就需要下面的单子运算来解决了。
五 Lazy 实现单子 (Monad)
快速理解:和 Java stream api 以及 Optional 中的 flatmap 功能类似
1 单子的定义
单子和函子的重大区别在于接收的函数,函子的函数一般返回的是原生的值,而单子的函数返回却是一个盒装的值。下图中的 function 如果用 map 而不是 flatmap 的话,就会导致结果变成一个俄罗斯套娃–两层盒子。
单子当然也有单子定律,但是比函子定律要复杂些,这里就不做阐释了,他的作用和函子定律也是类似,确保 flatmap 能够如实反映 function 的映射关系。
2 Lazy 单子
实现同样很简单:
public < S> Lazy< S> flatMap(Function< ? super T, Lazy< ? extends S>> function) {return Lazy.of(() -> function.apply(get()).get());}
利用 flatmap 解决之前遇到的问题:
Lazy< Set< String>> permissions = departmentLazy.flatMap(department ->supervisorLazy.map(supervisor -> getPermissions(department, supervisor))
);
三参数的情况:
Lazy< Long> param1Lazy = Lazy.of(() -> 2L);
Lazy< Long> param2Lazy = Lazy.of(() -> 2L);
Lazy< Long> param3Lazy = Lazy.of(() -> 2L);
Lazy< Long> result = param1Lazy.flatMap(param1 ->param2Lazy.flatMap(param2 ->param3Lazy.map(param3 -> param1 + param2 + param3))
);
其中的规律就是,最后一次取值用 map,其他都用 flatmap。
3 题外话:函数式语言中的单子语法糖
看了上面的例子你一定会觉得惰性计算好麻烦,每次为了取里面的惰性值都要经历多次的 flatmap 与 map。这其实是 Java 没有原生支持函数式编程而做的妥协之举,Haskell 中就支持用 do 记法简化 Monad 的运算,上面三参数的例子如果用 Haskell 则写做:
doparam1 < - param1Lazyparam2 < - param2Lazyparam3 < - param3Lazy-- 注释: do 记法中 return 的含义和 Java 完全不一样-- 它表示将值打包进盒子里,-- 等价的 Java 写法是 Lazy.of(() -> param1 + param2 + param3)return param1 + param2 + param3
Java 中虽然没有语法糖,但是上帝关了一扇门,就会打开一扇窗。在 Java 中可以清晰地看出每一步在做什么,理解其中的原理,如果你读过了本文之前的内容,肯定能明白这个 do 记法就是不停地在做 flatmap 。
六 Lazy 的最终代码
目前为止,我们写的 Lazy 代码如下:
public class Lazy< T> implements Supplier< T> {private final Supplier< ? extends T> supplier;private T value;private Lazy(Supplier< ? extends T> supplier) {this.supplier = supplier;}public static < T> Lazy< T> of(Supplier< ? extends T> supplier) {return new Lazy< >(supplier);}public T get() {if (value == null) {T newValue = supplier.get();if (newValue == null) {throw new IllegalStateException("Lazy value can not be null!");}value = newValue;}return value;}public < S> Lazy< S> map(Function< ? super T, ? extends S> function) {return Lazy.of(() -> function.apply(get()));}public < S> Lazy< S> flatMap(Function< ? super T, Lazy< ? extends S>> function) {return Lazy.of(() -> function.apply(get()).get());}
}
七 构造一个能够自动优化性能的实体
利用 Lazy 我们写一个构造通用 User 实体的工厂:
@Component
public class UserFactory {// 部门服务, rpc 接口@Resourceprivate DepartmentService departmentService;// 主管服务, rpc 接口@Resourceprivate SupervisorService supervisorService;// 权限服务, rpc 接口@Resourceprivate PermissionService permissionService;public User buildUser(long uid) {Lazy< String> departmentLazy = Lazy.of(() -> departmentService.getDepartment(uid));// 通过部门获得主管// department -> supervisorLazy< Long> supervisorLazy = departmentLazy.map(department -> SupervisorService.getSupervisor(department));// 通过部门和主管获得权限// department, supervisor -> permissionLazy< Set< String>> permissionsLazy = departmentLazy.flatMap(department ->supervisorLazy.map(supervisor -> permissionService.getPermissions(department, supervisor)));User user = new User();user.setUid(uid);user.setDepartment(departmentLazy);user.setSupervisor(supervisorLazy);user.setPermissions(permissionsLazy);}
}
工厂类就是在构造一颗求值树,通过工厂类可以清晰地看出 User 各个属性间的求值依赖关系,同时 User 对象能够在运行时自动地优化性能,一旦某个节点被求值,路径上的所有属性的值都会被缓存。
八 异常处理
虽然我们通过惰性让 user.getDepartment() 仿佛是一次纯内存操作,但是他实际上还是一次远程调用,所以可能出现各种出乎意料的异常,比如超时等等。
异常处理肯定不能交给业务逻辑,这样会影响业务逻辑的纯粹性,让我们前功尽弃。比较理想的方式是交给惰性值的加载逻辑 Supplier。在 Supllier 的计算逻辑中就充分考虑各种异常情况,重试或者抛出异常。虽然抛出异常可能不是那么“函数式”,但是比较贴近 Java 的编程习惯,而且在关键的值获取不到时就应该通过异常阻断业务逻辑的运行。
九 总结
利用本文方法构造的实体,可以将业务建模上需要的属性全部放置进去,业务建模只需要考虑贴合业务,而不需要考虑底层的性能问题,真正实现业务层和物理层的解耦。
同时 UserFactory 本质上就是一个外部接口的适配层,一旦外部接口发生变化,只需要修改适配层即可,能够保护核心业务代码的稳定。
业务核心代码因为外部调用大大减少,代码更加接近纯粹的运算,因而易于书写单元测试,通过单元测试能够保证核心代码的稳定且不会出错。
十 题外话:Java 中缺失的柯里化与应用函子(Applicative)
仔细想想,刚刚做了这么多,目的就是一个,让签名为 C f(A,B) 的函数可以无需修改地应用到盒装类型 Box< A>和 Box< B> 上,并且产生一个 Box< C>,在函数式语言中有更加方便的方法,那就是应用函子。
应用函子概念上非常简单,就是将盒装的函数应用到盒装的值上,最后得到一个盒装的值,在 Lazy 中可以这么实现:
// 注意,这里的 function 是装在 lazy 里面的public < S> Lazy< S> apply(Lazy< Function< ? super T, ? extends S>> function) {return Lazy.of(() -> function.get().apply(get()));}
不过在 Java 中实现这个并没有什么用,因为 Java 不支持柯里化。
柯里化允许我们将函数的几个参数固定下来变成一个新的函数,假如函数签名为 f(a,b),支持柯里化的语言允许直接 f(a) 进行调用,此时返回值是一个只接收 b 的函数。
在支持柯里化的情况下,只需要连续的几次应用函子,就可以将普通的函数应用在盒装类型上了,举个 Haskell 的例子如下(< *> 是 Haskell 中应用函子的语法糖, f 是个签名为 c f(a, b) 的函数,语法不完全正确,只是表达个意思):
-- 注释: 结果为 box c
box f < *> box a < *> box b
参考资料
- 在 Java 函数式类库 VAVR 中提供了类似的 Lazy 实现,不过如果只是为了用这个一个类的话,引入整个库还是有些重,可以利用本文的思路直接自己实现
- 函数式编程进阶:应用函子 前端角度的函数式编程文章,本文一定程度上参考了里面盒子的类比方法:https://juejin.cn/post/6891820537736069134?spm=ata.21736010.0.0.595242a7a98f3U
- 《Haskell函数式编程基础》
- 《Java函数式编程》
相关文章:

函数式编程的Java编码实践:利用惰性写出高性能且抽象的代码
转载链接 本文会以惰性加载为例一步步介绍函数式编程中各种概念,所以读者不需要任何函数式编程的基础,只需要对 Java 8 有些许了解即可。 一 抽象一定会导致代码性能降低? 程序员的梦想就是能写出 “高内聚,低耦合”的代码&…...

2024年甘肃省职业院校技能大赛信息安全管理与评估 样题二 模块二
竞赛需要完成三个阶段的任务,分别完成三个模块,总分共计 1000分。三个模块内容和分值分别是: 1.第一阶段:模块一 网络平台搭建与设备安全防护(180 分钟,300 分)。 2.第二阶段:模块二…...
mysql 一对多 合并多个通过 逗号拼接展示
mysql 一对多 合并多个通过 逗号拼接展示 以上内容由chatgpt中文网 动态生成 SELECTuser_id,project_id,GROUP_CONCAT(project_specs_id) AS merged_specs_ids FROMzt_medication_specs_total WHEREspecs_num_total < 5 GROUP BYuser_id, project_id;laravel model 对应写…...

Java零基础教学文档第五篇:jQuery
今日新篇章 【jQuery】 【主要内容】 jQuery简介 jQuery安装 jQuery语法 jQuery选择器 jQuery事件处理 jQueryDOM操作 jQuery元素遍历 jQuery过滤 jQuery其它方法 【学习目标】 1.jQuery简介 1.1 jQuery简介 jQuery 库可以通过一行简单的标记被添加到网页中。 1.…...
samba
samba 文章目录 samba1. samba简介2. samba访问3. 示例 1. samba简介 Samba是在Linux和UNIX系统上实现SMB协议的一个免费软件,由服务器及客户端程序构成。 在此之前我们已经了解了NFS,NFS与samba一样,也是在网络中实现文件共享的一种实现&a…...

「云渲染科普」3dmax vray动画渲染参数如何设置
动画渲染一直都是占用时间最多的地方,动画帧数通常 1 秒在 25 帧或者以上,电脑通常需要对每一帧的画面分批渲染,通常本地电脑由于配置上的限制,往往无法在短时间内快速的完成渲染任务。这时“云渲染”则成为了动画渲染的主要方案&…...

【GCC】6 接收端实现:周期构造RTCP反馈包
基于m98代码。GCC涉及的代码,可能位于:webrtc/modules/remote_bitrate_estimator webrtc/modules/congestion_controller webrtc/modules/rtp_rtcp/source/rtcp_packet/transport_feedback.cc webrtc 之 RemoteEstimatorProxy 对 remote_bitrate_estimator 的 RemoteEstimato…...

【RTOS】快速体验FreeRTOS所有常用API(6)事件组
目录 六、事件组6.1 基本概念6.2 创建6.3 设置事件6.4 等待事件6.5 实例 六、事件组 该部分在上份代码基础上修改得来,代码下载链接: https://wwzr.lanzout.com/iihn01la39je 密码:dtr0 该代码尽量做到最简,不添加多余的、不规范的代码。 内容…...
Springboot开发的大学生寝室考勤系统刷脸进出宿舍系统论文
摘要 近年来随着社会科学技术的全面进步及高等教育的普及,以计算机与通信技术为基础的信息系统正处于蓬勃发展的时期,学生作为预备的高新技术人才数量也在急剧上升,随之而来的就是高校管理上的一系列问题,首当其冲的便是高校应对…...

2024年美赛数学建模思路 - 复盘:校园消费行为分析
文章目录 0 赛题思路1 赛题背景2 分析目标3 数据说明4 数据预处理5 数据分析5.1 食堂就餐行为分析5.2 学生消费行为分析 建模资料 0 赛题思路 (赛题出来以后第一时间在CSDN分享) https://blog.csdn.net/dc_sinor?typeblog 1 赛题背景 校园一卡通是集…...

测试覆盖率 之 Cobertura的使用
什么是代码覆盖率? 代码覆盖率是对整个测试过程中被执行的代码的衡量,它能测量源代码中的哪些语句在测试中被执行,哪些语句尚未被执行。 为什么要测量代码覆盖率? 众所周知,测试可以提高软件版本的质量和可预测性。…...
vue表格插件vxe-table导出 excel
vxe-table 默认支持导出 CSV、HTML、XML、TXT格式的文件,不支持 xlsx 文件 要想导出 xlsx 文件,需要使用 vxe-table-plugin-export-xlsx 依赖 参考:https://cnpmjs.org/package/vxe-table-plugin-export-xlsx/v/2.1.0-beta 1.安装 npm inst…...

stable diffusion使用相关
IP Adapter,我愿称之它为SD垫图 IP Adapter是腾讯lab发布的一个新的Stable Diffusion适配器,它的作用是将你输入的图像作为图像提示词,本质上就像MJ的垫图。 IP Adapter比reference的效果要好,而且会快很多,适配于各种…...
Center项目创建——数据初始化(Linux/Windows版本)
本博客主要讲述Center的模块安装配置和数据初始化 1、定义安装Install函数,IP地址由makefile自动传入,也就是用户自动传入 bool Install(std::string ip); 2、编写Install函数 #define CENTER_CONF "ip bool XCenter::Install(std::string ip)…...

保送阿里云的云原生学习路线
近期好多人都有咨询学习云原生有什么资料。与其说提供资料不如先说一说应该如何学习云原生。 Linux基础知识:云原生技术通常在Linux环境中运行,因此建议首先掌握Linux的基础知识,包括命令行操作、文件系统、权限管理等。 容器化技术&#x…...

【昕宝爸爸小模块】深入浅出之JDK21 中的虚拟线程到底是怎么回事(二)
➡️博客首页 https://blog.csdn.net/Java_Yangxiaoyuan 欢迎优秀的你👍点赞、🗂️收藏、加❤️关注哦。 本文章CSDN首发,欢迎转载,要注明出处哦! 先感谢优秀的你能认真的看完本文&…...

两周掌握Vue3(三):全局组件、局部组件、Props
文章目录 一、全局组件1.创建全局组件2.在main.js中注册全局组件3.使用全局组件 二、局部组件1.创建局部组件2.在另一个组件中注册、使用局部组件 三、Props1.定义一个子组件2.定义一个父组件3.效果 代码仓库:跳转 本博客对应分支:03 一、全局组件 Vue…...
Web前端篇——element-plus组件设置全局中文
背景:在使用el-date-picker组件时,发现组件中的文字默认都是英文。 设置全局中文的方法如下:(本文只介绍CDN方式) <script src"//unpkg.com/element-plus/dist/locale/zh-cn"></script> <s…...

【iOS】数据存储方式总结(持久化)沙盒结构
在iOS开发中,我们经常性地需要存储一些状态和数据,比如用户对于App的相关设置、需要在本地缓存的数据等等,本篇文章将介绍六个主要的数据存储方式 iOS中数据存储方式(数据持久化) 根据要存储的数据大小、存储数据以及…...

硬盘重新分区怎么恢复分区之前的文件?
分区是常见的故障,通常由多种原因引起。一方面,硬盘老化或者受到损坏可能会导致分区表出现问题;另一方面,用户误操作,如格式化或分区不当,也可能导致分区丢失。针对此问题,解决方法包括使用专业…...
ES6从入门到精通:前言
ES6简介 ES6(ECMAScript 2015)是JavaScript语言的重大更新,引入了许多新特性,包括语法糖、新数据类型、模块化支持等,显著提升了开发效率和代码可维护性。 核心知识点概览 变量声明 let 和 const 取代 var…...

关于iview组件中使用 table , 绑定序号分页后序号从1开始的解决方案
问题描述:iview使用table 中type: "index",分页之后 ,索引还是从1开始,试过绑定后台返回数据的id, 这种方法可行,就是后台返回数据的每个页面id都不完全是按照从1开始的升序,因此百度了下,找到了…...

Opencv中的addweighted函数
一.addweighted函数作用 addweighted()是OpenCV库中用于图像处理的函数,主要功能是将两个输入图像(尺寸和类型相同)按照指定的权重进行加权叠加(图像融合),并添加一个标量值&#x…...

el-switch文字内置
el-switch文字内置 效果 vue <div style"color:#ffffff;font-size:14px;float:left;margin-bottom:5px;margin-right:5px;">自动加载</div> <el-switch v-model"value" active-color"#3E99FB" inactive-color"#DCDFE6"…...
Neo4j 集群管理:原理、技术与最佳实践深度解析
Neo4j 的集群技术是其企业级高可用性、可扩展性和容错能力的核心。通过深入分析官方文档,本文将系统阐述其集群管理的核心原理、关键技术、实用技巧和行业最佳实践。 Neo4j 的 Causal Clustering 架构提供了一个强大而灵活的基石,用于构建高可用、可扩展且一致的图数据库服务…...

【单片机期末】单片机系统设计
主要内容:系统状态机,系统时基,系统需求分析,系统构建,系统状态流图 一、题目要求 二、绘制系统状态流图 题目:根据上述描述绘制系统状态流图,注明状态转移条件及方向。 三、利用定时器产生时…...
【C语言练习】080. 使用C语言实现简单的数据库操作
080. 使用C语言实现简单的数据库操作 080. 使用C语言实现简单的数据库操作使用原生APIODBC接口第三方库ORM框架文件模拟1. 安装SQLite2. 示例代码:使用SQLite创建数据库、表和插入数据3. 编译和运行4. 示例运行输出:5. 注意事项6. 总结080. 使用C语言实现简单的数据库操作 在…...
3403. 从盒子中找出字典序最大的字符串 I
3403. 从盒子中找出字典序最大的字符串 I 题目链接:3403. 从盒子中找出字典序最大的字符串 I 代码如下: class Solution { public:string answerString(string word, int numFriends) {if (numFriends 1) {return word;}string res;for (int i 0;i &…...

算法笔记2
1.字符串拼接最好用StringBuilder,不用String 2.创建List<>类型的数组并创建内存 List arr[] new ArrayList[26]; Arrays.setAll(arr, i -> new ArrayList<>()); 3.去掉首尾空格...
AGain DB和倍数增益的关系
我在设置一款索尼CMOS芯片时,Again增益0db变化为6DB,画面的变化只有2倍DN的增益,比如10变为20。 这与dB和线性增益的关系以及传感器处理流程有关。以下是具体原因分析: 1. dB与线性增益的换算关系 6dB对应的理论线性增益应为&…...