SWE · Phone Coding

Snowflake 内存文件系统:路径树、ls、mkdir 与文件内容

两篇 Phone 面经都提到内存文件系统,其中一篇标 LC 588;本文用明确的训练接口处理目录树、路径冲突、文件追加和有序列举。

题目重点:两篇 Phone 面经都提到内存文件系统,其中一篇标 LC 588;本文用明确的训练接口处理目录树、路径冲突、文件追加和有序列举。

题意与假设

这道练习题保留了面经中值得训练的核心约束,并将需要澄清的条件单独列出。现场拿到变体时,先和面试官确认这些条件。

  • 路径必须是绝对路径;根为 /,其他路径不能有尾斜杠、重复斜杠、. 或 ..。文件和目录名字在同一父目录下互斥。
  • mkdir 递归创建缺失的目录;add_content 仅在父目录已存在时创建文件或追加文本;ls(文件) 返回该文件名,ls(目录) 返回字典序子项。
  • 缺失路径报 FileNotFoundError;路径中途遇文件也报错;根不能作为文件。该异常约定只是训练选择,面试须先确认。

解题路线

  1. 把文件系统建成以根 Directory 开始的树,目录保存 name→File/Directory 映射;切分路径时先验证格式,避免 /a//b、/a/../b 隐式归一造成冲突。
  2. _directory 从根逐段查找;mkdir 允许创建缺失段,其他操作只读取已有目录。中途碰到 File 就拒绝继续向下。
  3. ls 先定位父目录再看最后一个节点:文件返回自身名,目录对直接子项排序;read_content 只接受 File。
  4. add_content 查父目录,不存在则拒绝;叶子缺失时新建 File,存在 File 时追加,存在 Directory 时抛错;不把内容写进目录节点。
  5. 用独立的完整路径→类型/内容映射随机对照 100,000 次创建、追加、读取与列举,覆盖根、同名冲突、缺失父目录和排序。

手动走一遍

mkdir('/a/b') 后 add_content('/a/b/f','hi'),再追加 '!':read_content('/a/b/f') 为 'hi!';ls('/a/b') 为 ['f'],ls('/a/b/f') 为 ['f']。试图 mkdir('/a/b/f/x') 会因 f 是文件而失败。

Python 参考实现

下面的代码按上文假设实现,适合本地运行并追加自己的测试用例。

代码区可横向滚动;使用键盘时,先聚焦代码区再按方向键查看。

"""Training file system: ls, mkdir, append content, and read content."""


class Directory:
    def __init__(self):
        self.children = {}


class File:
    def __init__(self, content: str = ""):
        self.content = content


class FileSystem:
    def __init__(self):
        self.root = Directory()

    def _parts(self, path: str) -> list[str]:
        if not path.startswith("/") or (path != "/" and (path.endswith("/") or "//" in path)):
            raise ValueError("invalid absolute path")
        parts = [] if path == "/" else path[1:].split("/")
        if any(part in ("", ".", "..") for part in parts):
            raise ValueError("invalid path segment")
        return parts

    def _directory(self, parts: list[str], create: bool = False) -> Directory:
        node = self.root
        for part in parts:
            child = node.children.get(part)
            if child is None and create:
                child = Directory()
                node.children[part] = child
            if not isinstance(child, Directory):
                raise FileNotFoundError("directory missing or path is a file")
            node = child
        return node

    def ls(self, path: str) -> list[str]:
        parts = self._parts(path)
        if not parts:
            return sorted(self.root.children)
        parent = self._directory(parts[:-1])
        child = parent.children.get(parts[-1])
        if isinstance(child, File):
            return [parts[-1]]
        if isinstance(child, Directory):
            return sorted(child.children)
        raise FileNotFoundError(path)

    def mkdir(self, path: str) -> None:
        self._directory(self._parts(path), create=True)

    def add_content(self, path: str, text: str) -> None:
        parts = self._parts(path)
        if not parts:
            raise ValueError("root is not a file")
        parent = self._directory(parts[:-1])
        child = parent.children.get(parts[-1])
        if child is None:
            child = File()
            parent.children[parts[-1]] = child
        if not isinstance(child, File):
            raise IsADirectoryError(path)
        child.content += text

    def read_content(self, path: str) -> str:
        parts = self._parts(path)
        if not parts:
            raise IsADirectoryError(path)
        parent = self._directory(parts[:-1])
        child = parent.children.get(parts[-1])
        if not isinstance(child, File):
            raise FileNotFoundError(path)
        return child.content

复杂度

路径有 d 段、目标目录有 c 个直接子项,mkdir/read/add_content 平均 O(d) 字典查找;ls(目录) 为 O(d+c log c),ls(文件) 为 O(d)。树节点空间 O(目录与文件数+内容字符数);Python 字符串追加在最坏情况下会复制已有内容,若高频大文件追加可改成分块缓冲。

易错点与追问

容易失分的边界

  • 把文件当目录自动创建子项,破坏路径类型不变量。
  • ls 对文件返回空列表,或把整个子树都递归列出来。
  • 用操作系统路径归一函数悄悄接受 ..,却没有定义安全边界。
  • 每次列举都扫描全局路径 Map,规模大时成本随文件总数增长。
  • 称 Python 的 content += text 在所有追加模式下都有摊还 O(1) 时间。

继续深挖

  • 如果支持 rm/mv,如何处理非空目录、原子移动与环?
  • 并发读写时,目录级锁或复制快照如何保证 ls 一致性?
  • 百万文件且路径很深时,如何分页 ls、限制深度并持久化?
  • 如果题面允许自动建立 addContent 的父目录,代码哪些地方要改?
✓已复制微信号!