UE5 FName
太长不看版:
每一个 FName 在构造的时候,会先去 FNamePool 中查找是否存在,如果存在,直接返回;如果不存在,先计算一个 Hash 值,然后把 Hash 值和 字符串保存到 FNamePool 里面。
1. FName 与 FString 总体区别概览
1.1 存储与全局唯一性
- FName
- 全局唯一表 Name Table
- 所有
FName实例存储在全局表中,相同字符串共享一份数据,避免重复存储。例如,多个对象使用名称 "Player" 时,内存中仅存一份。
- 所有
- 不区分大小写
- 存储时会统一为小写(或其它规范形式),比较时忽略大小写。
FName("Player")和FName("player")视为相同。
- 存储时会统一为小写(或其它规范形式),比较时忽略大小写。
- 轻量级
- 内部通过索引值引用,操作高效。
- 全局唯一表 Name Table
- FString
- 动态字符串:每个实例独立存储字符串的内容,支持修改(如 拼接、替换)。
- 区分大小写
- UTF -16 编码:支持宽字符和国际化文本(如中文)。
1.2 性能与内存
-
FName
- 创建开销:首次创建时需查表,后续复用现有实例,适合频繁使用的名称(如 资源路径、标签)。
- 快速比较:直接比较哈希或指针,时间复杂度 O(1)。
- 内存优化:重复字符串共享内存,节省资源。
-
FString
- 操作灵活:支持动态修改,但每次修改可能触发内存重分配。
- 比较开销:需逐字符比对,长字符串性能较低。
- 独立内存占用:每个实例独立存储,可能浪费内存。
1.3 使用场景
-
FName
- 标识符:资源名称(如材质、纹理路径)、标签 (Actor Tags)、对象名称。
- 引擎内部使用:资源引用、骨骼动画名称、属性名(如
UProperty)。 - 不依赖内容的操作:无需修改或处理字符串。
-
FString
- 文本处理:用户输入、文件读写、网络数据、UI 显示文本。
- 动态操作:需要拼接(如
Append)、格式化(如FString::Printf)或复杂处理。 - 区分大小写:密码、区分大小写的标识。
2. FName 表存在哪里 以及 Hash 是怎么实现的
2.1 数据

FName 中存储了 FNameEntryId 类型的 ComparisonIndex 、DisplayIndex 和 uint32 类型的 Number
ComparisonIndex 可以认为是 全局 FName 数组的索引,在 FName 构造的时候被赋值

2.2 实现
这里只是简单看下源码,不涉及具体更细节的一些东西,知道大概原理

重载了许多构造函数

真正的实现委托给了 FNameHelper::MakeDetectNumber

传参之前先把 WIDECHAR 类型转换成 FWideStringViewWithWidth 类型,里面包含了字符串内容和长度。

MakeInternal 里面就到了主要逻辑部分 FindOrStoreString

FindOrStoreString 会根据传进来的 FindType 在 FNamePool 中存储或查找,返回一个 FNameEntryIds 结构体。

FNameEntryIds 结构体:

等下再讲 FNameEntryId 结构体。回到 FindOrStoreString 函数,核心是FNamePool 的存储

基本逻辑是先去查找池子里是否已经存在 ,如果存在,直接返回 FNameEntryId。
看下 FNameEntryId 这个结构体

FNameEntryId 这个结构体的核心数据是 uint32 Value,但是 UE 为了性能做了非常多的优化,比如快速比较等等,这里不去深入。

回到 FNamePool::Store 函数
如果不存在,先根据 FNameStringView Name 构造一个 FNameComparisonValue,看下这个结构体。

FNameComparisonValue 实际就是忽略大小写的 FNameValue。

FNameValue 有三个成员变量,FNameStringView Name, FNameHash Hash 和 t条件宏触发时的 FNameEntryId ComparisonId。
构造函数时,传入一个 FNameStringView,赋给 Name,然后调用 HashName,赋给 Hash。
HashName 函数如下

看下 FNameHash 这个结构体

这里就到了真正计算 Hash 值的地方

把 const char* s 转换为 uint64 哈希值。
回到 FNamePool::Store 函数。如果池中不存在,Insert 插入池中,

Insert 里面实际调用了

Entries->Create 如下

它实际上创建了一个 FNameEntryHandle

FNameEntryHandle 可以转换成 FNameEntryId。
于是 FNamePool::Store 这个函数得到了 FNameEntryId,存储了下来。
在 FName 的构造过程中,最后一步是 FinalConstruct

这里会把前面获取的 ComparisonId 和 DisplayId 赋值给成员变量。

浙公网安备 33010602011771号