You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何基于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/

简单说就是:

  1. 同层级的节点(文件/文件夹)按字母逆序排列(比如Wood在Brick前面,Table在Chair前面)
  2. 遍历顺序是先处理最深层的叶子节点,再回溯处理父文件夹(典型的后序遍历+逆序调整)

链表结构体的设计思路

因为每次遍历只能拿到「完整路径」和「是否为文件夹」这两个信息,我们的链表节点需要存储这些基础信息,同时还要能体现文件系统的层级关系(父/子节点关联)。直接上贴合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) {}
};

这个结构的好处是:既可以按遍历顺序形成一条线性链表,又能通过ParentNodeChildNodes快速构建出完整的文件系统树。


结合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();

关键注意事项

  1. 内存管理:因为用了new动态分配节点,一定要在使用完后遍历链表释放内存,UE4的TLinkedList不会自动管理节点内存。
  2. 跨平台兼容性:排序时用ESearchCase::IgnoreCase,因为Windows文件系统不区分大小写,而Linux/macOS区分,这样能保证遍历顺序一致。
  3. 特殊目录处理IterateDirectorybSkipSpecialDirs参数设为true,自动跳过...,避免无效节点。

内容的提问来源于stack exchange,提问作者RectangleEquals

火山引擎 最新活动