编程语言¶
不同语言间的区别¶
Go¶
- 一切都是值传递(所有参数传递都是按值复制)
- 变量分配到堆、栈是由编译器根据逃逸分析确定的,而非数据类型
- 全局变量以
var声明 - 局部变量可通过
:=赋值 iota只能在const声明中用于自增- 接口隐式实现(鸭子类型)
- 不支持三元运算符、
++i - 无
while循环,只有for循环 - 通过首字母大小写控制可见性
delete()专用于删除map中的键值对- 操作队列、堆比较繁琐
- 函数支持多返回值
Python¶
- 一切皆对象
- 不支持三元运算符(用
if-else替代)、++i、i++ - 不支持
switch,用match替代 - 算法题目语法友好,首选语言
- 函数通过
tuple支持多返回值
Rust¶
- 包在父文件中声明
- 变量默认不可变
- 无
null,用Option<T>替代 - 错误处理用
Result<T, E> - 函数通常以最后的表达式返回,而非
return关键字 - 操作链表非常繁琐
- 与Python一样,函数通过
tuple支持多返回值
PHP¶
array同时承担普通数组与map的职责,以索引数组 / 关联数组两种形式使用- 弱类型
==会做隐式类型转换,严格比较需用=== - 无
main函数,脚本从顶层直接执行 - 字符串拼接用
.而非+ - 对象或属性访问使用
->而非. - 函数通过数组返回多值
变量名与函数名风格¶
| 语言 | 变量名风格 | 函数名风格 | 文件名风格 |
|---|---|---|---|
| Go | 驼峰(camelCase) |
驼峰(camelCase) |
蛇形(snake_case.go) |
| Python | 蛇形(snake_case) |
蛇形(snake_case) |
蛇形(snake_case.py) |
| Rust | 蛇形(snake_case) |
蛇形(snake_case) |
蛇形(snake_case.rs) |
| Java | 驼峰(camelCase) |
驼峰(camelCase) |
大驼峰(PascalCase.java) |
| JavaScript | 驼峰(camelCase) |
驼峰(camelCase) |
短横线(kebab-case.js) |
| PHP | 驼峰(camelCase) |
方法用驼峰,函数用蛇形 | 大驼峰(PascalCase.php) |
并发调度模型¶
所有主流语言(C、C++、Java、Rust)的原生线程都是受操作系统内核控制的抢占式调度。协程调度则分为以下两种:
| 语言 | 调度方式 | 栈类型 | 机制说明 |
|---|---|---|---|
| Python | 协作式 | 无栈 | asyncio 事件循环,await 处主动让出 |
| JavaScript | 协作式 | 无栈 | 事件循环,await 处主动让出 |
| C# | 协作式 | 无栈 | async/await,编译为状态机 |
| Rust | 协作式 | 无栈 | async/await,编译为状态机,无运行时 |
| Go | 抢占式 | 有栈 | 信号驱动(SIGURG),1.14+ 无条件抢占 |
| Erlang/Elixir | 抢占式 | 有栈 | reduction 计数,死循环也不会卡死调度器 |
| Haskell | 抢占式 | 有栈 | 运行时定时器信号抢占 |
抢占式需要运行时引入抢占信号、上下文保存、原子锁、甚至复杂的GC支持,这会带来不可忽视的运行时开销。而协作式在编译后仅仅是一个简单的状态机,没有后台monitor线程,性能极高,真正实现了“不使用就不付出代价”。 另外,在协作式模型中,同一个协程内两个 await 之间的代码不会被中断,对于单线程运行时(如 Node.js、Python asyncio)而言,任意协程间都不会并发执行,局部状态天然安全。而抢占式模型中,任何一行代码执行完都可能被突然抢占,如果不加锁或不用Channel,很容易写出隐蔽的数据竞争。
GC¶
垃圾回收算法¶
-
引用计数(Reference Counting)
- 原理:每个对象维护一个引用计数器,引用 +1,释放 -1,计数为 0 则回收。
- 优点:简单易实现,可预测性好,内存立即回收。
- 缺点:无法处理循环引用,每次引用变化都需更新计数(有开销)。
- 应用:Python(主机制)、PHP、Swift(弱引用+周期检测)。
-
标记-清除(Mark-Sweep)
- 原理:
- 标记:从根对象出发遍历,标记所有可达对象。
- 清除:回收未标记的对象。
- 优点:能处理循环引用。
- 缺点:会产生内存碎片,需要 STW(Stop The World)。
- 应用:Java(新生代)、JavaScript、C#。
- 原理:
-
标记-压缩(Mark-Compact)
- 原理:在标记-清除基础上,将存活对象向一端移动,消除碎片。
- 优点:无碎片,可连续分配。
- 缺点:移动对象开销大,需要 STW。
- 应用:Java(老年代)、C#。
-
复制(Copying)
- 原理:将内存分为两半(From-Space 和 To-Space),只回收 From-Space。
- 优点:分配快,无碎片。
- 缺点:空间浪费一半。
- 应用:Java(新生代)、Go(新生代)。
-
分代回收(Generational)
- 原理:根据对象存活率划分代(新生代、老年代),不同代使用不同算法。
- 优点:效率高(大部分对象在新生代,且生命短)。
- 应用:Java、Python、C#。
不同语言的GC机制¶
| 语言 | 垃圾回收机制 | 特点 |
|---|---|---|
| Java | 标记-清除 + 标记-压缩 + 分代回收 | 了解对象年龄、GC Roots、Stop The World 概念 |
| Go | 三色标记 + 混合写屏障 | 弱一致性,标记期间不 STW |
| Rust | 所有权 + 引用计数(Rc)+ 析构函数(drop) |
编译期检查,无运行时 GC |
| Python | 析构函数(__del__())+ 引用计数(GIL难以消除的原因) + 分代回收 + 循环垃圾回收器(模拟减1解决循环引用) |
只有容器对象(如list、dict、自定义类实例)才会产生循环引用 |
| JavaScript | 标记-清除 + 引用计数(IE) | 现代引擎多用标记-清除 |
| PHP | 析构函数(__destruct())+ 引用计数 + 循环垃圾回收器(模拟减1解决循环引用) |
由于多线程环境下的引用计数需要原子操作,而原子操作在多核CPU上的开销是非常昂贵的(通常比普通读写慢数十倍)。因此,Python 和 PHP 选择非原子引用计数(避免原子操作开销),因此必须依赖 GIL 或单线程模型来保证线程安全。而 Swift/C++ 使用原子引用计数,代价是每次引用变化都有 CAS 开销。
命名空间/包/模块¶
| 语言 | 模块/包名方式 | 与文件名一致? |
|---|---|---|
| Rust | 父模块声明的 mod name |
必须一致(找 name.rs) |
| Python | 文件名(如 xxx.py) |
必须一致(import xxx) |
| Go | 文件内的 package name |
不需要一致(通常一致) |
| PHP | 代码顶部的 namespace XXX; |
语法上不需要(PSR-4规范需要) |
| Java | 文件顶部的 package xxx.yyy; 声明 |
必须一致(文件名必须与 public 类名一致;且包路径必须与目录路径一致) |
| JavaScript | 文件路径,或 package.json 声明的模块名 |
必须一致(导入路径/模块名必须对应物理文件/目录) |
代码阅读能力¶
面对一个陌生项目,按以下步骤可以快速建立全局认知、找到切入点:
- 阅读 README.md 了解其功能
- 查看项目目录结构,了解各个模块的功能
- 把项目跑起来
- 找到入口文件,跟踪核心调用链路(部分关键代码可采用debug、单元测试、AI解释等方式辅助理解)
- 也可以根据某个重要函数的调用关系反向跟踪
金融计算与精度处理¶
计算机使用 IEEE 754 标准存储浮点数,本质上只能表示二进制分数的近似值。在金融场景中,浮点误差会随运算次数不断累积,最终导致对账不平或资损。
常见的解决思路有两种:
- 整数化:将金额转换为货币最小单位(如"分")的整数进行运算,彻底规避精度问题。
- Decimal 类型:使用任意精度的十进制库,在可控精度下进行算术运算。
各语言推荐的 Decimal / Money 库如下:
| 语言 | Decimal 库 | Money 库(带货币单位/分账) |
|---|---|---|
| Go | shopspring/decimal |
Rhymond/go-money |
| Python | decimal(标准库) |
py-moneyed |
| Rust | rust_decimal |
rusty_money |
| PHP | moneyphp/money |
brick/money |
| Java | BigDecimal(标准库) |
JavaMoney (JSR 354) |
| JS/TS | decimal.js |
dinero.js |
最佳实践:
- 始终使用字符串创建 Decimal,绝不能先传入
float再转换——传入的那一刻精度就已经丢失了。 - 中间计算多保留几位小数(建议 4~6 位),仅在最终结算或展示时才执行
Round保留 2 位,以防累积舍入误差。 - 分账/拆单场景下,优先使用 Money 库自带的分配(allocate)方法,避免手动除法导致的"少一分"问题。