Fork me on GitHub

UE5 FName

太长不看版:

每一个 FName 在构造的时候,会先去 FNamePool 中查找是否存在,如果存在,直接返回;如果不存在,先计算一个 Hash 值,然后把 Hash 值和 字符串保存到 FNamePool 里面。

1. FName 与 FString 总体区别概览

1.1 存储与全局唯一性

  • FName
    • 全局唯一表 Name Table
      • 所有 FName 实例存储在全局表中,相同字符串共享一份数据,避免重复存储。例如,多个对象使用名称 "Player" 时,内存中仅存一份。
    • 不区分大小写
      • 存储时会统一为小写(或其它规范形式),比较时忽略大小写。
        • FName("Player")FName("player") 视为相同。
    • 轻量级
      • 内部通过索引值引用,操作高效。
  • 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 数据

image-20250806153407561

FName 中存储了 FNameEntryId 类型的 ComparisonIndexDisplayIndexuint32 类型的 Number

ComparisonIndex 可以认为是 全局 FName 数组的索引,在 FName 构造的时候被赋值

image-20250806153633946

2.2 实现

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

image-20250414172655278

重载了许多构造函数

image-20250414172722971

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

image-20250414174605247

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

image-20250414174716046

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

image-20250414174808790

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

image-20250414175041241

FNameEntryIds 结构体:

image-20250806150021383

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

image-20250806143652562

基本逻辑是先去查找池子里是否已经存在 ,如果存在,直接返回 FNameEntryId

看下 FNameEntryId 这个结构体

image-20250806150513051

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

image-20250806150744989

回到 FNamePool::Store 函数

如果不存在,先根据 FNameStringView Name 构造一个 FNameComparisonValue,看下这个结构体。

image-20250806143750409

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

image-20250806143925187

FNameValue 有三个成员变量,FNameStringView Name, FNameHash Hash 和 t条件宏触发时的 FNameEntryId ComparisonId

构造函数时,传入一个 FNameStringView,赋给 Name,然后调用 HashName,赋给 Hash

HashName 函数如下

image-20250806144525819

看下 FNameHash 这个结构体

image-20250806143352836

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

image-20250806143433994

const char* s 转换为 uint64 哈希值。

回到 FNamePool::Store 函数。如果池中不存在,Insert 插入池中,

image-20250806142748177

Insert 里面实际调用了

image-20250806145733780

Entries->Create 如下

image-20250806155254024

它实际上创建了一个 FNameEntryHandle

image-20250806155321491

FNameEntryHandle 可以转换成 FNameEntryId

于是 FNamePool::Store 这个函数得到了 FNameEntryId,存储了下来。

在 FName 的构造过程中,最后一步是 FinalConstruct

image-20250806155700355

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

posted @ 2025-04-14 18:28  icewalnut  阅读(138)  评论(0)    收藏  举报