PHP内存探秘:引用计数分离与写时复制如何工作
第一次在线上环境里看到PHP进程内存飙到 2GB 时我差点以为是内存泄漏。排查半天发现罪魁祸首不是泄漏而是我对于 PHP 的变量存储模型理解得太模糊——尤其是“引用计数分离”这个概念。当时满脑子只有“变量会在堆上分配吗”“refcount 是哪个类型都有吗”结果错把对象当数组用把字符串当标量用把“引用”和“传值”混成一锅粥。这篇文章不聊框架不聊业务就是扎进 PHP 底层把“只有复杂类型String、Array、Object才会在堆上分配实际数据并维护 refcount”这件事讲透同时用代码和调试工具把它从黑盒变成白盒。适合正在啃 PHP 内核源码、准备面试或者跟我一样被线上内存折磨过的人。1. 先搞清楚 PHP 变量到底是什么zval 与堆分配的分界线1.1 zval 是变量的“名片”不是变量本身很多教程会告诉你“PHP 变量就是一个 zval”这个说法半对半错。更准确地说在 Zend 引擎里变量名与 zval 之间有一层映射关系而 zval 本身只是变量的“名片”。在 PHP 7 之后的版本里zval 是一个 16 字节左右的结构体它里面有一个 union 类型的 value 字段typedef struct _zval_struct { zend_value value; union { uint32_t type_info; struct { zend_uchar type; zend_uchar type_flags; union { uint32_t extra; uint32_t reserved; } u; } v; } u1; union { uint32_t next; uint32_t cache_slot; uint32_t opline_num; uint32_t lineno; uint32_t num_args; uint32_t fe_pos; uint32_t fe_iter_idx; uint32_t access_flags; uint32_t property_guard; } u2; } zval;其中zend_value又是个联合体存放long、double、指针等。关键点就在这里整数、浮点数、布尔值、null 这些标量类型会直接把值内联在 zval 的 value 字段里而字符串、数组、对象、资源、引用这类复杂类型zval 里放的只是一个 8 字节的指针真正的数据体zend_string、zend_array、zend_object被分配在堆上同时维护一个 refcount 字段用来记录“这个堆上结构被多少个 zval 共享”。如果仍然觉得抽象可以这样类比标量变量就像写在名片上的几个数字名片递给谁都不需要额外的账本复杂变量则像名片背后写着“去第几号仓库取货”仓库里堆着真正的货物仓库管理员需要记录还有多少人拿着这张名片以免货物提前被清走。1.2 为什么只有复杂类型需要在堆上分配直觉上字符串不也是一个“值”吗为什么不直接塞进 zval 里原因有三点。第一长度动态可变。整数最长就那么长布尔值只有一个位字符串却可能从空字符串到几十兆的大文本。如果内联在 zval 里要么限制字符串长度上限要么把 zval 设计成可变长结构后者会让变量表、调用栈的空间布局完全失控。PHP 选择了固定大小 zval 堆上分配字符串的方式这样任何变量都只占固定字节数引擎遍历符号表时不需要处理“这个变量到底有多大”。第二数组大小和哈希表结构复杂。数组底层是zend_array即 HashTable包含 arData 指针、nTableSize、nNumUsed 等大量元数据加上每个桶里的 Bucket 又要存储 key、哈希值和 value。这种结构完全不可能内联只能通过指针引用。第三对象需要生命周期管理。对象实体可能有继承链、属性表、方法表甚至包含其他对象引用。哪怕只有一个对象变量也可能牵扯到一大片堆内存所以对象的 zval 里放的同样是“句柄/指针”。至于为什么大多数标量不需要 refcount就更直观标量值是内联的不存在“共享同一个底层数据”的问题。只有复杂类型因为 zval 只保存指针才会出现多个变量指向同一份堆数据的情况也因此才需要记录共享次数也就是 refcount。类型数据存储位置zval 里放什么是否维护 refcountint / float / bool / nullzval 内部 inline直接值否string堆上的 zend_string指向 zend_string 的指针是array堆上的 zend_array指向 zend_array 的指针是object堆上的 zend_object对象句柄/指针是resource堆上的资源结构指向资源结构的指针是持久化资源另算reference可以把任意类型套一层指向 zend_reference 的指针是提示很多人只记 “String, Array, Object”但 resource 也会在堆上分配并做引用计数只不过平时用得少。注意 PHP 内部对资源还有持久资源与非持久资源的区分refcount 的管理方式并不完全一样。1.3 一个关键误区字符串不是“小东西”它也是堆上的复杂类型我见过不少同学写 PHP 时想当然地认为“字符串不就是一个字符数组嘛跟 C 语言的 char 数组一样是值类型”。这个认知在 PHP 里是错的。当执行$s hello;时引擎会创建一个zend_string它并不藏在局部变量的栈里而是 malloc 到堆上。zend_string结构如下struct _zend_string { zend_refcounted_h gc; zend_ulong h; size_t len; char val[1]; };其中gc是引用计数头h是字符串哈希缓存避免重复计算len是长度val是柔性数组紧跟结构体之后存放字符串内容和结尾的\0。正因为字符串是堆分配 引用计数你才能写这样的代码而几乎不产生内存复制$big str_repeat(x, 100 * 1024 * 1024); $first $big; $second $big;此时三个变量其实共享同一个zend_stringrefcount 变成 3而内存只涨了 100MB 左右而不是 300MB。这在 C/C 里如果不主动封装早就复制三份了。字符串还有一个特殊机制叫 interned string。像name、php这类编译期就可以确定的字符串会被放入只读数据区refcount 固定为 1永远不会被释放。这也是为什么你调试时总觉得字符串的 refcount 数据不合常理——它可能根本没走普通的堆内存管理。2. refcount 到底怎么工作从共享到分离的完整过程2.1 refcount 的含义与底层结构前面提到的zend_refcounted_h是复杂类型的共同头部typedef struct _zend_refcounted_h { uint32_t refcount; uint32_t type_info; } zend_refcounted_h;refcount记录“有多少个 zval 引用着这个结构体”。type_info则记录了类型种类以及 GC 需要的标记位像是IS_TYPE_REFCOUNTED、IS_TYPE_COLLECTABLE等。IS_TYPE_REFCOUNTED这个标志很重要它是 zval 在决定写时复制策略时首先要检查的位。现在回到一个常见场景$a [1, 2, 3]; $b $a;在这两行执行完后$a和$b两个 zval 的 value 指针指向同一个zend_array这个数组头部的 refcount 从 1 变成 2。这里的“共享”是引擎自动完成的目的是不复制数组。那如果这时候修改$b呢比如$b[] 4;你会希望$a保持原样。PHP 也确实是这么做的原因是引擎在写入时检测到数组的 refcount 1于是先把数组复制一份再在副本上做修改。这就是把“共享”变成“独有”的过程专业说法是 Copy-on-Write也叫“写时复制”。但我个人更愿意把它理解成“写时分离”——把原本共享的数据结构通过减少原 refcount 与新建独立结构的方式拆成两个互不干扰的容器。这里有句总结可以记住引用计数分离的本质就是在需要写操作时把 refcount 1 的共享结构拆成单个独立结构并同步调整各 zval 指向。2.2 写时复制让共享不产生副作用说千遍不如跑一遍。我们用内存占用和 a 变量来观察这个过程$base memory_get_usage(); $a str_repeat(x, 10 * 1024 * 1024); // 分配 10MB 字符串 echo memory_get_usage() - $base, PHP_EOL; // 约 10MB 以上 $b $a; // 共享同一个 zend_string echo memory_get_usage() - $base, PHP_EOL; // 基本不增加 $b . y; // 触发写时复制内部进行字符串复制 echo memory_get_usage() - $base, PHP_EOL; // 又增加约 10MB输出结果会清楚地展示$b $a并没有复制底层数据真正的复制发生在拼接时。类似地对于数组$b $a后修改任意一个变量都会把数组在第一个写操作点复制出去原数组 refcount 减一新数组 refcount 保持为 1。写时复制对用户代码是透明的但它能大幅减少内存占用和赋值耗时。你可以理解为只要没人动手改大家共用一份数据谁改谁掏钱复制。2.3 引用计数分离什么时候会分裂出一个独立副本严格来说Zend 引擎的很多内部函数名字里直接有_copy、_sep比如zend_array_dup、zend_string_copy。而“引用计数分离”这个说法通常指代引擎判断 refcount 1 后将共享结构拆分为独立副本的那一步。触发分离的分水岭是“写”操作。以下情况都会导致分离修改数组元素$arr[k] v给数组追加元素$arr[] v修改字符串内容$str . x通过引用参数修改函数内的值且外部也有其他变量共享着这个结构unset 一个变量使得其他变量的引用关系发生变化看一段代码$a [project php]; $b $a; // 注意这里是引用赋值而不是普通赋值 $c $a; // 普通赋值 unset($b);其中$b $a会把$a包装成zend_reference结构$a和$b同时指向这个 referencerefcount 为 2。$c $a会把 reference 内部的 value 再复制给$c的 zval此时底层数组 refcount 会重新计算。unset($b)又会减少引用层的 refcount当 refcount 归零时zend_reference 被释放如果内部 value 仍有其他变量引用则 value 的 refcount 相应调整。用这个例子你可以很清楚地感知到“引用计数分离”并不是一个单一动作而是引擎在变量生命周期里不断做出的“剪线”动作。它保证所有普通赋值都共享所有引用赋值都有独立的引用计数层所有写操作都让被修改者获得独立副本。3. 逐个拆解三大复杂类型的堆分配与引用计数细节3.1 字符串别把字符串当成“简单值”字符串的底层是zend_string它自带引用计数已经被我讲过。这里再补充几个实操中容易踩的细节。字符串搞“写时复制”后很多面试题会问为什么$str1 $str2后修改$str2不影响$str1。答案是引擎在拼接时构建新 zend_string 并执行分离老的字符串 refcount 减一新字符串 refcount 等于 1。还有一个经常被忽略的点字符串哈希缓存。zend_string里的h字段用来缓存这个字符串的哈希值方便它作为数组 key 时快速查找。所以当你在循环里反复用同一个字符串当数组下标时第二次开始就不需要重新计算哈希这也是 PHP 数组性能的关键。从这个角度看字符串的堆分配并不完全是“浪费”它其实换来了很多内部优化。动态拼接$str . $suffix时引擎不一定立刻复制整个字符串。如果$str的 refcount 为 1并且底层zend_string的容量足够大cap 大于当前 len 拼接长度它会在原内存上扩容只有当容量不够或 refcount 1 时才重新分配。所以大量字符串拼接并不总是 O(n^2)引擎内部已经做过不少优化。注意不要为了省内存把所有字符串变量都用引用赋值。引用一旦建立后续每次写入都会直接修改共享实体很难再通过写时复制隔离变量反而会让逻辑变得难以追踪。3.2 数组引用计数其实藏在 HashTable 里数组在 PHP 7 之后是真正的zend_array而不是 PHP 5 时代的zval*数组。结构如下typedef struct _zend_array { zend_refcounted_h gc; union { struct { ZEND_ENDIAN_LOHI_4( uint32_t flags, uint32_t nTableMask, uint32_t nTableSize, uint32_t nNumUsed) } v; uint32_t flags; } u; uint32_t nTableMask; Bucket *arData; uint32_t nNumUsed; uint32_t nNumOfElements; uint32_t nTableSize; HashTable *pDestructor; } zend_array;数组 zval 里存的就是指向zend_array的指针。refcount在数组头部因此“多个变量共享一个数组”跟“多个变量共享一个字符串”本质是一样的。但数组内部还有 Bucket每个 Bucket 是typedef struct _Bucket { zval val; zend_ulong h; zend_string *key; } Bucket;Bucket 里的 val 又可以是任意复杂类型因此一个数组共享时的写时复制会递归地进行判断。假设数组里有子数组当你只修改外层数组的一个元素时引擎会先复制整个外层zend_array然后把所有内层 val 的 refcount 都增加一次因为新的外层数组也指向相同的子结构。如果继续修改内层子数组才会进一步地分离并复制那个子数组。这种“延迟复制”叫递归写时复制会让内存开销尽可能延后。实操中经常遇到的情况是函数参数传递function add_element(array $arr) { $arr[] new; } $arr [a, b, c]; add_element($arr); var_dump($arr); // 还是 [a, b, c]因为传入函数时是“传值”语义外层数组 refcount 从 1 变成 2函数内部追加元素时触发分离$arr在函数作用域内复制出新数组追加发生在副本上。函数返回后原数组 refcount 归位外部不受影响。这正是引用计数隔离变量语义的体现。3.3 对象句柄与对象实体分离式设计对象和数组、字符串有个重大区别数组、字符串在普通赋值后你修改副本不影响原值但对象普通赋值后修改任何一个变量都会影响另一个。class User { public $name php; } $u1 new User(); $u2 $u1; $u2-name go; echo $u1-name; // go原因就在于对象的 zval 保存的其实只是一个句柄底层zend_object实体被多个 zval 共享。如果字符串/数组类比的是“整箱货物”对象类比的是“同一个仓库的钥匙”钥匙可以复制一百份仓库只有一个。对象是否维护 refcount答案是维护。zend_object结构里也带zend_refcounted_h头部但它和字符串、数组的引用计数层级不同。对象实体会被一个对象存储层统一管理并且 PHP 7 之后对象直接嵌入zend_object_handle可以快速定位。正因为有 refcount当最后一个指向对象的变量 unset 时对象析构、释放。对象如果想要独立副本必须显式clone$u3 clone $u1; $u3-name rust; echo $u1-name; // 仍然是 goclone会复制对象实体让两个变量各自持有独立的对象。默认的 clone 是浅复制对象的属性如果是对象或者数组仍然可能共享需要手动在__clone魔法方法里做深复制。做业务开发时若需要“快照”再考虑 clone普通赋值不会替你复制对象。4. 实操调试把 refcount 从黑盒变成白盒4.1 debug_zval_dump 的诡异行为与正确姿势PHP 自带的debug_zval_dump是个非常容易误导人的函数。很多人用它看 refcount结果看到一个莫名其妙的“refcount2”以为变量被复制了。其实这是因为函数参数按值传递调用那一刻会创建一个临时的 zval使 refcount 暂时多一。建议这样观察$a hello; debug_zval_dump($a);按引用传递就能看到这个字符串真实的 refcount。如果是数组也可以$arr [1, 2, 3]; $ref $arr; debug_zval_dump($arr);打印里会显示类似array(3) refcount(2){ ... }注意直接手动构造引用会让 refcount 增加所以看到 refcount2 并不代表底层的zend_array实际被两个变量共享还可能是由于 reference 层导致的显示结果。如果安装了 Xdebug用xdebug_debug_zval()会更直观它也会展示 refcount 字段。提示如果你只想判断两个变量是否共享底层数据看debug_zval_dump还是会绕。最稳妥的办法还是打开底层日志或在关键点输出memory_get_usage()观察内存增量趋势。4.2 用脚本模拟引用计数分离的完整过程我们写一个小脚本把“共享、分离、回收”完整走一遍function status($step) { printf([%s] memory: %d KB\n, $step, memory_get_usage() / 1024); } $base memory_get_usage(); $a str_repeat(a, 1024 * 1024 * 5); status(create a); $b $a; status(set b a (sharing)); $c $a; status(set c a (reference)); $b . x; status(modify b (copy-on-write)); unset($c); status(unset c);观察内存变化你会发现set b a阶段内存几乎零增长set c a阶段内存也几乎零增长真正的内存增长点在modify b。这说明字符串直到被修改时才完成了分离。unset $c后如果$a和$b各自持有独立字符串内存不会释放只有引用计数归零的字符串才会被回收。对于数组可以加入递归场景$arr [inner [a 1, b 2], count 100]; $copy $arr; $copy[inner][a] 999;$copy[inner][a] 999这一步会先分离外层数组再分离内层数组。外层复制时内层数组的 refcount 增加一次修改内层元素时内层数组因为 refcount 1 又会再复制一次。这是典型的“双层写时复制”。4.3 从源码层面看引用计数增减流程很多工程师一听到源码就紧张但只需要看几个宏就能抓住重点。在Zend/zend_gc.h里有#define GC_ADDREF(p) (GC_REFCOUNT(p)) #define GC_DELREF(p) (--GC_REFCOUNT(p))GC_ADDREF用于共享比如赋值给新变量、函数入参时 zval 数量增加GC_DELREF用于释放引用比如 unset、作用域结束。当GC_DELREF执行后 refcount 降到 0就会走释放函数把字符串、数组、对象所占用的内存归还给系统或内存池。引擎在修改变量时有没有写时复制看字符串的实现套路当修改一个字符串时如果 refcount 1就调用类似zend_string_sep的函数先新建字符串并复制内容再减少原字符串 refcount。做这种判断的核心代码极其频繁所以这是整个 PHP 运行时的热点路径也因此引擎对字符串、数组的头部字段做了大量缓存友好设计例如type_info里有多余的位来标记“是否可回收”“是否正在 GC 中”。透过源码能理解一个道理PHP 的内存模型不是“每个变量都持有值”而是“每个变量都持有指针指针指向一个带引用计数的堆对象”。想清楚这一点再看php.ini里的memory_limit、gc配置就顺理成章了。5. 常见坑位与排查实录引用计数不是玄学5.1 循环引用refcount 永远不会归零最经典的坑就是数组里引用自身$a []; $a[self] $a; unset($a);执行完unset($a)之后数组 refcount 并不会归零因为数组内部还存储着一个指向自己的引用。外部变量虽然消失内部引用仍让容器存活这块内存就泄漏了。字符串、对象也会有类似问题对象互相引用就是最常见的泄漏源。普通引用计数没有机制能检测这类环所以 PHP 引入了 GC 根缓冲机制。包含可回收结构的 zval 在销毁时如果 refcount 减到 1 而不是 0可能被认为是垃圾节点会被放入根缓冲。当缓冲区满了GC 会尝试模拟删除找出真正引用计数归零的节点并回收。手动清理循环引用可以用gc_collect_cycles();但不要在日常业务代码里频繁调用开销不小。最好的方案是避免循环引用实在无法避免时利用周期性的gc_collect_cycles或让 PHP-FPM 定期重启来兜底。5.2 “修改函数里的数组”为什么不生效这个问题我在文章前面已经提过这里再补充一个对比。默认情况下函数参数是传值但传值并不代表复制底层数据结构只是 refcount1。函数内修改才会触发分离。如果想在函数里修改外层变量有两种方案声明参数为引用function f(array $arr)返回修改后的数组并重新赋值$arr f($arr);前者省内存但改变函数签名后者更干净但会多一次引用计数操作。对于大数组建议直接用引用参数但要控制副作用做好命名约定比如函数名用append_item这类动词暗示引用修改。闭包也同理$count 0; $closure function() use ($count) { $count; }; $closure();如果use ($count)传值闭包内外变量的 refcount 都会增加但闭包内修改的是副本第二次调用闭包时$count已经不再变这是很多人写闭包计数器踩过的坑。5.3 调试时字符串 refcount2 的困惑很多人会有这种经历$s php; debug_zval_dump($s);打印出的结果是string(3) php refcount(2)接下来就开始怀疑人生不是只有一个变量吗为什么 refcount 是 2原因有两个一是debug_zval_dump本身作为函数的参数传递会让 refcount 短暂 1二是php可能是 interned string字符串存活在只读内存里引擎对它的引用统计并不代表可释放的堆内存。所以看到 refcount2 不用慌它不代表“泄漏”。在排查内存问题时更有效的做法是关注大对象的引用情况而不是盯着单字母字符串。找出那些超过 1MB 的字符串、数组检查是不是被缓存容器例如画布、缓存池无意中长期持有。5.4 性能调优经验什么时候该用引用什么时候该用值引入引用计数和写时复制后有些同学开始走极端把所有数组传参都改成引用把所有数组赋值都用引用以为这样“省内存”。引用不是万能药它也会带来副作用引用变量一旦创建就建立一个共享实体之后任何一方修改都会直接影响另一方。而值传递结合写时复制反而能实现“未修改前共享、修改后隔离”的最优平衡。我的实际建议是大数组、大字符串要传递给函数且不需要外部修改原始值时值传递即可写时复制会帮你省内存。大数组需要频繁在函数内修改且希望改动回写到外部容器用引用参数省去复制与返回。对象类型的传递只需要考虑引用传递因为对象本来就是句柄共享不必加来追求“省内存”。循环中拼接超大字符串别用$str $str . $piece改用数组收集再 join字符串的引用计数本身不会替你解决复杂度问题只是降低常量层面的开销。优化之前先用memory_get_usage和gc_status做基线避免凭感觉动代码。调优最忌讳的是在不清楚 refcount 的情况下乱加冒号比如所有函数参数都顺手加了个最后排查引用副作用时后悔莫及。6. 写在最后回头看我前面提到的那次 2GB 内存事故根因其实是一段日志模块代码每次接收一条日志内部都会对一条很长的 json 字符串做json_encode、json_decode、字符串拼接和数组合并每一步都在复制。我没有看代码只看内存监控自然只能抓到“内存变大”这个表象。后来我用memory_get_usage分段打点再用debug_zval_dump($var)观察关键大变量的 refcount才发现整个流程里同一份日志内容被反复复制了十多次。改成一个临时数组累积片段、最后一次性implode内存直接掉了 70%。如果你也想彻底吃透这块我的建议是先不要啃一堆源码而是在本地写几个小脚本用debug_zval_dump配合memory_get_usage观察“赋值、修改、unset、传参”这几个节点前后 refcount 和内存的变化。等你真正理解了只有复杂类型才在堆上分配数据并维护 refcount再回头看Zend/zend_gc.h里的GC_ADDREF和GC_DELREF你会发现整个 PHP 内存模型豁然开朗。排内存问题最怕的是玄学但只要把引用计数分离的过程想明白大部分内存困惑都能变成确定性的算术题。

相关新闻

swiftui-expert-skill - list-patterns

swiftui-expert-skill - list-patterns

SwiftUI List 模式参考 目录 ForEach 身份与稳定性枚举序列带自定义样式的 List带下拉刷新的 List使用 ContentUnavailableView 的空状态(iOS 17)自定义 List 背景Table汇总清单 ForEach 身份与稳定性 始终为 ForEach 提供稳定的身份。 对于动态内容…

2026/10/11 5:32:46 阅读更多 →
V模型与敏捷开发在汽车控制器测试中的融合实践:从需求到HIL的全链路自动化

V模型与敏捷开发在汽车控制器测试中的融合实践:从需求到HIL的全链路自动化

做汽车控制器开发这几年,V模型和敏捷开发就像两个互相看着不顺眼的老同事,一个坚持流程严谨、一个喊快速交付,吵到最后基本都会在测试环节爆发。我做了快十年的控制器项目,从BMS到域控制器,慢慢摸清了一个门道&#xf…

2026/10/11 5:32:46 阅读更多 →
np.where在电磁近场测量数据处理中的实战应用

np.where在电磁近场测量数据处理中的实战应用

做电磁近场测量,最烦的不是架探头、对位、设步进,而是扫完一圈之后,面对那一大坨幅相矩阵。数据里经常藏着各种不老实的东西:线缆一抖,某个点幅度飙到 99.9;放大器瞬间饱和,相位从 179 跳到 -17…

2026/10/12 6:52:18 阅读更多 →

最新新闻

代码评审不是走过场:一套可落地的团队协作与提效实践

代码评审不是走过场:一套可落地的团队协作与提效实践

我见过太多团队把代码评审(Code Review)做成了“打卡”。每天PR列表都有人回复,但回复内容永远是“LGTM”“1”,或者更礼貌一点的“Looks good”。代码照样上线,Bug照样出,线上事故该复盘还是复盘。问题出在…

2026/10/12 8:02:39 阅读更多 →
开放代码评审:构建可追溯、可验证、可复用的工程信任链

开放代码评审:构建可追溯、可验证、可复用的工程信任链

1. 项目概述:这不是代码检查,而是一场公开的协作式技术对话“open-code-review”这个词组乍看像一个工具名,但其实它代表的是一种正在快速演化的工程实践范式——把原本封闭在团队内部、甚至仅限于少数核心成员参与的代码评审过程&#xff0c…

2026/10/12 8:02:39 阅读更多 →
开源代码审查自动化工作流 open-code-review 实战指南

开源代码审查自动化工作流 open-code-review 实战指南

我做代码审查做了将近十年,从最早在命令行里逐行看 diff,到后来在网页上点来点去评论,再到折腾各种自动化检查工具,原以为已经把这摊事摸得差不多了。直到年初在某个午夜上线前,因为一行漏掉的空指针判断导致回滚&…

2026/10/12 8:02:39 阅读更多 →
传统工艺美学AI参数化:从LoRA训练到批量风格生成实战

传统工艺美学AI参数化:从LoRA训练到批量风格生成实战

我去年把一半业余时间砸在一个叫 artcraft 的长期项目上,说直白一点:它不是又一款「输入提示词、等模型出图」的模板式工具,而是一条把传统手工美学解构成可控参数的风格生产线。每年都有朋友拿着Midjourney或SD生成的漂亮图片问我"这种…

2026/10/12 8:02:39 阅读更多 →
Android天气小程序开发:从Retrofit网络请求到数据展示全流程

Android天气小程序开发:从Retrofit网络请求到数据展示全流程

简介:一款使用安卓官方集成开发环境(Android Studio)编写的天气预报小程序完整源码,面向初学安卓开发的读者,以及需要快速搭建天气类功能演示的开发者,能够帮助理解从创建项目、配置网络依赖、发送接口请求…

2026/10/12 8:02:39 阅读更多 →
从电子管到固态:四大厂商中短波发射机技术路线对比

从电子管到固态:四大厂商中短波发射机技术路线对比

这几年在中短波广播发射机这个圈子里,变化比过去二十年都大。2021 到 2026,正好赶上一批老式高末级电子管发射机批量到龄退运、DRM 数字广播从"可选项"变成"默认考虑项"、以及固态功放器件成本持续下探的交叉期。我的日常工作中&…

2026/10/12 8:01:38 阅读更多 →

日新闻

复古胶片颗粒感噪点合成器:Canvas ImageData 像素高斯杂色注入算法

复古胶片颗粒感噪点合成器:Canvas ImageData 像素高斯杂色注入算法

在数码相机、高清显示屏与现代矢量图形技术高度发达的今天,画面可以做到绝对的锐利、平滑与无瑕。然而,当一张秋日手账插画或拍立得照片过于“平整无瑕”时,往往会散发出一种冰冷生硬的“数码塑料感(Digital Plasticity&#xff0…

2026/10/12 0:00:59 阅读更多 →
活字印刷古籍线装排版:Canvas 竖排文字与栏线自适应算法

活字印刷古籍线装排版:Canvas 竖排文字与栏线自适应算法

在现代网页与移动端设计中,横排(Horizontal Layout)早已经成为了绝对的主流。然而,当我们翻开泛黄的线装古籍、宋版木刻诗集,或是欣赏一张茶道雅集的手写便签时,那种**自上而下纵向书写、自右向左逐列铺展&…

2026/10/12 0:00:59 阅读更多 →
周日晚间的“精神松绑减震器”:无压力情绪倾倒箱与温和轻声陪伴

周日晚间的“精神松绑减震器”:无压力情绪倾倒箱与温和轻声陪伴

每到周日的晚上八点到十点,很多人心里都会悄悄亮起一盏警示灯。 在心理学上,这种现象有一个专门的称谓——“周日夜晚焦虑症(Sunday Scaries)”。明天又是周一,闹钟又要重新在七点响彻卧房;脑海里仿佛有一个…

2026/10/12 0:00:59 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/12 0:16:30 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/12 0:16:38 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/12 0:16:43 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/11 10:45:37 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/11 14:36:53 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/11 14:36:54 阅读更多 →