【HarmonyOS开发小实践】HarmonyOS GC 引用计数 vs 对象追踪,三种回收算法

HarmonyOS GC 引用计数 vs 对象追踪,三种回收算法

手动管内存的年代,C 程序员一半的 bug 都来自忘记 free 或者 free 了又用。GC 把这件事自动化了,但自动化的方式不止一种。ArkTS 运行时选了对象追踪这条路,原因是什么,得先把两大类算法的优缺点摆出来看。这篇就把引用计数、对象追踪,以及对象追踪下的三种回收算法讲透,给后面看 HPP GC 打底。

为什么需要 GC

程序运行时在堆上分配对象,用完了得回收,否则内存越占越多最终 OOM。手动管理(C/C++ 的 malloc/free、new/delete)要求开发者精确知道每个对象什么时候不再被使用,这在有复杂引用关系的程序里极难做到:

  • 释放早了:还有别的地方持有引用,访问到野指针,crash。
  • 释放晚了:内存一直占着,泄漏。
  • 释放两次:堆被破坏,行为未定义。

GC 的思路是让运行时自动判断哪些对象不再被使用,自动回收。判断"不再被使用"的方法,就是 GC 算法的核心。两大流派:引用计数和对象追踪。

引用计数法

原理很直白:每个对象带一个计数器,有多少个引用指向它计数就是多少。引用建立时 +1,断开时 -1,降到 0 就回收。
#mermaid-svg-9m9chlr8gbDGsYDR{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-9m9chlr8gbDGsYDR .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-9m9chlr8gbDGsYDR .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-9m9chlr8gbDGsYDR .error-icon{fill:#552222;}#mermaid-svg-9m9chlr8gbDGsYDR .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-9m9chlr8gbDGsYDR .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-9m9chlr8gbDGsYDR .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-9m9chlr8gbDGsYDR .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-9m9chlr8gbDGsYDR .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-9m9chlr8gbDGsYDR .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-9m9chlr8gbDGsYDR .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-9m9chlr8gbDGsYDR .marker{fill:#333333;stroke:#333333;}#mermaid-svg-9m9chlr8gbDGsYDR .marker.cross{stroke:#333333;}#mermaid-svg-9m9chlr8gbDGsYDR svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-9m9chlr8gbDGsYDR p{margin:0;}#mermaid-svg-9m9chlr8gbDGsYDR .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-9m9chlr8gbDGsYDR .cluster-label text{fill:#333;}#mermaid-svg-9m9chlr8gbDGsYDR .cluster-label span{color:#333;}#mermaid-svg-9m9chlr8gbDGsYDR .cluster-label span p{background-color:transparent;}#mermaid-svg-9m9chlr8gbDGsYDR .label text,#mermaid-svg-9m9chlr8gbDGsYDR span{fill:#333;color:#333;}#mermaid-svg-9m9chlr8gbDGsYDR .node rect,#mermaid-svg-9m9chlr8gbDGsYDR .node circle,#mermaid-svg-9m9chlr8gbDGsYDR .node ellipse,#mermaid-svg-9m9chlr8gbDGsYDR .node polygon,#mermaid-svg-9m9chlr8gbDGsYDR .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-9m9chlr8gbDGsYDR .rough-node .label text,#mermaid-svg-9m9chlr8gbDGsYDR .node .label text,#mermaid-svg-9m9chlr8gbDGsYDR .image-shape .label,#mermaid-svg-9m9chlr8gbDGsYDR .icon-shape .label{text-anchor:middle;}#mermaid-svg-9m9chlr8gbDGsYDR .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-9m9chlr8gbDGsYDR .rough-node .label,#mermaid-svg-9m9chlr8gbDGsYDR .node .label,#mermaid-svg-9m9chlr8gbDGsYDR .image-shape .label,#mermaid-svg-9m9chlr8gbDGsYDR .icon-shape .label{text-align:center;}#mermaid-svg-9m9chlr8gbDGsYDR .node.clickable{cursor:pointer;}#mermaid-svg-9m9chlr8gbDGsYDR .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-9m9chlr8gbDGsYDR .arrowheadPath{fill:#333333;}#mermaid-svg-9m9chlr8gbDGsYDR .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-9m9chlr8gbDGsYDR .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-9m9chlr8gbDGsYDR .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-9m9chlr8gbDGsYDR .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-9m9chlr8gbDGsYDR .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-9m9chlr8gbDGsYDR .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-9m9chlr8gbDGsYDR .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-9m9chlr8gbDGsYDR .cluster text{fill:#333;}#mermaid-svg-9m9chlr8gbDGsYDR .cluster span{color:#333;}#mermaid-svg-9m9chlr8gbDGsYDR div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-9m9chlr8gbDGsYDR .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-9m9chlr8gbDGsYDR rect.text{fill:none;stroke-width:0;}#mermaid-svg-9m9chlr8gbDGsYDR .icon-shape,#mermaid-svg-9m9chlr8gbDGsYDR .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-9m9chlr8gbDGsYDR .icon-shape p,#mermaid-svg-9m9chlr8gbDGsYDR .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-9m9chlr8gbDGsYDR .icon-shape .label rect,#mermaid-svg-9m9chlr8gbDGsYDR .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-9m9chlr8gbDGsYDR .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-9m9chlr8gbDGsYDR .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-9m9chlr8gbDGsYDR :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 被 B 引用
又被 C 引用
C 释放引用
B 释放引用
对象 A

count=0
对象 A

count=1
对象 A

count=2
对象 A

count=1
对象 A

count=0

✅ 回收

优点

  • 及时回收:计数一降到 0 立刻回收,不用等专门的 GC 暂停。
  • 无 STW:没有 Stop The World 阶段,回收动作分散在每次引用变更时。
  • 实现简单:计数器加减,逻辑直白。

致命缺陷:循环引用

typescript 复制代码
class Parent {
  child: Child | null = null;
}

class Child {
  parent: Parent | null = null;
}

function main() {
  const parent: Parent = new Parent();
  const child: Child = new Child();
  parent.child = child;   // child 计数 +1 → 2
  child.parent = parent;  // parent 计数 +1 → 2
  // main 返回后,parent 和 child 的局部变量引用断开
  // 但它们互相引用,计数都是 1,永远降不到 0
}

parent 和 child 互相持有,函数结束后两个对象的计数都是 1,谁也释放不了。这就是循环引用导致的内存泄漏,引用计数法从原理上解决不了。

其他缺点

  • 性能开销:每次赋值都要改计数器,频繁的引用变更场景下开销累积。
  • 计数溢出:理论上计数器有上限,虽然实际很少遇到。
  • 线程安全:多线程下计数器加减要原子操作,有竞争开销。

对象追踪法

对象追踪(Tracing GC)的思路反过来:不跟踪引用的建立和断开,而是从一组肯定存活的对象出发,顺着引用链遍历,能遍历到的就是活的,遍历不到的就是垃圾。

根对象

遍历的起点叫 GC Root,包括:

  • 栈上的局部变量
  • 全局对象
  • 活跃的 ArkTS 函数闭包变量
  • Native 引用持有的对象

从 Root 出发能到达的对象都是存活的,到达不了的就是垃圾。
#mermaid-svg-EapmWMEZXFagBsFz{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-EapmWMEZXFagBsFz .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-EapmWMEZXFagBsFz .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-EapmWMEZXFagBsFz .error-icon{fill:#552222;}#mermaid-svg-EapmWMEZXFagBsFz .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-EapmWMEZXFagBsFz .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-EapmWMEZXFagBsFz .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-EapmWMEZXFagBsFz .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-EapmWMEZXFagBsFz .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-EapmWMEZXFagBsFz .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-EapmWMEZXFagBsFz .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-EapmWMEZXFagBsFz .marker{fill:#333333;stroke:#333333;}#mermaid-svg-EapmWMEZXFagBsFz .marker.cross{stroke:#333333;}#mermaid-svg-EapmWMEZXFagBsFz svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-EapmWMEZXFagBsFz p{margin:0;}#mermaid-svg-EapmWMEZXFagBsFz .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-EapmWMEZXFagBsFz .cluster-label text{fill:#333;}#mermaid-svg-EapmWMEZXFagBsFz .cluster-label span{color:#333;}#mermaid-svg-EapmWMEZXFagBsFz .cluster-label span p{background-color:transparent;}#mermaid-svg-EapmWMEZXFagBsFz .label text,#mermaid-svg-EapmWMEZXFagBsFz span{fill:#333;color:#333;}#mermaid-svg-EapmWMEZXFagBsFz .node rect,#mermaid-svg-EapmWMEZXFagBsFz .node circle,#mermaid-svg-EapmWMEZXFagBsFz .node ellipse,#mermaid-svg-EapmWMEZXFagBsFz .node polygon,#mermaid-svg-EapmWMEZXFagBsFz .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-EapmWMEZXFagBsFz .rough-node .label text,#mermaid-svg-EapmWMEZXFagBsFz .node .label text,#mermaid-svg-EapmWMEZXFagBsFz .image-shape .label,#mermaid-svg-EapmWMEZXFagBsFz .icon-shape .label{text-anchor:middle;}#mermaid-svg-EapmWMEZXFagBsFz .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-EapmWMEZXFagBsFz .rough-node .label,#mermaid-svg-EapmWMEZXFagBsFz .node .label,#mermaid-svg-EapmWMEZXFagBsFz .image-shape .label,#mermaid-svg-EapmWMEZXFagBsFz .icon-shape .label{text-align:center;}#mermaid-svg-EapmWMEZXFagBsFz .node.clickable{cursor:pointer;}#mermaid-svg-EapmWMEZXFagBsFz .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-EapmWMEZXFagBsFz .arrowheadPath{fill:#333333;}#mermaid-svg-EapmWMEZXFagBsFz .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-EapmWMEZXFagBsFz .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-EapmWMEZXFagBsFz .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-EapmWMEZXFagBsFz .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-EapmWMEZXFagBsFz .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-EapmWMEZXFagBsFz .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-EapmWMEZXFagBsFz .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-EapmWMEZXFagBsFz .cluster text{fill:#333;}#mermaid-svg-EapmWMEZXFagBsFz .cluster span{color:#333;}#mermaid-svg-EapmWMEZXFagBsFz div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-EapmWMEZXFagBsFz .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-EapmWMEZXFagBsFz rect.text{fill:none;stroke-width:0;}#mermaid-svg-EapmWMEZXFagBsFz .icon-shape,#mermaid-svg-EapmWMEZXFagBsFz .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-EapmWMEZXFagBsFz .icon-shape p,#mermaid-svg-EapmWMEZXFagBsFz .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-EapmWMEZXFagBsFz .icon-shape .label rect,#mermaid-svg-EapmWMEZXFagBsFz .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-EapmWMEZXFagBsFz .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-EapmWMEZXFagBsFz .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-EapmWMEZXFagBsFz :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} GC Root
对象 A
对象 B
对象 C
对象 D
对象 E
对象 F

蓝色(A/B/C/D)从 Root 可达,存活;黄色(E/F)不可达,是垃圾。

优点

  • 解决循环引用:循环引用的对象只要从 Root 不可达,就会被识别为垃圾。parent 和 child 互相引用,但 main 返回后从 Root 已经到不了它们,一起回收。
  • 赋值无开销:建立和断开引用不触发任何 GC 动作,赋值就是写个指针。

缺点

  • 有 STW:遍历对象图期间要保证引用关系不变,得暂停业务线程。这个暂停就是 GC 调优的主要目标。
  • 回收延迟:垃圾要等到下一次 GC 才被回收,期间占着内存,叫"浮动垃圾"。
  • 实现复杂:遍历对象图、维护标记位、处理并发标记,比引用计数复杂得多。

ArkTS 为什么选对象追踪

引用计数的循环引用问题在面向对象编程里太常见了------父子节点、双向链表、观察者模式都容易写出循环引用。ArkTS 是面向对象的语言,选引用计数等于把泄漏问题甩给开发者。对象追踪虽然实现复杂、有 STW,但 STW 可以通过并发标记、分代回收等手段压到毫秒级,开发者无感。两害相权,ArkTS 运行时选了对象追踪。

对象追踪的三种回收类型

标记阶段找出存活对象后,怎么回收垃圾有三种基本做法。每种在内存碎片、空间利用率、性能之间做不同取舍。

标记-清扫(Mark-Sweep)

标记完,直接把垃圾对象占的内存放回空闲队列。对象不动,只改空闲链表。

复制代码
标记前:[A活][B垃圾][C活][D垃圾][E活]
标记后:[A活][B空闲][C活][D空闲][E活]
清扫:  把 B、D 加入空闲链表,下次分配从这里取

优点:对象不移动,效率高;对有外部引用的对象友好(地址不变)。

缺点:内存碎片严重。空闲的 B 和 D 不连续,下次要分配一个大对象,虽然总空闲空间够,但没有连续的大块,分配失败。极端情况下明明还有一半内存空闲,却 OOM。

标记-复制(Mark-Compact,又叫 Copying GC)

把堆对半分:From 和 To。GC 时把存活对象从 From 复制到 To,复制完整个 From 一次性回收,下次分配从 To 开始。From 和 To 角色互换。

复制代码
From: [A活][B垃圾][C活][D垃圾][E活]
复制到 To: [A][C][E]   (紧凑排列)
回收 From 整个空间

优点:无碎片,分配快(指针碰撞即可);存活对象少时复制开销小。

缺点:空间利用率 50%,永远有一半堆是空的等下次用。存活对象多时复制开销大。对象地址变了,有外部引用的话要更新所有引用。

适合存活率低的区域------年轻代。新生对象大多朝生夕死,复制开销小,无碎片的收益大。

标记-整理(Mark-Compact)

标记完,把存活对象往一端挪,挤掉中间的垃圾,最后尾部一大块空闲。

复制代码
标记前:[A活][B垃圾][C活][D垃圾][E活]
整理后:[A][C][E][          空闲          ]

优点:无碎片,空间利用率 100%(不像复制算法浪费一半)。

缺点:整理要移动对象、更新所有引用,开销最大。对象多时慢。

适合存活率高、对碎片敏感的区域------老年代。老年代对象大多长期存活,复制开销大,整理虽然慢但频率低,分摊下来可接受。

三种对比

算法 内存碎片 空间利用率 性能 适合区域
标记-清扫 严重 高 快 存活率高、对碎片不敏感
标记-复制 无 50% 存活少时快 年轻代(存活率低)
标记-整理 无 高 慢 老年代(存活率高)

没有一种算法在所有维度上都最优,这就是为什么现代 GC 都走分代+混合算法的路子------不同区域用不同算法。HPP GC 也是这个思路,下一篇细讲。

举个栗子:看三种算法在不同存活率下的表现

假设一个 100MB 的区域,GC 后存活对象占 20MB。

标记-清扫:清扫开销≈0(改空闲链表),总耗时≈标记时间。但留下 80MB 碎片空间,下次分配大对象可能失败。

标记-复制:复制 20MB 存活对象到 To 区,耗时≈复制 20MB。From 区 100MB 整个回收。To 区剩 80MB 连续空闲,分配爽快。代价是平时只有 50MB 可用。

标记-整理:移动 20MB 存活对象到一端,耗时≈移动 20MB + 更新引用。剩 80MB 连续空闲。比复制慢一点(要更新引用),但平时 100MB 都能用。

存活率 80% 时(80MB 存活):

  • 标记-清扫:还是改链表,快。
  • 标记-复制:复制 80MB,慢;而且 To 区只有 50MB,放不下,根本不能用。
  • 标记-整理:移动 80MB,慢,但能跑完。

这就能看出为什么年轻代用复制(存活少、复制快、无碎片),老年代用整理(存活多、复制放不下、整理能扛)。

实践中要注意的

  • 别把 GC 当魔法:GC 能回收不可达对象,但"不可达"不等于"你不用了"。如果你把对象塞进一个全局 Map 当缓存忘了删,它就一直从 Root 可达,GC 永远不回收,这就是内存泄漏。GC 解决的是"找不到引用的对象",不是"逻辑上不再需要的对象"。
  • 循环引用在 ArkTS 里不是问题:对象追踪法天然处理循环引用,parent 互相引用 child,只要从 Root 不可达就一起回收。从 C++/Python(引用计数)转过来的开发者老担心循环引用,在 ArkTS 里这个担心可以放下。
  • STW 暂停能感知到:虽然 HPP GC 把 STW 压到毫秒级,但敏感场景(滑动、动画)里偶发的几毫秒卡顿可能就是 GC。看到卡顿先抓 GC 日志,别急着怀疑业务代码。
  • 手动触发 GC 通常没必要 :ArkTS 运行时的 GC 触发策略已经调得比较保守,手动 ArkTools.hintGC() 多数时候是干扰。只在明确知道一大堆对象刚变成垃圾、且接下来有性能敏感操作时才考虑手动触发。

总结一下下

  • 短生命周期对象尽量在函数内创建,函数返回后从 Root 不可达,下次 Young GC 就回收了。Young GC 频率高、开销小,这种对象几乎无感。
  • 长生命周期对象(缓存、单例)会被晋升到老年代,Old GC 频率低、开销大。老年代对象多不是问题,问题是老年代对象频繁增删会导致碎片和频繁 Old GC。
  • 大对象(几 MB 以上的 ArrayBuffer)会直接进 HugeObjectSpace,每个独占一个 Region。大对象频繁创建回收会让 HugeObjectSpace 的 Region 反复分配释放,关注这块的 GC 日志。
  • 别在 finalize 回调里做复杂逻辑。finalize 是 GC 回收对象时调用的,在里面分配新对象、持有引用都会干扰 GC,且执行时机不确定。
相关推荐
blxr_1 小时前
力扣hot100前k个高频元素https://leetcode.cn/problems/top-k-frequent-elements/
算法·leetcode·职场和发展
500841 小时前
React Native for OpenHarmony 实战:三方库 react-native-torch 的鸿蒙化适配指南
javascript·react native·react.js·harmonyos
动物园猫2 小时前
Compose Multiplatform 三方库 Coil(coil-core)的 OpenHarmony 鸿蒙化适配实战
华为·harmonyos
500843 小时前
React Native for OpenHarmony 实战:三方库 react-native-device-uptime 的鸿蒙化适配指南
javascript·分布式·react native·react.js·harmonyos
longlongzihan11 小时前
LeetCode 17电话号码的字母组合:回溯算法(DFS)详解
c++·算法·leetcode·深度优先
封印师请假去地球钓鱼11 小时前
边解边变的问题:从“决策依赖“一词出发
人工智能·算法
星辰徐哥12 小时前
鸿蒙平台 KDevelop 集成开发环境适配实战:基于 Electron 壳方案的跨平台多语言代码编辑器开发
electron·编辑器·harmonyos
熊猫钓鱼>_>12 小时前
开源鸿蒙平台 KMP 三方库 Essenty 适配全流程:从生命周期抽象到状态重建验证
华为·开源·harmonyos·鸿蒙·kmp·atomgit·essenty
今晚打老虎12 小时前
c++之提高A(前缀和)(第三课)
数据结构·c++·算法