如何基于UE4 IPlatformFile以特殊遍历顺序构建文件系统链表结构
用链表结构体表示UE4文件系统(适配逆深度优先字母序遍历)
嘿,我正好对UE4的IPlatformFile遍历和链表映射文件系统这块熟得很,来给你一步步拆解清楚!首先得把两个核心点拎明白:逆深度优先字母序的遍历规则,以及如何用链表结构体精准映射这个遍历出来的文件系统。
先搞懂遍历规则:逆深度优先+字母序
首先得明确这个特殊遍历的输出顺序,不然链表结构就无从谈起。举个实际目录的例子:
ProjectRoot/ ├─ Assets/ │ ├─ Models/ │ │ ├─ Chair.fbx │ │ └─ Table.fbx │ └─ Textures/ │ ├─ Brick.png │ └─ Wood.jpg └─ Config/ └─ DefaultEngine.ini
按照逆深度优先字母序的规则,遍历顺序应该是:Wood.jpg → Brick.png → Textures/ → Table.fbx → Chair.fbx → Models/ → Assets/ → DefaultEngine.ini → Config/ → ProjectRoot/
简单说就是:
- 同层级的节点(文件/文件夹)按字母逆序排列(比如Wood在Brick前面,Table在Chair前面)
- 遍历顺序是先处理最深层的叶子节点,再回溯处理父文件夹(典型的后序遍历+逆序调整)
链表结构体的设计思路
因为每次遍历只能拿到「完整路径」和「是否为文件夹」这两个信息,我们的链表节点需要存储这些基础信息,同时还要能体现文件系统的层级关系(父/子节点关联)。直接上贴合UE4风格的C++结构体:
#include "CoreTypes.h" #include "Containers/LinkedList.h" struct FSFileSystemNode { // 存储文件/文件夹的完整绝对路径 FString FullPath; // 标记当前节点是文件夹(true)还是文件(false) bool bIsDirectory; // 父节点指针:用于快速回溯到上一级目录 FSFileSystemNode* ParentNode; // 子节点链表:如果需要构建完整的树状结构,用来存放当前目录下的所有子节点 TLinkedList<FSFileSystemNode*> ChildNodes; // 构造函数方便初始化 FSFileSystemNode() : bIsDirectory(false), ParentNode(nullptr) {} };
这个结构的好处是:既可以按遍历顺序形成一条线性链表,又能通过ParentNode和ChildNodes快速构建出完整的文件系统树。
结合IPlatformFile实现遍历+链表构建
UE4的IPlatformFile提供了IterateDirectory(遍历当前目录)和IterateDirectoryRecursively(递归遍历),但后者是常规的深度优先,不符合我们的逆序需求。所以我们需要自己实现逆深度优先的遍历逻辑,同时把遍历到的节点逐个加入链表。
核心实现代码
#include "PlatformFilemanager.h" #include "Paths.h" void BuildReverseDFSLinkedList(const FString& RootPath, TLinkedList<FSFileSystemNode*>& OutLinkedList) { IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile(); // 获取当前目录下的所有条目,自动排除.和.. TArray<FString> DirectoryEntries; if (!PlatformFile.IterateDirectory(RootPath, DirectoryEntries, /*bIncludeHidden=*/false, /*bSkipSpecialDirs=*/true)) { UE_LOG(LogTemp, Warning, TEXT("Failed to iterate directory: %s"), *RootPath); return; } // 对条目按字母逆序排序(忽略大小写,适配跨平台) DirectoryEntries.Sort([](const FString& A, const FString& B) { return A.Compare(B, ESearchCase::IgnoreCase) > 0; }); for (const FString& EntryName : DirectoryEntries) { const FString FullEntryPath = FPaths::Combine(RootPath, EntryName); const bool bIsDir = PlatformFile.DirectoryExists(FullEntryPath); if (bIsDir) { // 逆深度优先核心:先递归遍历子目录的所有内容,再把当前文件夹节点加入链表 BuildReverseDFSLinkedList(FullEntryPath, OutLinkedList); // 创建文件夹节点并添加到链表头部(因为递归先处理子节点,头部添加能保证顺序正确) FSFileSystemNode* DirNode = new FSFileSystemNode(); DirNode->FullPath = FullEntryPath; DirNode->bIsDirectory = true; OutLinkedList.AddHead(DirNode); } else { // 文件直接创建节点并添加到链表头部 FSFileSystemNode* FileNode = new FSFileSystemNode(); FileNode->FullPath = FullEntryPath; FileNode->bIsDirectory = false; OutLinkedList.AddHead(FileNode); } } }
补全父节点关联
上面的代码已经按顺序构建了链表,但节点之间还没有父/子关联。我们可以遍历一次链表,通过路径解析找到父节点并建立关联:
void LinkParentChildNodes(TLinkedList<FSFileSystemNode*>& NodeList) { // 先把所有节点存入路径映射表,方便快速查找 TMap<FString, FSFileSystemNode*> PathToNodeMap; for (FSFileSystemNode* Node : NodeList) { PathToNodeMap.Add(Node->FullPath, Node); } // 遍历每个节点,找到对应的父节点并关联 for (FSFileSystemNode* Node : NodeList) { const FString ParentDirPath = FPaths::GetPath(Node->FullPath); if (PathToNodeMap.Contains(ParentDirPath)) { Node->ParentNode = PathToNodeMap[ParentDirPath]; Node->ParentNode->ChildNodes.AddTail(Node); } } }
使用示例与注意事项
调用方式
// 初始化链表 TLinkedList<FSFileSystemNode*> FileSystemList; // 构建链表 BuildReverseDFSLinkedList(FPaths::ProjectDir(), FileSystemList); // 关联父/子节点 LinkParentChildNodes(FileSystemList); // 遍历链表验证顺序 for (FSFileSystemNode* Node : FileSystemList) { UE_LOG(LogTemp, Log, TEXT("%s (%s)"), *Node->FullPath, Node->bIsDirectory ? TEXT("Dir") : TEXT("File")); } // 记得用完后释放内存,避免泄漏! for (FSFileSystemNode* Node : FileSystemList) { delete Node; } FileSystemList.Empty();
关键注意事项
- 内存管理:因为用了
new动态分配节点,一定要在使用完后遍历链表释放内存,UE4的TLinkedList不会自动管理节点内存。 - 跨平台兼容性:排序时用
ESearchCase::IgnoreCase,因为Windows文件系统不区分大小写,而Linux/macOS区分,这样能保证遍历顺序一致。 - 特殊目录处理:
IterateDirectory的bSkipSpecialDirs参数设为true,自动跳过.和..,避免无效节点。
内容的提问来源于stack exchange,提问作者RectangleEquals




