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
赋值给成员变量。