FString
封装TArray
FName
用于保存对象或资产名称的字符串类型,在查找、比较、存储方面做了优化。
相关类型与内存布局
EFindName
针对FNamePool操作时的操作方法,分为查找Name、添加Name、添加Name(若已存在则替换,要求编码类型与字符串长度相等)
EName
对UE常用的FName进行特殊记录,用于更高效的查找与创建
FNameBuffer
对字符串进行比较或计算处理时,临时使用的结构体类型
1 | union FNameBuffer |
FNameEntry
用于在内存分配器中最终存储Name字符串
1 |
|
FNameEntryAllocator
一个分块存储的内存分配器,首先开辟空间并记录在在Blocks[0],当Blocks[0]存满后,继续开辟空间记录在Blocks[1],依次类推。
1 | mutable FRWLock Lock; |
FNameEntryHandle
用于记录FNameEntryAllocator中的某一个FNameEntry的地址
1 | uint32 Block = 0;//FNameEntry所在的内存块 |
FNameEntryId
用一个uint32记录FNameEntry在内存分配器中的地址
1 | uint32 Value;//将FNameEntryHandle中的两个数压缩到32位中存储,0-15位表示Offset,16-28位表示Block |
FName
1 | //FNameEntryId作为字符串的唯一标识符 |
FNameHash
将字符串哈希为 64 位,用于确定分片和插槽索引
1 | uint32 ShardIndex;//Hash值的高32位值,FNameComparisonValue对应的FNamePoolShard索引 |
FNameHelper
1 | //纯函数类 |
FNamePool
1 | FNameEntryAllocator Entries; |
FNamePoolShard
1 | //继承FNamePoolShardBase,子类无数据 |
FNamePoolShardBase
1 | mutable FRWLock Lock; |
FNameSlot
用于记录FNameEntryId,以及字符串hash值中的3比特位(用于加快查找)
1 | static constexpr uint32 EntryIdBits = FNameMaxBlockBits + FNameBlockOffsetBits; |
FNameStringView
一种通用的数据格式,用于包裹WIDECHAR或ANSICHAR字符串
1 | union //存储实际的字符串 |
FNameValue
包含完整的字符串与对应的Hash值
1 | FNameStringView Name; //实际的字符串 |
FWideStringViewWithWidth
仅用于包裹WIDECHAR字符串
1 | const WIDECHAR* Str;//原始的字符串 |
典型的构造方法
默认构造方法
FName():将成员初始化为0
FName(ENoInit):不执行成员初始化
常规的文本构造流程
FName(const WIDECHAR* Name, EFindName FindType=FNAME_Add)
WIDECHAR转换为FWideStringViewWithWidth类型,并记录对应的数据。
1
static FWideStringViewWithWidth MakeUnconvertedView(const WIDECHAR* Str)
解析字符串,将Name_1格式的数据解析成的数字部分与字符部分,若无数字则解析为0。
1
2template<typename ViewType>
static FName MakeDetectNumber(ViewType View, EFindName FindType)若不是真正的宽字符编码则转换为ANSI编码,将转码完成的数据更换为FNameStringView存储,以便进一步压缩空间。
1
static FName MakeWithNumber(const FWideStringViewWithWidth View, EFindName FindType, int32 InternalNumber)
通过FNamePool完成数据存储,返回DisplayId与ComparisonId。
1
static FName Make(FNameStringView View, EFindName FindType, int32 InternalNumber)
- 若是第一次使用FNamePool,则需要先执行构造函数
FNamePool()
。- 建议先查看FNamePool成员含义
- 初始化DisplayShards与ComparisonShards的每一个元素,分配Slots内存。查看FNamePoolShardBase
- 将UnrealNames.inl中的UE常用FName调用Store()函数完成数据存储。
- 将默认常用FName的FNameEntryId保存到ENameToEntry,用于快速根据EName查找FName,同时封装FNameEntryId到EName的映射表到EntryToEName。便于EName与FName的互相查找。
- 接下来执行
Store()
函数完成数据存储- 将字符串转换为小写,并使用CityHash算法完成Hash计算,将结果保存于FNameValue中。
- 根据Hash值,向对应的预分配好的ComparisonShards中插入元素,调用Insert方法最终返回FNameEntryId作为DisplayId。
- 根据字符串Hash值查找FNamePoolShard中对应位置的FNameSlot
- 若hash值对应的桶中已存在字符串,则依次比较哈希值、字符串长度与类型、字符串原始数据,若完全相同则直接返回。
- 若字符串不完全相同,仅仅是普通的hash冲突,则循环查找桶的下一个位置。
- 字符串若已存在,直接返回即可,否则根据FNameValue在ComparisonShards中生成FNameSlot。
- 使用FNameEntryAllocator内存分配器分配一块空间,将空间的位置信息保存到FNameEntryHandle
- 在刚才分配的空间中按照FNameEntry格式记录原始的字符串数据。
- 将最终的FNameEntryId转换为FNameSlot格式并记录在Slots。
- 若Slots存满则倍增扩容。
- 若是第一次使用FNamePool,则需要先执行构造函数
根据DisplayId、ComparisonId、后缀数字完成FName构造。
1
return FName(ComparisonId, DisplayId, InternalNumber);
通过EName构造
FName(EName Ename, int32 InNumber):EName直接通过FNamePool中ENameToEntry缓存是数据返回ComparisonId。
总结
内存布局
- 使用FName存储字符串索引与字符串后缀。
- 在FNamePool中按照字符串的高位hash值分桶到FNamePoolShard。
- 在FNamePoolShard中按照字符串的低位hash值分桶到FNameSlot。
- 使用FNameEntryAllocator分配内存存储字符串,并将对应的索引保存到刚才查找到的FNameSlot中。
特点
- 高效查找与比较
- 特定类型字符串的高效存储(字符串复用、字符串空间精密排布)
- 不支持删除