摸鱼精选第 35 期
1. 请删掉 99%的 useMemo
一般来说,如果是基础的中后台应用,大多数交互都比较粗糙,通常不需要。如果你的应用类似图形编辑器,大多数交互是颗粒状的(比如说移动形状),那么此时 useMemo 可能会起到很大的帮助。
使用 useMemo 进行优化仅在少数情况下有价值:
- 你明确知道这个计算非常的昂贵,而且它的依赖关系很少改变。
- 如果当前的计算结果将作为 memo 包裹组件的 props 传递。计算结果没有改变,可以利用 useMemo 缓存结果,跳过重渲染。
- 当前计算的结果作为某些 hook 的依赖项。比如其他的 useMemo/useEffect 依赖当前的计算结果。
这几句是不是很熟悉,就是开头我说的 useMemo 的官方文档的用法中提到的这几项。
在其他情况下,将计算过程包装在 useMemo 中没有任何好处。不过这样做也没有重大危害,所以一些团队选择不考虑具体情况,尽可能多地使用 useMemo,这种做法会降低代码可读性。此外,并不是所有 useMemo 的使用都是有效的:一个“永远是新的”的单一值就足以破坏整个组件的记忆化效果。
2. 使用 Taro 开发鸿蒙原生应用 第三篇
本指南详细介绍了鸿蒙运行环境的配置、使用 Taro 开发鸿蒙应用的步骤和注意事项。
其他两篇姊妹文章:
使用 Taro 开发鸿蒙原生应用第一篇——探秘适配鸿蒙 ArkTS 的工作原理
使用 Taro 开发鸿蒙原生应用第二篇 —— 当 Taro 遇到纯血鸿蒙
3. 深入理解经典红黑树
本文主要介绍了红黑树的经典实现,包括 2-3-4 搜索树和经典红黑树的定义、性质,以及红黑树的插入和删除节点操作。此外,还分析了红黑树的时间复杂度,并给出了相关的参考资料。
重要亮点
- 📖 2-3-4 搜索树:在 2-3 搜索树中增加了 4-节点,新节点插入 4-节点时,需要先将 4-节点转换成 3 个 2-节点,再在其中的 2-节点中执行插入操作。
- 🔴 经典红黑树:与 2-3-4 搜索树同构,满足节点颜色为红色或黑色、根节点是黑色的、叶子节点为黑色、红色节点的两个子节点为黑色、任意叶子节点到根节点路径上的黑色节点数量相同等性质。
- ⚙️ 插入节点:插入 2-节点直接将节点插入 2-节点;插入 3-节点需要分左斜 3-节点和右斜 3-节点两种情况讨论;插入 4-节点需要将其分解成 3 个 2-节点,之后将 2-节点的“根节点”合并到它的父节点中。
- 🗑️ 删除节点:删除节点是红黑树中最复杂的实现,如果删除的是 3-节点或 4-节点,那么移除该节点不会影响红黑树的平衡;如果删除的是 2-节点,需要在删除节点后向上修复红黑树的平衡。
- 🕙 时间复杂度:含有 n 个节点的红黑树的高度为 logn,不调用 fixAfterDeletion 方法时,复杂度为 O(logn),在 fixAfterDeletion 中,情况 1,3,4 在各执行常数次的颜色改变和至多 3 次旋转后便终止,只有在情况 2 中才可能重复修复平衡,指针也至多上升 O(logn)次,且没有任何旋转操作,所以 fixAfterDeletion 复杂度为 O(logn),最多旋转 3 次,因此红黑树删除方法的时间复杂度为 O(logn)。
- 📚 巨人的肩膀:《算法导论》:第 13 章红黑树、知乎-关于 AVL 树和红黑树的一点看法、LeetCode-红黑树从入门到看开、博客园-红黑树的删除。
4. 教不会你算我输系列 | 手把手教你 HarmonyOS 应用开发
本文介绍了鸿蒙系统的开发,包括开发简介、第一个鸿蒙版 Hello World、小试牛刀-度加首页和基础图谱入门。鸿蒙系统是一款面向万物互联的全场景分布式操作系统,支持手机、平板、智能穿戴、智慧屏和车机等多种终端设备。本文还介绍了鸿蒙系统的开发环境搭建、开发套件、第一个鸿蒙应用的创建、项目结构、运行和基础图谱入门。
5. C++常见避坑指南
这篇文章是一份全面的 C++编程避坑指南,它详细总结了在 C++开发中常见的错误和最佳实践。以下是文章的精确凝练总结:
- 空指针调用成员函数:静态成员函数可以被空指针调用,而非静态成员函数则会导致程序崩溃。
- 字符串处理:使用
std::string
的查找方法时要注意逻辑严谨性,避免错误的字符串匹配。同时,std::string
与std::wstring
之间的转换要确保字符编码一致,避免乱码。 - 全局静态对象:虽然提供了良好的封装和线程安全,但可能会增加程序启动时间和资源消耗,建议使用
extern
和constexpr
来声明全局常量。 - 迭代器删除:在使用 STL 容器时,要注意迭代器失效问题,使用
erase
和remove
组合可以安全地删除元素。 - 对象拷贝:避免不必要的对象拷贝,使用引用传递和返回值优化(RVO/NRVO)来提升性能。
std::shared_ptr
线程安全:shared_ptr
的引用计数操作是线程安全的,但修改指向时需注意线程安全问题。std::map
使用:使用operator[]
可能插入未定义值的元素,应确保键存在或指定默认值。sizeof
与strlen
:sizeof
返回类型或变量的字节大小,strlen
返回以 null 结尾的字符串长度,两者使用场景不同。std::async
:可能不是真正的异步,取决于启动策略,确保使用std::launch::async
以实现真正的异步执行。- 内存泄漏:在使用智能指针如
std::shared_ptr
时,确保在异常安全的情况下创建和管理,避免内存泄漏。 const
和constexpr
:const
保证变量不可修改,而constexpr
用于编译时可计算的常量表达式,提高程序性能。
文章最后鼓励 C++开发者交流经验,以避免这些常见的陷阱,并提供了一些额外的学习资源链接。
6. 迈向 Android 架构师:模块化设计原则
这篇文章讨论了 Android 架构中的模块化设计原则,以实现高内聚和低耦合的代码结构。以下是文章的精确凝练总结:
-
组件与模块定义:组件是业务相关的文件集合,在 Android 中等同于 Gradle 模块。
-
模块化的好处:包括更合理的代码目录、更快的编译速度和更高的开发效率。
-
内聚性原则:由共同闭包原则(CCP)、共同复用原则(CRP)和复用发布等同原则(REP)组成,指导如何合理划分模块以追求高内聚。
-
耦合性原则:包括无环依赖原则(ADP)、稳定依赖原则(SDP)和稳定抽象原则(SAP),关注模块间的依赖关系。
-
模块划分:讨论了基于层级、业务和领域的划分方法,每种方法都有其优势和局限性。
-
封装模块:强调模块内部代码应该避免公开,以保持封装性和降低耦合。
-
集成模块:app 模块作为集成层,负责连接模块所需的所有“胶水代码”、初始化逻辑、导航代码和公共依赖注入。
-
实践建议:推荐学习《整洁架构》一书,以深入理解 Android 架构理念,并应用于实际项目中。
文章强调,模块化设计是每个初阶工程师迈向高阶过程中必须掌握的知识,整洁架构中的设计原则对于构建可维护和可扩展的 Android 应用至关重要。
7. 自研流媒体协议探索与实践
B 站流媒体技术部直播组开发了自研流媒体协议 BMT(Bili Media Transport),旨在解决直播下行带宽节省和回源带宽成本问题。BMT 协议在兼容性、载荷比和握手简化方面进行了优化,支持多种编解码器和多轨道能力,减少额外开销,提高效率。通过核心库 libbmt 的开发和回源架构设计,BMT 在自建 CDN 回源带宽上取得显著节省效果,并在多音轨直播活动中展现了其优势。未来,BMT 协议将继续迭代,探索在推流端的应用并增加互动玩法,为业务赋能。
8. Flutter 鸿蒙终端一体化—鹊桥相会
文章介绍了如何在鸿蒙系统中开发 Flutter 项目,并解决 Flutter 开发者不熟悉鸿蒙代码的问题。通过使用 pigeon 工具,可以一键生成 ets 代码,实现多端代码复用。文章提供了详细的代码示例和步骤,包括如何指定引用、创建接口协议文件、执行命令生成 Channel 文件,以及鸿蒙端的代码实现。通过这种方式,Flutter 代码可以无损迁移,实现 Android、iOS、鸿蒙的 UI 多端统一。
9. Beyond Compare! Rust Vs Js
文章由 360 奇舞团前端开发工程师撰写,探讨了 Rust 与 JavaScript/TypeScript 在 Web 开发中的对比。作者在计划开发一个 SSR 渲染的 Blog 时,面临了选择 JavaScript 还是 Rust 的决策。文章从前端视角出发,寻找 Rust 与 Js/Ts 的语法相似之处,帮助加深对 Rust 语言的理解。同时,文章还讨论了 Rust 中一些有争议的新特性,如 Async,并提供了 Rust 与 JavaScript 在异步编程、类型系统、错误处理、模块系统等方面的详细对比。作者建议前端开发者通过实践和编译错误来学习 Rust,并尝试在 Rust 中实现熟悉的 JavaScript/TypeScript 功能或概念,以此加深两种语言的相似之处和不同之处的认知。
10. 五年沉淀,微信全平台终端数据库 WCDB 迎来重大升级!
微信客户端团队开源的 WCDB 数据库经过五年发展,推出重大升级。新版 WCDB 不向后兼容旧接口,增加 C++核心逻辑支持,通过桥接方法使 Swift 和 Java 能接入。它完整支持 Java 和 Kotlin ORM,覆盖更多终端平台。WCDB 重写 Winq 提高 SQL 表达能力,强化数据备份和修复方案,引入数据迁移、压缩及自动添加新列功能,并进行 FTS5 优化和支持可中断事务。这些改进让 WCDB 在性能、安全性、灵活性和扩展性方面有显著提升,支持多语言且易于集成,适用于复杂业务场景。
11. 客户端动态降级系统
文章讨论了客户端开发中如何通过动态降级系统确保在不同硬件和网络环境下提供流畅的用户体验。服务端已有的降级和熔断机制可以作为客户端设计降级系统的参考。客户端需要处理性能和网速两大类问题,进一步细分为 CPU、内存、电量和网速等方面。通过实时监控这些指标并设定阈值,系统能够计算出性能等级并在必要时通知业务方进行降级处理。
系统设计包括三个主要部分:DynamicLevelManager
负责调用监控和决策模块,并通过通知告知业务方;DynamicLevelMonitor
监控关键性能指标;DynamicLevelDecision
对性能指 标进行计算并决定性能级别。文章提供了伪代码示例,阐释了设计思路,并展示了如何在 iOS 系统中实现监控和计算逻辑。
降级处理的示例包括降低网络请求图片尺寸等,目的是减少系统性能消耗,快速恢复到正常运行状态。文章还介绍了如何通过NSNotificationCenter
发送性能降级或恢复通知,并根据当前性能等级调整业务处理规则。
整体而言,动态降级系统通过实时监控和智能决策,帮助客户端在面对性能或网络问题时,依然能够提供相对流畅的用户体验,并保证应用内功能的正常使用不受影响。
12. Ollama:本地大模型运行指南
Ollama 是一个开源框架,允许用户在本地运行大型语言模型,如 Llama 3、Mistral、Gemma 等,同时支持自定义和创建新模型。该框架使用 Go 语言开发,适用于 macOS、Linux 和 Windows 系统。
13. Java 线程池的实现原理及其在业务中的最佳实践
本文深入探讨了 Java 线程池的实现原理、源码分析,并提供了在业 务场景中使用线程池的最佳实践。
线程池简介:
- 定义:线程池是一种管理并复用线程的机制,通过预先创建并维护一定数量的线程来执行任务,减少线程创建和销毁的性能开销。
- 好处:减少线程创建和销毁的开销,控制和优化系统资源利用,提高响应速度和并发性能。
Java 线程池的实现原理:
- 类继承关系:核心类为
ThreadPoolExecutor
,继承自AbstractExecutorService
。 - 核心方法:包括
execute()
、submit()
、shutdown()
等。 - 线程池状态:包括 RUNNING、SHUTDOWN、STOP、TIDYING、TERMINATED。
- 执行流程:任务提交后,根据当前线程数和队列状态决定是直接执行、入队列还是创建新线程。
- 问题思考:分析了线程池的核心线程是否可回收,以及是否可以在提交任务前创建线程。
源码分析:
- execute(Runnable command):任务提交入口,根据线程池状态和工作线程数决定任务去向。
- addWorker(Runnable firstTask, boolean core):创建新工作线程并添加到线程池。
- runWorker(Worker w):执行任务,支持线程复用。
- getTask():从阻塞队列中获取任务。
- processWorkerExit(w, completedAbruptly):处理线程退出。
线程池在业务中的最佳实践:
- 选择合适参数:根据任务类型(CPU 密集型或 I/O 密集型)和线程池用途(快速响应或批量处理)选择合适的参数。
- 正确创建线程池对象:推荐使用饿 汉式的单例模式创建线程池,支持灵活参数配置。
- 避免相互依赖的子任务使用同一线程池:防止线程饥饿和死锁。
- 合理选择 submit()和 execute()方法:根据任务是否需要返回结果选择合适的方法。
- 捕获线程池中子任务的异常:防止线程异常导致资源浪费。
文章强调了在业务开发中合理利用线程池的重要性,并提供了实际代码示例和问题分析,帮助开发者更高效地使用线程池。
14. JVM 的基础入门
文章全面介绍了 Java 虚拟机(JVM)的基础知识,涵盖了 JVM 内存划分、堆内存分配策略、对象创建步骤、引用类型、类加载机制、垃圾回收(GC)及其算法、垃圾收集器配置、性能调优工具和故障排查策略。
JVM 内存划分:包括堆、方法区(元空间)、虚拟机栈、本地方法栈和程序计数器。堆是对象实例存储的共享区域,方法区存储类信息和静态变量,JDK1.8 后改为元空间。
堆内存分配策略:对象优先在 Eden 区分配,大对象直接进入老年代,长期存活的对象也会进入老年代。动态对象年龄判定和空间分配担保是确保 GC 安全进行的机制。
创建对象步骤:类加载检查、分配内存、初始化零值、设置对象头和执行 init 方法。
对象引用:包括强引用、软引用、弱引用和虚引用,影响垃圾回收行为。
JVM 类加载过程:包括加载、验证、准备、解析和初始化五个阶段。双亲委派机制保证了核心类的安全加载。
垃圾回收:介绍了引用计数法和可达性分析法,以及两次标记过程。垃圾回收算法包括复制算法、标记清除、标记整理和分代收集。
垃圾收集器:从 Serial、Parnew 到 CMS、G1、ZGC 等,各有特点,适用于不同的应用场景。
性能调优:使用 jps、jinfo、jstat 等工具监控和调优 JVM 性能。
故障排查:包括硬件故障、内存泄漏、CPU 飙高、死循环等问题的排查方法。
文章适合作为 JVM 入门和日常调优工作的参考。
15. Netty 的基础入门
本文详细介绍了 Netty 框架的核心组件、逻辑架构、IO 模型、Reactor 多线程模型、拆包粘包问题解决方案、自定义协议、WriteAndFlush 操作、内存管理、高性能数据结构和定时器原理。
核心组件:
- Core 核心层:提供底层网络通信的通用抽象和实现。
- Protocol Support 协议支持层:覆盖主流协议的编解码实现。
- Transport Service 传输服务层:定义和实现网络传输能力。
逻辑架构:
- 网络通信层:执行网络 I/O 操作,包括 BootStrap、ServerBootStrap 和 Channel。
- 事件调度层:通过 Reactor 线程模型对事件进行聚合处理,核心组件为 EventLoopGroup 和 EventLoop。
- 服务编排层