手写带头结点的单链表:C语言核心实现与指针细节
[这个位置是博文正文]1. 到底为什么要自己手写单链表很多时候我把这个问题抛给刚入门的朋友对方第一反应是“这不是造轮子吗”。其实不完全是。线性表是数据结构里最基础的一类存储结构而单链表作为链式结构里最简单的一种形态它承载的核心不是“写一个能用的链表”而是让你搞懂“内存里的数据到底是怎么被组织起来的”。顺序表依赖连续内存逻辑相邻等于物理相邻而单链表靠指针把不连续的内存串起来逻辑相邻但物理随意这两者在底层思路上的差别是一切后续数据结构学习的起点。从就业角度说只要是正规的笔试面试手写单链表几乎是保留项目。反转链表、合并有序链表、找中间结点、判断环这些题目背后的基本功就是你对指针和结点生命周期的掌控力。所以别再觉得这是大学实验课的应付作业把它当成一次对内存管理的深度体检收益远比你想象的大。1.1 带头结点和不带头结点的区别“带头结点”这四个字是理解这个实验的关键分水岭。头结点Head Node是附加在第一个元素结点之前的一个额外结点它的data域通常不存有效数据或者只用来存链表长度等附加信息真正的作用是让“空表”和“非空表”的处理逻辑统一。不带头结点的单链表空表时头指针直接为NULL插入和删除第一个元素结点时要单独更新头指针逻辑分支多容易出错。带头结点后头指针始终指向头结点不管链表是不是空插入和删除第一个元素结点的操作和其他位置一样不需要特殊处理。这个差别在做实验时可能觉得“只是少写一个if”但等你写复杂算法、做递归、做多线程共享链表时就会明白统一的边界条件能省下多少心智负担。我自己在带新人时总说一句话不带头结点是锻炼你考虑边界的能力带头结点是让你把精力聚焦在核心逻辑上。既然实验题目明确要求带头结点那就按这个思路来。1.2 结点结构要怎么定义结点的本质是一个结构体里面有两个东西数据域和指针域。数据域存什么完全看需求最简单的场景存int复杂场景可以存结构体甚至泛型用void*。指针域就是指向下一个结点的指针。typedef struct Node { int data; // 数据域 struct Node *next; // 指针域 } Node;注意这里用的是struct Node *next不是Node *next。原因很简单在typedef还没有生效的时候类型名Node还不存在只能用结构体本身的标签struct Node来声明指针。这个细节看起来不起眼但你如果顺序写反了编译器会直接报“未知类型名”的错误。我在实际教学里见过太多次新手在这里卡住。如果想把头和结点区分得更语义化还可以拆开定义typedef struct Node { int data; struct Node *next; } Node, *LinkList;这里LinkList就是Node *的别名用来声明头指针时会让你读代码时更清楚“这是链表整体”还是“这是单个结点”。2. 核心操作的整套实现思路我们需要完整实现一个带头结点的单链表包含初始化、判空、遍历、按位查找、按值查找、插入、删除、销毁等操作。听起来很多但拆开看它们的核心就是两个动作改指针管内存。链式结构的所有操作都绕不开“找前驱”这三个字。要删除第i个结点你得找到第i-1个结点要在第i个位置插入你也得找到第i-1个结点。所以很多操作的代码骨架是高度相似的学会一个其他的都是变形。2.1 初始化和判空别小看这两个“基础操作”初始化带头结点的单链表时要申请一个头结点让头指针指向它同时把头结点的next置为NULL。int InitList(LinkList *L) { *L (LinkList)malloc(sizeof(Node)); if (*L NULL) { return 0; // 内存申请失败 } (*L)-next NULL; return 1; }为什么这里传入的是LinkList *L而不是LinkList L因为我们要修改头指针本身的值让它指向新申请的头结点。C语言参数传递是值传递直接传LinkList L的话在函数内部修改L并不会影响外部的头指针。这个“二级指针问题”是单链表实现里最经典的坑之一后面我会专门展开讲。判空操作就非常简单了如果头结点的next是NULL说明没有元素结点链表为空。int IsEmpty(LinkList L) { return L-next NULL; }这里虽然返回1代表空但有经验的程序员会更倾向于写成return L-next NULL;而不是if (...) return 1; else return 0;。因为C语言里比较表达式的结果本身就是0或1多包一层if反而是冗余。2.2 前插法和尾插法两种构建方式各有各的场前插法头插法的核心逻辑是“新结点插到头结点之后成为第一个元素结点”。新结点的next指向原来第一个元素结点然后头结点的next指向新结点。int InsertAtHead(LinkList L, int val) { Node *newNode (Node*)malloc(sizeof(Node)); if (newNode NULL) { return 0; } newNode-data val; newNode-next L-next; L-next newNode; return 1; }头插法构建链表时输入顺序和链表实际顺序相反。如果你依次输入1、2、3得到的链表是3、2、1。这个特性在有些场景里是缺点但在某些算法里反而是优点比如用头插法实现单链表的逆序不需要额外申请空间。尾插法需要一个尾指针记录链表的最后一个结点每次新结点直接接在尾指针后面。int InsertAtTail(LinkList L, int val) { Node *tail L; while (tail-next ! NULL) { tail tail-next; } Node *newNode (Node*)malloc(sizeof(Node)); if (newNode NULL) { return 0; } newNode-data val; newNode-next NULL; tail-next newNode; return 1; }如果频繁尾插每次都从头部遍历到尾部的效率是O(n)。如果在一个循环里反复尾插n次总复杂度就到了O(n²)。工程上通常用一个额外的尾指针来维护每次插入后更新尾指针让复杂度降回O(1)。我建议实验里一开始就养成维护尾指针的习惯后续写队列、写循环链表时会非常受益。2.3 按位查找和按值查找遍历逻辑的两个变体按位查找的目标是找到第pos个元素结点从1开始计数需要一个计数器从第一个元素结点开始移动指针每移动一次计数器加1。如果移动到链表末尾还没到pos说明pos不合法。Node* GetElemByIndex(LinkList L, int pos) { if (pos 1) { return NULL; } Node *cur L-next; int count 1; while (cur ! NULL count pos) { cur cur-next; count; } return cur; // 可能为NULL表示pos超过链表长度 }按值查找更简单从头到尾遍历比对data值找到返回结点指针找不到返回NULL。逻辑上和按位查找几乎一样就是把“移动count次”变成了“比对data是否相等”。这两个操作的边界条件非常统一空链表时cur一开始就是NULL循环不执行直接返回NULL不会出问题。这也是带头结点带来的好处——空链表有一个实体头结点遍历代码不需要额外判断。2.4 删除操作最容易写错指针的地方删除第pos个结点先找到第pos-1个结点前驱让前驱的next跨过被删结点指向被删结点的下一个结点然后释放被删结点的内存。int DeleteByIndex(LinkList L, int pos, int *e) { if (pos 1) { return 0; } Node *prev L; int count 0; while (prev-next ! NULL count pos - 1) { prev prev-next; count; } if (prev-next NULL) { return 0; // 第pos个结点不存在 } Node *del prev-next; *e del-data; prev-next del-next; free(del); return 1; }这段代码的原理从头结点开始找prev指向目标位置的直接前驱。循环条件prev-next ! NULL保证了prev不会越界而count pos - 1控制移动次数。比如删除第3个结点prev从头结点出发移动2次后指向第2个结点此时prev-next就是第3个结点合法。这里最关键的一行是prev-next del-next。它把前驱的next直接指向被删结点的后继相当于在链表中把被删结点“跳过去”。很多人第一次写的时候容易写成prev-next prev-next-next虽然效果一样但逻辑上不如先用del指针把结点捉住再来再断开。说得直白点先抓住要被释放的结点再改指针最后free这个顺序不能乱。如果先free了del再去改指针那就是典型的“悬垂指针”访问。3. 完整实操从一个可编译运行的链表开始理论说再多都不如把代码写出来跑一遍。这一节我直接给你一个完整的、可在本地编译运行的实现方案。3.1 完整代码主干从结构体到所有基础操作#include stdio.h #include stdlib.h typedef struct Node { int data; struct Node *next; } Node, *LinkList; // 初始化带头结点的单链表 int InitList(LinkList *L) { *L (LinkList)malloc(sizeof(Node)); if (*L NULL) return 0; (*L)-next NULL; return 1; } // 头插法 int InsertAtHead(LinkList L, int val) { Node *newNode (Node*)malloc(sizeof(Node)); if (newNode NULL) return 0; newNode-data val; newNode-next L-next; L-next newNode; return 1; } // 尾插法 int InsertAtTail(LinkList L, int val) { Node *cur L; while (cur-next ! NULL) { cur cur-next; } Node *newNode (Node*)malloc(sizeof(Node)); if (newNode NULL) return 0; newNode-data val; newNode-next NULL; cur-next newNode; return 1; } // 遍历打印 void PrintList(LinkList L) { Node *cur L-next; while (cur ! NULL) { printf(%d , cur-data); cur cur-next; } printf(\n); } // 按位查找 Node* GetElemByIndex(LinkList L, int pos) { if (pos 1) return NULL; Node *cur L-next; int count 1; while (cur ! NULL count pos) { cur cur-next; count; } return cur; } // 按值查找查找第一个匹配的结点的位置从1开始 int GetIndexByValue(LinkList L, int val) { Node *cur L-next; int idx 1; while (cur ! NULL) { if (cur-data val) { return idx; } cur cur-next; idx; } return -1; // 未找到 } // 删除第pos个结点被删值通过e返回 int DeleteByIndex(LinkList L, int pos, int *e) { if (pos 1) return 0; Node *prev L; int count 0; while (prev-next ! NULL count pos - 1) { prev prev-next; count; } if (prev-next NULL) return 0; Node *del prev-next; *e del-data; prev-next del-next; free(del); return 1; } // 获取链表长度 int ListLength(LinkList L) { Node *cur L-next; int len 0; while (cur ! NULL) { len; cur cur-next; } return len; } // 销毁链表 void DestroyList(LinkList L) { Node *cur L; while (cur ! NULL) { Node *next cur-next; free(cur); cur next; } } int main() { LinkList L NULL; InitList(L); InsertAtTail(L, 1); InsertAtTail(L, 2); InsertAtTail(L, 3); InsertAtHead(L, 0); printf(当前链表: ); PrintList(L); printf(链表长度: %d\n, ListLength(L)); Node *p GetElemByIndex(L, 2); if (p ! NULL) { printf(第2个元素是: %d\n, p-data); } int idx GetIndexByValue(L, 3); printf(值为3的元素在第 %d 个位置\n, idx); int delVal; if (DeleteByIndex(L, 2, delVal)) { printf(删除了元素: %d\n, delVal); } printf(删除后链表: ); PrintList(L); DestroyList(L); return 0; }把这段代码保存成linked_list.c在终端里执行gcc linked_list.c -o linked_list编译然后运行./linked_list你能看到如下输出当前链表: 0 1 2 3 链表长度: 4 第2个元素是: 1 值为3的元素在第 4 个位置 删除了元素: 1 删除后链表: 0 2 3我建议你在自己敲代码的时候故意把DeleteByIndex里的prev-next del-next改成prev-next prev-next然后编译运行试试。链表没有断但被删结点的内存也没释放再去free就会变成双重释放调试器会直接报警告。这种“故意踩坑”的实验方法比反复背代码有用得多。3.2 二级指针到底什么时候必须用很多新手在看到InitList(L)时会有困惑为什么初始化时要传地址而插入、删除、遍历时只需要传L核心准则如果你要在函数里修改头指针本身的值就必须传头指针的地址即二级指针。如果只修改头指针指向的结点的内容或者修改某个结点的next字段传一级指针就够了。用一个生活化的类比来解释你得把门牌号的地址告诉快递员快递员才能找到你家、改你家门口的东西。如果你只把“你家的名字”告诉他他找错房子就麻烦了。具体到代码InitList里*L malloc(...)这行代码就是在修改头指针保存的地址从NULL变成一个合法的堆地址所以不传二级指针就改不出去。插入操作里newNode-next L-next; L-next newNode;修改的是头结点里面的next字段这个字段是在一个已经存在的结构体内部通过一级指针就能访问到所以不需要二级指针。对实验报告来说能回答清楚“为什么InitList要用二级指针”基本就算真正理解了指针传递的本质。面试时这也是一个很高频的追问点。3.3 Python单链表两种语言对比着学效率更高如果你平时更熟Python想对比理解可以用Python写一个带头结点的单链表。Python里的“引用”天然就是指针的行为所以代码长这样class Node: def __init__(self, data): self.data data self.next None class LinkedList: def __init__(self): self.head Node(None) # 头结点 def insert_at_tail(self, val): cur self.head while cur.next is not None: cur cur.next cur.next Node(val) def delete_by_index(self, pos): if pos 1: return False prev self.head for _ in range(pos - 1): if prev.next is None: return False prev prev.next if prev.next is None: return False prev.next prev.next.next return TruePython里不需要手动malloc和free垃圾回收帮我们处理内存但代价是你更容易忽略“结点到底被谁引用着”这个问题。C语言里漏掉一个free跑久了内存涨上去你立刻能感知到Python里写错指针可能只是某个结点悄悄丢了引用链逻辑诡异但你很难定位。所以我的建议是想真正理解链表先用C再用Python想快速写算法题应付笔试先用Python打底再用C巩固一遍。4. 常见问题与排查技巧实录这些坑我基本都踩过也帮很多人定位过。整理成速查表你可以直接对号入座。问题现象根本原因排查/解决思路程序启动后直接段错误头指针为NULL就调用L-next初始化时没分配头结点或InitList没执行成功打印链表时末尾多出随机值尾结点next没置为NULL新申请的结点next必须初始化malloc出来的内存是脏的删除结点后程序崩溃先free了结点再改指针按“先捉结点、改链、再free”的顺序执行插入后链表只是丢失部分数据头插法顺序与输入顺序相反检查用头插还是尾插确认两个方向各自的结果是否符合预期退出程序后内存泄漏没有遍历释放每个结点调用DestroyList释放时先存next再free当前结点pos传0或负数时行为诡异没做参数合法性校验所有查插入删除的pos参数第一步就检查pos 14.1 野指针和孤儿结点的区别这两个概念新手容易混。野指针是指针变量保存的地址对应的内存已经被释放或者根本没有被分配你通过它去访问内存就是未定义行为。孤儿结点是指某个结点在逻辑上已经不在链表里了但它的内存还没被释放形成一个既不属于链表也没被free的“游离个体”。我举个例子你写删除时如果只用prev-next prev-next-next被跳过去的那个结点就从链表逻辑上消失了但它的内存没人释放这就是孤儿结点。内存泄漏就是大量孤儿结点积累的结果。而野指针更危险它可能让你读到垃圾数据也可能直接导致段错误。调试野指针最直接的办法是打开编译器的地址检测工具。Linux下用gcc -g -fsanitizeaddress编译运行时会直接定位到非法访问的代码行。Windows下用Visual Studio的调试版本Visual C的运行库本身就有相关检测。用工具辅助定位比自己盯着代码干想快太多。4.2 关于“不带头结点”的兼容方案虽然题目要求带头结点但万一你遇到要求设计“不带头结点”的变体知道怎么改也是加分项。不带头结点时头指针直接指向第一个元素结点空表是NULL。关键差异在插入和删除插入第一个结点时需要*L newNode必须二级指针或返回新头指针。删除最后一个结点后头指针可能变成NULL同样需要二级指针。遍历、查找、求长度的代码不变起点从L-next变成L。我个人的体会是压轴笔试里如果出了不带头结点的题考官实际想考察的是你对头指针更新边界的警觉性而不是真的要你写多复杂的逻辑。只要你能说清楚“头指针可能在操作后变化因此必须显式更新”这个区分点就打出来了。4.3 合并两个有序单链表把基本功串起来的经典题热搜词里出现了“合并两个有序的单链表”顺便说一下这个进阶操作。两个带头结点的有序链表假设都是升序合并后仍然升序不需要额外申请太多新结点直接用“穿针引线”的方式改指针即可。思路核心准备一个虚拟头结点作为新链表起点用tail指针标记新链表末尾。同时扫描两个有序链表谁的data小就把谁接到tail后面tail后移。某一个链表遍历完毕直接把另一个链表剩余部分接上。LinkList MergeSortedList(LinkList L1, LinkList L2) { if (L1 NULL || L2 NULL) return L1 ! NULL ? L1 : L2; Node *dummy (Node*)malloc(sizeof(Node)); Node *tail dummy; Node *p L1-next; Node *q L2-next; while (p ! NULL q ! NULL) { if (p-data q-data) { tail-next p; p p-next; } else { tail-next q; q q-next; } tail tail-next; } tail-next p ! NULL ? p : q; Node *result dummy-next; free(dummy); return result; }这里用到了一个“虚拟头结点”的技巧本质上就是“经验型的带头结点思想”。因为合并过程中新链表的头可能来自L1也可能来自L2用一个虚拟头结点可以避免在循环里反复判断“是不是第一个结点”。我在很多复杂链表的算法题里都会先用虚拟头结点打底等代码跑通了再按需优化掉它。5. 循环单链表的延伸一次看清单链表家族欢迎词里有“循环单链表”这是个自然的延伸。循环单链表的最后一个结点的next不再指向NULL而是指向头结点或者不带头结点时指向第一个结点。这个设计让“从任意位置出发遍历整个链表”成为可能代价是遍历的终止条件从cur NULL变成cur head带头结点时。它在哪些场景有实际价值我用生命周期短的例子来说明调度器里用一个循环链表管理等待队列从头结点出发一直转圈就能轮询所有任务。约瑟夫环问题更是教科书级的“循环单链表经典应用”每隔k个结点移除一个直到只剩一个。你自己动手实现一次约瑟夫环对指针操作的熟练度会提升一个台阶。代码层面的改动其实就两处初始化时让头结点next指向自身遍历时判断回到头结点就停止。其他的插入删除逻辑几乎不变但不带头结点的循环链表在处理“回到起点”时会复杂不少我建议你从带头结点的循环单链表入手先跑通再谈优化。6. 几个容易忽略的工程细节6.1 内存分配的“脏数据”问题malloc并不负责清零它返回的内存里可能是上次被释放后留下的残骸。所以每个新申请的结点必须先把next初始化为NULL。如果没有这一步你打印或遍历到链表末尾时cur会顺着一个随机地址跑下去直到撞上非法内存才崩溃。这种问题有时候跑一次是好的跑两次就段错误属于最难排查的“偶发bug”。避免它的习惯很简单结点申请成功后立刻初始化字段newNode-data val; newNode-next NULL;两行代码的顺序也别反。强迫自己先初始化next再赋值data也行但重要的是别漏。6.2 减少嵌套的早期返回逻辑写链表操作时很多人习惯一层层if嵌套最后代码的可读性很差。比如删除函数可以先做非法参数检查直接return再处理正常逻辑。我自己更推荐“前置校验尽早返回”的写法if (pos 1) return 0; Node *prev L; ...这样每个函数的主体逻辑很扁平别人读起来也不会陷在多层括号里。等代码规模上了几百行你就会意识到这个习惯救了你好多次眼睛。6.3 单测驱动的“最小可运行”调试策略我每次写一个完整的链表头文件时从来不会一口气写完所有操作再编译。而是先实现一个最简框架主函数只做InitList和PrintList编译运行通过后再加InsertAtHead验证头插没有问题再继续加DeleteByIndex。这个“最小可运行”策略在调试时极其有效因为最新加入的代码就是最可能的bug来源排查范围被压缩到最小。对比坏习惯一口气写完十几个函数再编译编译器报一堆错你很难判断是结构体定义错了还是某个操作函数影响到了其他逻辑。宁可多编译几次也别省编译器的时间。根据我的经验做这种链表实验最痛苦的不是技术本身而是你在犯迷糊时缺乏一个可预期的“正确路径”。如果你能在纸上先把链表画出来把每个步骤的箭头画清楚再写代码你的出错率会降低一半。纸上画箭头这件事听起来简单又“低效”但恰恰是理解“链式结构”四个字最直接的途径。要是实在画不清楚就找一些小卡片或者便利贴把一段一段的结点连起来感受一下指针移动的轨迹。把实验做完代码跑通你手指间对指针的那份熟悉感会在后续学习树、图、哈希表时持续发光。

相关新闻

连接器立式注塑机全自动生产线:从方案设计到量产优化实战解析

连接器立式注塑机全自动生产线:从方案设计到量产优化实战解析

连接器立式注塑机全自动生产线做连接器这行的人都清楚,但凡涉及嵌件注塑(Insert Molding),基本上绕不开立式注塑机。前两年我接手了一个连接器车间的自动化升级项目,老板要求把原来的人工作业模式改成全自动生产线&…

2026/10/9 3:21:06 阅读更多 →
自注意力对抗深度子空间聚类:无监督聚类精度提升方案

自注意力对抗深度子空间聚类:无监督聚类精度提升方案

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

2026/10/9 3:21:06 阅读更多 →
CNN-Bi-LSTM中文情感分析毕设实战:轻量、可调、可答辩

CNN-Bi-LSTM中文情感分析毕设实战:轻量、可调、可答辩

简介:本资源是一套基于CNN-Bi-LSTM混合神经网络架构的中文情感分析系统完整源码,专为高校计算机、人工智能及相关专业学生设计,适用于毕业设计、课程设计及深度学习入门实践。项目代码经过严格测试,功能完备、可直接运行&#xff…

2026/10/9 3:21:06 阅读更多 →

最新新闻

JSP+Servlet+MySQL成绩管理系统:角色权限与课程设计实战

JSP+Servlet+MySQL成绩管理系统:角色权限与课程设计实战

简介:基于JspServlet实现的Web成绩管理系统,面向JavaWeb初学者及课程设计人群,覆盖学生、教师、管理员三类角色的核心业务,适合作为SSM/JSP阶段课设参考或毕设改造底稿。压缩包共870个文件,约39.64MB,包含9…

2026/10/9 3:58:28 阅读更多 →
ThreadPoolExecutor线程池核心原理与参数配置:从源码到线上故障排查

ThreadPoolExecutor线程池核心原理与参数配置:从源码到线上故障排查

入职第三年,我第一次在线上碰到线程池被打满的故障。业务高峰期,某个核心接口的响应从 200ms 一路飙升到 3 秒,还在往上走,监控面板上线程池的队列深度一个多小时没降下来。事后复盘,根子不在代码逻辑,而在…

2026/10/9 3:58:28 阅读更多 →
Apache SkyWalking实战:无侵入分布式链路追踪与排坑指南

Apache SkyWalking实战:无侵入分布式链路追踪与排坑指南

讲真,服务一拆成微服务,线上问题排查那叫一个酸爽。以前单体应用出问题,翻翻日志、看看堆栈基本就能定位;现在一个请求从网关进来,经过四五个服务,调了三四个数据库和缓存,到底卡在哪儿、失败出…

2026/10/9 3:58:28 阅读更多 →
华为eNSP跨交换机VLAN实验:Access与Trunk配置及802.1Q抓包

华为eNSP跨交换机VLAN实验:Access与Trunk配置及802.1Q抓包

简介:这份资源是面向计算机网络专业学生、网络管理员及网络技术初学者的跨交换机VLAN配置实验文档,基于华为eNSP模拟软件,帮助读者掌握复杂交换式以太网的设计流程与VLAN划分方法。内容围绕跨交换机VLAN划分、接入端口与主干端口的功能差异、…

2026/10/9 3:58:28 阅读更多 →
华科计院网络实验报告:从VLAN划分到SOCKET编程的完整链路

华科计院网络实验报告:从VLAN划分到SOCKET编程的完整链路

简介:这份华中科技大学计算机学院《计算机网络》实验报告,面向正在学习网络课程、需要完成组网与路由配置实验的高校学生及自学者。内容围绕IP协议、网络层与数据链路层协议、子网划分、RIP与OSPF路由协议、路由器及二/三层交换机配置、VLAN划分和访问控…

2026/10/9 3:58:28 阅读更多 →
Java决策树算法实现大学生就业预测系统:从CART原理到Spring Boot落地

Java决策树算法实现大学生就业预测系统:从CART原理到Spring Boot落地

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

2026/10/9 3:57:27 阅读更多 →

日新闻

Java时间API实战:LocalDate、Date与ZonedDateTime的转换与避坑指南

Java时间API实战:LocalDate、Date与ZonedDateTime的转换与避坑指南

Java时间API这个话题,隔三差五就会在群里被翻出来讨论一次。上周还有个同事线上处理一个订单超时问题,排查到最后发现是ZonedDateTime序列化后时区丢了,用户在下单当天晚上看到的时间整整差了8个小时。这类问题几乎每个做Java开发的人都遇到过…

2026/10/9 0:00:49 阅读更多 →
EasyTier实践:从NAT穿透到子网代理的异地组网部署与排错

EasyTier实践:从NAT穿透到子网代理的异地组网部署与排错

前几个月我手头有好几台机器需要互相访问:办公室台式机、家里 NAS、还有一台云主机。如果只是偶尔传个文件倒还好,问题是工作场景经常要在几处环境之间来回切换,每次都先登录跳板机再层层代理,实在折腾。我先后试过端口映射、自建…

2026/10/9 0:00:49 阅读更多 →
AI Agent工程实战:从七要素到七个决策点的系统设计指南

AI Agent工程实战:从七要素到七个决策点的系统设计指南

AI Agent 这个词在过去一年里被反复提及,但真正动手搭过一套能跑起来的 Agent 系统的人都知道,从"知道它是什么"到"让它稳定干活"之间隔着一整套工程决策。我前后参与过几个 Agent 项目的落地,从最初用现成框架拼装&…

2026/10/9 0:01:50 阅读更多 →

周新闻

KT148A语音芯片外挂8002D功放的工程实践指南

KT148A语音芯片外挂8002D功放的工程实践指南

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

2026/10/8 15:26:32 阅读更多 →
LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

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

2026/10/8 15:26:40 阅读更多 →
ARM架构深度解析:从RISC设计理念到交叉编译实战

ARM架构深度解析:从RISC设计理念到交叉编译实战

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

2026/10/8 10:10:36 阅读更多 →

月新闻

我发现了一个新思路:用 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/8 21:13:17 阅读更多 →
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/8 15:26:17 阅读更多 →
黑夜航拍船只数据集训练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/7 13:34:55 阅读更多 →