跳转至

编程语言

不同语言间的区别

Go

  • 一切都是值传递(所有参数传递都是按值复制)
  • 变量分配到堆、栈是由编译器根据逃逸分析确定的,而非数据类型
  • 全局变量以 var 声明
  • 局部变量可通过 := 赋值
  • iota 只能在 const 声明中用于自增
  • 接口隐式实现(鸭子类型)
  • 不支持三元运算符、++i
  • while循环,只有for循环
  • 通过首字母大小写控制可见性
  • delete() 专用于删除map中的键值对
  • 操作队列、堆比较繁琐
  • 函数支持多返回值

Python

  • 一切皆对象
  • 不支持三元运算符(用if-else替代)、++ii++
  • 不支持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

垃圾回收算法

  1. 引用计数(Reference Counting)

    • 原理:每个对象维护一个引用计数器,引用 +1,释放 -1,计数为 0 则回收。
    • 优点:简单易实现,可预测性好,内存立即回收。
    • 缺点:无法处理循环引用,每次引用变化都需更新计数(有开销)。
    • 应用:Python(主机制)、PHP、Swift(弱引用+周期检测)。
  2. 标记-清除(Mark-Sweep)

    • 原理
      1. 标记:从根对象出发遍历,标记所有可达对象。
      2. 清除:回收未标记的对象。
    • 优点:能处理循环引用。
    • 缺点:会产生内存碎片,需要 STW(Stop The World)。
    • 应用:Java(新生代)、JavaScript、C#。
  3. 标记-压缩(Mark-Compact)

    • 原理:在标记-清除基础上,将存活对象向一端移动,消除碎片。
    • 优点:无碎片,可连续分配。
    • 缺点:移动对象开销大,需要 STW。
    • 应用:Java(老年代)、C#。
  4. 复制(Copying)

    • 原理:将内存分为两半(From-Space 和 To-Space),只回收 From-Space。
    • 优点:分配快,无碎片。
    • 缺点:空间浪费一半。
    • 应用:Java(新生代)、Go(新生代)。
  5. 分代回收(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 声明的模块名 必须一致(导入路径/模块名必须对应物理文件/目录)

代码阅读能力

面对一个陌生项目,按以下步骤可以快速建立全局认知、找到切入点:

  1. 阅读 README.md 了解其功能
  2. 查看项目目录结构,了解各个模块的功能
  3. 把项目跑起来
  4. 找到入口文件,跟踪核心调用链路(部分关键代码可采用debug、单元测试、AI解释等方式辅助理解)
  5. 也可以根据某个重要函数的调用关系反向跟踪

金融计算与精度处理

计算机使用 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

最佳实践:

  1. 始终使用字符串创建 Decimal,绝不能先传入 float 再转换——传入的那一刻精度就已经丢失了。
  2. 中间计算多保留几位小数(建议 4~6 位),仅在最终结算或展示时才执行 Round 保留 2 位,以防累积舍入误差。
  3. 分账/拆单场景下,优先使用 Money 库自带的分配(allocate)方法,避免手动除法导致的"少一分"问题。

评论