GC 基础入门

什么是GC(Garbage Collection)?

内存管理方式通常分为两种:

  • 手动内存管理(Manual Memory Management)
  • 自动内存管理(Garbage Collection, GC)
手动内存管理

手动内存管理是指开发者直接管理内存的分配和释放。典型的语言如C、C++使用malloc/freenew/delete等来手动分配和释放内存。

优点:

  • 精确控制内存的分配和释放。
  • 可预测的性能,没有GC带来的额外开销。
  • 不需要的内存可以立即释放,从而节省资源。

缺点:

  • 存在**内存泄漏(Memory Leak)**问题。
  • 存在**悬挂指针(Dangling Pointer)**问题。
  • 开发复杂度增加。

内存泄漏: 分配的内存未被释放,导致程序运行期间内存占用持续增加,可能导致系统内存耗尽而崩溃。 悬挂指针: 已释放的内存被引用时,可能会引发不可预测的行为。

近年来,Rust通过所有权系统(Ownership System)提供了一种安全的内存管理方式,这并不是完全的手动内存管理,而是手动内存管理的一种新替代方案。

GC(垃圾回收)

GC是编程语言中自动回收不再使用的内存的功能。接下来我们将详细解释这一机制。

自由存储列表(Free-Storage List)

在任何时刻,只有一部分为列表结构预留的内存实际上用于存储S表达式(S-expressions)。其余的寄存器(在我们的系统中大约有15,000个)组成了一个称为**自由存储列表(free-storage list)**的单一列表。程序中的某个特定寄存器FREE包含该列表的第一个寄存器位置。

当需要额外的列表结构时,自由存储列表的第一个单词将被使用,并且寄存器FREE的值会更新为自由存储列表的第二个单词位置。用户不需要编程将寄存器返回到自由存储列表中。

——《Recursive Functions of Symbolic Expressions Their Computation by Machine, Part I》John McCarthy

GC的概念始于1960年,约翰·麦卡锡(John McCarthy)在其论文《Recursive Functions of Symbolic Expressions and Their Computation by Machine, Part I》中介绍了自由存储列表(Free-Storage List)的概念,这是后来GC(垃圾回收)的基础思想。

为什么需要这个概念?

LISP(LISt Processor)是一种高级语言,支持符号处理(Symbolic Processing)和递归调用(Recursion)。然而,当时的计算机技术难以应对LISP复杂的内存管理问题。LISP在运行过程中不断生成对象,未使用的对象(垃圾)堆积会导致严重的内存泄漏问题。由于程序员很难完全追踪所有的内存分配和释放,因此引入了自动内存管理技术。

自由存储列表(Free-Storage List)是早期LISP系统中实现自动内存管理的基础概念。当时LISP采用动态生成列表结构的方式,程序员显式释放内存非常困难。为了解决这个问题,引入了跟踪未使用内存块的Free-Storage List概念,这最终发展为自动内存管理的思想。其基本原理是使用特定寄存器(FREE)存储可用内存列表的起始位置,按需获取内存并在不再使用时将其返回。这种思想成为了后来Mark-and-Sweep方式GC的基础。

Newell-Shaw-Simon 的列表存储方式的一个重要特点是,即使相同的数据多次出现,也可以只在计算机内存中存储一次。也就是说,列表可以“重叠(overlapped)”。
然而,这种重叠会在删除(erasure)过程中引发问题。当不再需要某个列表时,必须选择性地删除那些没有与其他列表重叠的部分。在LISP中,麦卡锡(McCarthy)提出了一个优雅但低效的解决方案。
本文则描述了一种能够实现高效删除的通用方法。
——《A Method for Overlapping and Erasure of Lists》 (1963),George E. Collins

尽管约翰·麦卡锡的论文奠定了Mark-and-Sweep方法的基础,但在执行GC时会导致程序暂停(Stop-the-World)现象。这使得实现实时GC变得困难。因此,Collins提出了引用计数(Reference Counting)的概念作为GC方法。
该方法通过为每个对象保存一个引用计数(Reference Count),并在引用计数变为0时立即回收内存。

Collins(1960年)提出的引用计数GC虽然实现了即时内存回收,但由于循环引用问题,并不是一个完美的解决方案。
尽管如此,现代语言如Python和Swift仍然使用这种方法,但加入了一些补充技术来解决其缺陷。

(顺便提一句,“优雅但低效”这样的评价让我觉得有趣。程序员这个职业似乎总有一种特点,他们喜欢用“优雅”的方式讽刺对方。也许是因为讨论的是抽象概念,缺乏实体暴力的存在感?在韩国,如果有人这样说话,可能会被拳头教训吧。)

《A LISP Garbage Collector for Virtual-Memory Computer Systems》论文摘要

本文提出了一种适用于非常大的虚拟内存环境的列表处理系统的垃圾回收算法。
该算法的主要目的不是“释放空闲内存”,而是压缩活动内存(compaction)
在虚拟内存系统中,由于空闲内存实际上并不会耗尽,因此很难决定何时触发垃圾回收。因此,本文讨论了触发垃圾回收的各种条件。
——《A LISP Garbage Collector for Virtual-Memory Computer Systems》 (Fenichel & Yochelson, 1969)

(虚拟内存(Virtual Memory):一种逻辑上管理超出物理内存大小的大容量内存的技术。)

这篇论文改进了Minsky的复制式GC(Copying GC),使其能够在虚拟内存环境中高效运行。
本文实际实现了Minsky的垃圾回收器,它利用深度优先搜索(DFS, Depth-First Search)将可达数据复制到辅助存储器中,分配到新的连续地址后重新加载回内存。尽管有评论认为其实现完成度较低(因为稍后会详细解释,所以这里不需要完全理解),这篇论文是首次将Minsky的方法应用于现代虚拟内存环境中的“Copying GC”,如果没有这篇论文,Java和C#中的分代GC(Generational GC)概念可能就不会出现。

“提出了一种简单的非递归(nonrecursive)列表结构压缩方法或垃圾收集器。该算法适用于紧凑(compact)结构和LISP风格的列表结构。通过逐步利用部分结构来追踪复制的列表,从而消除了对递归的依赖。” — (C. J. Cheney, 1970)

随后,使用广度优先搜索(BFS, Breadth-First Search)而非深度优先搜索(DFS),设计了一种“非递归(Nonrecursive)”的GC,使其能够在没有栈的情况下运行。
这项研究对Java、C#、Python的Copying GC方式产生了重要影响,基于BFS的设计避免了栈溢出问题,并具有缓存友好的结构。

为什么BFS可以以非递归的方式实现,而DFS不能?

首先,大多数读者应该知道,栈是一种**后进先出(LIFO, Last In, First Out)的数据结构。最后添加的元素会最先被移除。
队列则是一种
先进先出(FIFO, First In, First Out)**的数据结构。也就是说,最先添加的元素会最先被移除。

DFS的工作原理

DFS采用一种沿着某条路径深入探索到底,然后回溯(backtracking)的方式。为了实现这一点,DFS需要使用栈(Stack)或递归函数(Recursive Call)。
DFS基于栈的原理如下:

  1. 将当前节点存储在栈中(或通过递归调用)。
  2. 如果有下一个要访问的节点,则继续通过栈调用(或递归调用)进行处理。
  3. 当没有更多可前进的地方时,执行回溯(返回上一步)。
BFS的工作原理

BFS则是基于队列的原理:

  1. 将起始节点添加到队列中(Enqueue)。
  2. 从队列中取出一个节点(Dequeue),访问它,并将其相邻节点重新添加到队列中。
  3. 重复上述过程,直到访问完所有节点。

由于BFS使用队列,因此不需要额外的递归调用。


以洞穴探险为例

DFS

在洞穴探险时,DFS会选择一条路一直走到尽头,如果路被堵住,就会回溯并尝试新的路径。
当路被堵住时,必须记住(存储)返回的路径,否则无法回到原点。

BFS

BFS会在洞口布置一支队伍,同时探索所有的岔路。先确认第一层的所有路径,然后再进入下一层。
为了合理分配队伍,必须优先安排最早到达的岔路。

GC的发展历程

年代

研究者

GC算法

改进点

问题

1960

John McCarthy

Mark-and-Sweep

首次引入GC概念

Stop-the-World,碎片化问题

1963

Marvin L. Minsky

Copying GC(基于DFS)

解决内存碎片化,优化缓存

循环引用问题,磁盘使用

1969

Fenichel & Yochelson

Copying GC(虚拟内存应用)

使用两个半空间(Semispaces)

内存不足时程序变慢

1970

C. J. Cheney

非递归Copying GC(基于BFS)

无需栈即可进行GC

BFS基础,内存重定位优化较少

GC算法发展的意义是什么?

通过减少“Stop-the-World”问题,并逐步向实时(Concurrent)GC发展,我们可以看到GC的进步过程。

那么现在让我们总结一下垃圾回收器有哪些算法,以及这些算法的概述。

在进入主题之前,先简单介绍一些基础知识:

  • 我们通常将内存管理分为两个池:堆内存和栈内存。
  • 栈内存主要用于存储小型(短生命周期)的数据(主要是值类型)。
  • 堆内存则用于存储更大、更持久的数据(主要是引用类型)。

GC算法大致可以分为两类:

1. Tracing GC(追踪型GC)

这是最常见的GC类型。
垃圾回收通过可达性(Reachability)来判断某个对象是否为垃圾。
从根(Root,如全局变量、栈变量等)开始查找可达对象(Reachable),然后销毁不可达对象(Unreachable)。
如果没有有效的引用,对象会被标记为unreachable并被回收。
常用的算法主要有以下三种(附带代码仅为作者学习核心概念实现的伪代码,不保证实际运行效果)。

1-1. Mark-and-Sweep(标记-清除)

(ref: Demystifying memory management in modern programming languages | Technorage)

#include <iostream>
#include <vector>
#include <algorithm>// Mark-and-Sweep GC 实现(使用原始指针)
class Object {
public:bool marked;                       // GC 标记状态std::vector<Object*> children;     // 子对象列表Object() : marked(false) {}// 添加子对象的方法void addChild(Object* child) {children.push_back(child);}// 析构函数:对象被删除时打印日志(用于调试)~Object() {std::cout << "Deleting Object at " << this << std::endl;}
};// 全局堆和根对象容器
std::vector<Object*> heap;
std::vector<Object*> roots;// 递归标记对象及其子对象
void mark(Object* obj) {if (obj == nullptr || obj->marked) {return;}obj->marked = true;for (Object* child : obj->children) {mark(child);}
}// 清除阶段:删除未标记的对象并从堆中移除
void sweep() {auto it = heap.begin();while (it != heap.end()) {Object* obj = *it;if (!obj->marked) {// 释放无法到达的对象delete obj;it = heap.erase(it);} else {// 复位标记以便下次 GC 运行obj->marked = false;++it;}}// 更新根对象列表,仅保留仍在堆中的对象std::vector<Object*> newRoots;for (Object* root : roots) {if (std::find(heap.begin(), heap.end(), root) != heap.end()) {newRoots.push_back(root);}}roots = newRoots;
}// 执行完整的 GC 过程:标记并清除
void gc() {for (Object* root : roots) {mark(root);}sweep();
}int main() {// 创建对象并加入堆中Object* a = new Object();Object* b = new Object();Object* c = new Object();// 设定对象间的引用关系a->addChild(b);b->addChild(c);// 将所有对象加入堆heap.push_back(a);heap.push_back(b);heap.push_back(c);// 设定根对象(此处 a 作为根对象)roots.push_back(a);std::cout << "Heap size before GC: " << heap.size() << std::endl;// 执行垃圾回收gc();std::cout << "Heap size after GC: " << heap.size() << std::endl;// 释放剩余的对象(实际系统中最终 GC 运行时会处理)for (Object* obj : heap) {delete obj;}heap.clear();roots.clear();return 0;
}

(如果在代码中使用C++的RAII特性并通过shared_ptr管理内存,则很难观察到GC的实际操作,因此需要使用原始指针。)

  1. Mark阶段 :识别正在使用的对象。
  2. Sweep阶段 :删除不可达对象。
  3. 出现内存碎片化(Fragmentation)问题。

简而言之,从Root Space找到连接的对象,并删除未连接的对象。
优点是实现相对简单,且能有效保留必要的对象,避免浪费内存。
缺点是会出现“Stop-the-World”现象,并产生内存碎片化问题。

内存碎片化 :当内存单元在堆中分配时,其大小取决于存储变量的大小。回收内存时,堆内存会分裂成碎片。

  • 内部碎片化 :已分配内存块中未使用的空间(例如分配4KB但只使用1KB)。
  • 外部碎片化 :可用内存分散,无法分配大块内存。

(img ref: Visualizing memory management in V8 Engine (JavaScript, NodeJS, Deno, WebAssembly) | Technorage )

1-2. Mark-Compact(标记-整理)

在Mark阶段之后,将存活对象移动到内存的一侧以进行整理(Compaction),从而解决碎片化问题。
缺点是整理过程中会产生额外的开销,GC时间较长。

#include <iostream>
#include <vector>
#include <algorithm>
#include <cstring>constexpr std::size_t HEAP_SIZE = 1024;// 对象类 (简单链表结构)
class Object {
public:int value;Object* next;explicit Object(int val) : value(val), next(nullptr) {}// 析构函数 (调试用途)~Object() {std::cout << "Deleting Object with value " << value << " at " << this << std::endl;}
};// 使用两个半空间 (Semispace) 进行内存管理
std::vector<char> fromSpace(HEAP_SIZE);
std::vector<char> toSpace(HEAP_SIZE);std::size_t fromIndex = 0;
std::size_t toIndex = 0;// 在给定的空间中分配对象 (使用 placement new)
Object* allocate(int val, std::vector<char>& space, std::size_t& idx) {if (idx + sizeof(Object) > space.size()) {return nullptr; // 空间不足}Object* obj = new (&space[idx]) Object(val);idx += sizeof(Object);return obj;
}// 递归复制对象及其链接
Object* copy(Object* obj, std::vector<char>& targetSpace, std::size_t& targetIdx) {if (obj == nullptr) {return nullptr;}Object* newObj = allocate(obj->value, targetSpace, targetIdx);if (newObj == nullptr) {return nullptr;}newObj->next = copy(obj->next, targetSpace, targetIdx);return newObj;
}// 复制垃圾回收 (Copying GC) - 复制所有可达对象到新的空间
void gc(Object*& root) {toIndex = 0;Object* newRoot = copy(root, toSpace, toIndex);// 交换空间,使新分配的空间成为新的堆std::swap(fromSpace, toSpace);fromIndex = toIndex;root = newRoot;
}int main() {// 在 fromSpace 中分配对象Object* a = allocate(10, fromSpace, fromIndex);if (a == nullptr) {std::cerr << "Allocation failed for object a." << std::endl;return 1;}Object* b = allocate(20, fromSpace, fromIndex);if (b == nullptr) {std::cerr << "Allocation failed for object b." << std::endl;return 1;}a->next = b;std::cout << "Before GC, root object value: " << a->value << std::endl;// 运行 GCgc(a);std::cout << "After GC, copied root object value: " << a->value << std::endl;return 0;
}
1-3. Copying GC(复制型GC)

将内存分为两个半空间(Semi-Space),分别称为From-Space和To-Space。
仅将可达对象复制到新空间(To-Space),然后销毁旧空间。
优点是没有内存碎片化,分配速度快;缺点是内存使用效率低(仅使用一半的内存)。

2. Reference Counting(引用计数)
#include <iostream>
#include <unordered_map>// 对象类:存储简单整数值
class Object {
public:int value;explicit Object(int val) : value(val) {}// 析构函数: 当对象被释放时打印日志~Object() {std::cout << "Deleting Object with value " << value << " at " << this << std::endl;}
};// 全局引用计数表
std::unordered_map<Object*, int> refTable;// 创建对象并初始化其引用计数
Object* createObject(int val) {Object* obj = new Object(val);refTable[obj] = 1;return obj;
}// 增加对象的引用计数
void addRef(Object* obj) {if (obj != nullptr) {auto it = refTable.find(obj);if (it != refTable.end()) {++(it->second);}}
}// 释放对象的引用:当引用计数为0时删除对象
void releaseRef(Object* obj) {if (obj != nullptr) {auto it = refTable.find(obj);if (it != refTable.end()) {if (--(it->second) == 0) {refTable.erase(it);delete obj;}}}
}int main() {// 创建对象Object* a = createObject(10);Object* b = createObject(20);// 增加 b 的引用计数 (模拟多个地方使用)addRef(b);// 释放 b 的引用:两次调用后计数变为0,对象被释放releaseRef(b);releaseRef(b);// 释放 areleaseRef(a);std::cout << "Remaining objects in refTable: " << refTable.size() << std::endl;return 0;
}

(使用全局引用表unordered_map,在创建对象时将引用计数初始化为1,并通过addRefreleaseRef调整引用计数。当引用计数为0时调用delete。)

每个对象存储一个引用计数(Reference Count),当引用计数为0时立即释放内存。
由于可以立即释放内存,因此没有“Stop-the-World”现象,且对象的使用量清晰可见。
缺点是存在循环引用问题,并且在处理大型对象时性能下降。
Swift中使用了改进版的ARC(Automatic Reference Counting)。

核心总结

分类

Tracing GC (Mark & Sweep)

Reference Counting GC

基本概念

从根开始追踪并清理对象

根据对象的引用计数释放内存

内存泄漏可能性

可能存在循环引用(GC自动解决)

循环引用导致内存泄漏

GC执行成本

与对象数量和引用图大小成正比

对象越多计算负担越大

性能

对象越多性能越可能下降

引用计数立即归零时快速释放

Stop-the-World(STW)

GC执行时暂停

立即释放,无需暂停

典型应用场景

C#, Java, Python

Objective-C (ARC), Swift

此外,除了基本的GC算法外,还有一些优化策略。

GC优化策略有哪些?

最常见的是Generational GC(分代GC)

1. Generational GC(分代GC)

根据对象的生存周期将其分为Young Generation和Old Generation,并对新生代(Young)快速GC,对老年代(Old)缓慢GC。
接下来我们看看Young和Old是如何划分的。

1-1. Young Generation(Eden + Survivor Space)
  • Eden(伊甸园)区域
    新创建的对象首先分配到这里。大多数对象在这里生成后很快就会被销毁,因此GC频繁发生。
  • Survivor(幸存者)区域
    Eden区域中经过GC后存活的对象会移动到这里。通常有两个Survivor区域。对象多次经历GC后最终晋升到Old Generation。
    Survivor区域通常分为两个:Survivor0(From Space)和Survivor1(To Space)。
    在这里,新创建的对象存储在此,大部分对象会快速销毁。此区域使用Copying GC快速移除对象。
    当该区域中的对象被移除时,称为Minor GC。
1-2. Old Generation(老年代)
  • Tenured(老年)区域
    多次在Young Generation中经历GC后存活的对象会移动到这里。
    此区域GC较少发生,但一旦发生,涉及的对象较多,处理成本较高。
    老年代对象存储在这里,使用Mark-Sweep或Mark-Compact方式进行清理。
    利用对象生命周期模式进行性能优化。当此区域中的对象被移除时,称为Major GC(或Full GC)。
    Young GC快速执行,Old GC较慢执行,但在Old Generation中发生GC时可能会出现“Stop-the-World”。

通常,Old区域分配较大,因此GC发生的频率低于Young区域。

可以用人类一生的例子来说明:

  1. 在Eden(伊甸园)中以灵魂状态存在;
  2. 接受世界的召唤出生;
  3. 经历世间的风雨洗礼(Survivor),无事故地幸存下来;
  4. 成为老人,获得社会尊重(Promotion);
  5. 受人尊敬的老人去世后,社会上更多的人哀悼(比年轻人更大的哀悼成本)。

2. Parallel GC(并行GC)

使用多个线程(Thread)并行执行GC,提升GC速度。适用于大型应用程序,但可能存在线程同步开销。Java HotSpot JVM实现了Parallel GC。
需要注意的是,分代GC和并行GC并不互斥,因此可以同时使用,实际上JVM HotSpot和.NET中也确实如此。

(Time Freeze!!!)

那么GC是如何执行的呢?

1. Stop-the-World(STW)

GC执行时程序完全停止。
这是传统的GC方式,实现相对简单,能够准确回收内存。
但会导致响应时间增加,用户体验下降。

(img ref:An attempt at visualizing the Go GC)

2. Incremental GC(增量GC)

将GC分成多个小步骤(Small Steps)执行,基于Dijkstra的三色标记算法(Tri-Color Marking Algorithm)。

这个算法的核心是高效地标记三种颜色:

  • 白色 :未处理(Unprocessed)
  • 灰色 :处理中(Processing)
  • 黑色 :已处理(Processed)

节点着色的规则如下:

  1. 所有节点初始为白色。
  2. 访问对象时标记为灰色。
  3. 访问完成后标记为黑色。

这样就形成了三个集合,对象从白色→灰色→黑色移动。重要的是,白色和黑色之间不会直接连接。

3. Concurrent GC(并发GC)

GC与应用程序同时运行,尽量减少“Stop-the-World”时间。
GC线程独立运行,并与其他应用程序线程同时进行内存标记(mark)或复制等操作。
这使得GC执行期间应用程序仍能运行,并利用多核优势,但内存管理变得更加复杂。

“过早优化是万恶之源。”——Donald Knuth

本文介绍了GC的历史、当前使用的GC算法、优化策略及执行方法。后续还提到了避免GC的ZoC(Zero Allocation)等内容,但由于本文面向初学者,部分内容(如某些优化策略)被省略了。

喜欢手动管理内存的人可能会将GC视为罪恶,认为它增加了编程的不确定性。
但正如Donald Knuth所说,与其沉迷于内存管理这样的“过早优化”,不如让GC帮助开发者专注于解决问题。
程序员的角色不仅是掌握技术,更是解决眼前的问题。
GC为许多程序员减轻了“内存管理”的负担,是一项重要的工具。

重要的是,不是“GC好还是不好”,而是思考“哪种工具最适合解决我的问题”。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/71155.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

简要分析LeetCode树经典题目(Java)

目录 开场白 实战环节 准备工作 遍历问题 LeetCode144. 二叉树的前序遍历 方法一 方法二 LeetCode94. 二叉树的中序遍历 LeetCode145. 二叉树的后序遍历 方法一 方法二 LeetCode102. 二叉树的层序遍历 LeetCode103. 二叉树的锯齿形层序遍历 LeetCode107. 二叉树的…

深度剖析 NVIDIA Isaac Sim:重塑机器人开发的创新引擎

一、引言 在上一篇文章探索探索 NVIDIA Isaac Sim&#xff1a;解锁机器人开发的无限可能中&#xff0c;我们初步领略了 NVIDIA Isaac Sim 平台为机器人开发带来的无限潜力。随着平台的不断更新迭代&#xff0c;又有许多令人惊喜的新特性和优化&#xff0c;让我们继续深入体验这…

Leetcode 712. Minimum ASCII Delete Sum for Two Strings

Problem Given two strings s1 and s2, return the lowest ASCII sum of deleted characters to make two strings equal. Algorithm Dynamic Programming (DP): similar as Longest Common Subsequence (LCS). If s1[i] ! s2[j]: F ( i , j ) min ⁡ ( F ( i − 1 , j ) …

【Python 学习 / 4】基本数据结构之 字符串 与 集合

文章目录 1. 字符串概念1.1 字符串的创建1.2 字符串的访问和操作1.2.1 下标访问1.2.2 切片操作1.2.3 字符串的拼接和重复1.2.4 字符串的长度 1.3 字符串的方法1.4 字符串的查找和替换1.5 字符串格式化1.5.1 使用 % 运算符1.5.2 使用 str.format()1.5.3 使用 f-string&#xff0…

Spring Boot (maven)分页3.0版本 通用版

前言&#xff1a; 通过实践而发现真理&#xff0c;又通过实践而证实真理和发展真理。从感性认识而能动地发展到理性认识&#xff0c;又从理性认识而能动地指导革命实践&#xff0c;改造主观世界和客观世界。实践、认识、再实践、再认识&#xff0c;这种形式&#xff0c;循环往…

OpenAI 放王炸,将发布整合多项技术的 GPT-5,并免费无限使用,该模型有哪些技术亮点

对于 ChatGPT 的免费用户&#xff0c;将可以无限制地访问 GPT-5&#xff0c;但仅限于标准的智能级别。该级别会设定滥用限制&#xff0c;以防止不当使用(意思就是你得付费嘛)。 OpenAI CEO Sam Altman 今天在 X 上透露了 GPT-4.5 和 GPT-5 的最新发展计划。 OpenAI 将发布代…

git pull 与 git pull --rebase的区别与使用

git pull 与 git pull --rebase的区别与使用 1. 优化commit很乱的记录1.1 解决问题例子 1.2 idea 中更新代码 1. 优化commit很乱的记录 课程&#xff1a;优化Commit记录 之前遇到过这个问题&#xff0c;已知没当回事&#xff0c;现在想做出改变&#xff0c;正好B站有个视频&am…

Web3 开发者周刊 36 | 构建自主未来:Agent、可扩展性与赏金

欢迎来到 Web3 开发者周刊 36&#xff0c;这里汇聚了赋能您的 Web3 构建之旅的各种资源。本周我们将剖析基于Agent的系统&#xff0c;讨论来自 Vitalik 关于以太坊 L1 和 L2 的最新思考&#xff0c;并提供最新高价值Bounty消息。 开始Build吧&#xff01; ✅ One Trillion Age…

[小M全新力作-修稿]PFC_LLC电源设计全攻略:从原理学习到SIMULINK仿真到EDA板实战,再到实物电源调试

未完结 可私信获取手稿以详细理解本文 LLC __2025.1.13 MOS ZVS ZCS 重锁粥支&#xff0c;MOS管由于设计工艺&#xff0c;总会产生一些寄生元件如&#xff1a;寄生电容&#xff08;pf级&#xff09;、寄生电阻、寄生二极管等 由于这些MOS参数&#xff0c;会导致MOS的导通…

VNC远程控制Mac

前言 macOS系统自带有VNC远程桌面&#xff0c;我们可以在控制端上安装配置VNC客户端&#xff0c;以此来实现远程控制macOS。但通常需要在不同网络下进行远程控制&#xff0c;为此&#xff0c;我们可以在macOS被控端上使用cpolar做内网穿透&#xff0c;映射VNC默认端口5…

关闭浏览器安全dns解决访问速度慢的问题

谷歌浏览器加载速度突然变慢了&#xff1f;检查安全DNS功能(DoH)是否被默认开启。 谷歌浏览器在去年已经推出安全DNS功能(即DoH) , 启用此功能后可以通过加密的DNS增强网络连接安全性。例如查询请求被加密后网络运营商将无法嗅探用户访问的地址&#xff0c;因此对于增强用户的…

ES分词技术

Elasticsearch&#xff08;ES&#xff09;的搜索功能依赖于分词技术&#xff0c;分词是将文本拆分为单个词或词项的过程&#xff0c;直接影响搜索的准确性和效率。以下是ES中分词技术的详细介绍&#xff1a; 1. 分词器&#xff08;Analyzer&#xff09; 分词器是分词的核心组…

SpringBoot3 快速启动框架

文章目录 1 SpringBoot3 介绍 1.1 SpringBoot3 简介1.2 快速入门1.3 入门总结 2 SpringBoot3 配置文件 2.1 统一配置管理概述2.2 属性配置文件使用2.3 YAML配置文件使用2.4 批量配置文件注入2.5 多环境配置和使用 3 SpringBoot 整合 springMVC 3.1 实现过程3.2 web相关配置3.3…

小小小病毒(3)(~_~|)

一分耕耘一分收获 声明&#xff1a; 仅供损害电脑&#xff0c;不得用于非法。损坏电脑&#xff0c;作者一律不负责。此作为作者原创&#xff0c;转载请经过同意。 欢迎来到小小小病毒&#xff08;3&#xff09; 感谢大家的支持 还是那句话&#xff1a;上代码&#xff01; …

Map 和 Set

目录 一、搜索 概念&#xff1a; 模型&#xff1a; 二、Map ​编辑 1.Map 实例化&#xff1a; 2. Map的常见方法&#xff1a; 3.Map的常见方法演示&#xff1a; 1. put(K key, V value)&#xff1a;添加键值对 3. containsKey(Object key)&#xff1a;检查键是否存在 4.…

pytest测试专题 - 2.1 一种推荐的测试目录结构

<< 返回目录 1 pytest测试专题 - 2.1 一种推荐的测试目录结构 2 pytest 项目目录结构及文件功能 以下是典型 pytest 项目中常见的文件和目录结构及其功能的概述&#xff1a; 2.1 文件/目录结构 文件/目录功能描述test_ 文件* 主测试文件&#xff0c;命名通常以 test_…

常用架构图:业务架构、产品架构、系统架构、数据架构、技术架构、应用架构、功能架构及信息架构

文章目录 引言常见的架构图I 业务架构图-案例模块功能说明1. 用户界面层 (UI)2. 应用服务层3. 数据管理层4. 基础设施层业务流程图示例技术实现II 功能架构图 -案例功能模块说明1. 船舶监控模块2. 报警管理模块3. 应急响应模块4. 通信管理模块5. 数据分析模块数据管理层基础设施…

爬虫实战:利用代理ip爬取推特网站数据

引言 亮数据-网络IP代理及全网数据一站式服务商屡获殊荣的代理网络、强大的数据挖掘工具和现成可用的数据集。亮数据&#xff1a;网络数据平台领航者https://www.bright.cn/?promoRESIYEAR50/?utm_sourcebrand&utm_campaignbrnd-mkt_cn_csdn_yingjie202502 在跨境电商、社…

蓝桥杯篇---温度传感器 DS18B20

文章目录 前言DS18B201. DS18B20 引脚说明2. 单总线通信协议3. DS18B20 操作流程初始化写操作读操作 4. 示例代码5. 代码说明6. 注意事项总结 前言 本文简单介绍了IAP15F2K61S2中温度传感器模块DS18B20的使用。 DS18B20 DS18B20 是一款数字温度传感器&#xff0c;采用单总线&…

YOLOv5-Seg 深度解析:与 YOLOv5 检测模型的区别

YOLOv5-Seg 深度解析&#xff1a;与 YOLOv5 检测模型的区别 1. 概述 YOLOv5 是一个强大的目标检测框架&#xff0c;而 YOLOv5-Seg 是其扩展版本&#xff0c;增加了实例分割功能。 YOLOv5&#xff08;目标检测&#xff09; vs YOLOv5-Seg&#xff08;实例分割&#xff09; 版…