IntelliJ 插件开发:索引与 PSI 存根机制
索引框架是 IDE 高效检索代码的关键机制,它允许开发者快速定位特定元素,例如在大型代码库中查找包含特定单词或具有特定名称的方法的文件。插件开发者既可以使用 IDE 自身构建的现有索引,也可以根据需求构建和使用自定义索引。
目前主要支持两种索引类型:
- 文件索引 (File-based Index)
- 存根索引 (Stubs Index)
文件索引直接建立在文件内容之上,而存根索引则建立在序列化的存根树之上。源文件的存根树是其 PSI 树的子集,仅包含外部可见的声明,并以紧凑的二进制格式序列化。
查询文件索引可获得匹配条件的文件集,而查询存根索引则能获取一组匹配的 PSI 元素。因此,自定义语言插件开发人员通常更倾向于在实现中使用存根索引。
Dumb 模式处理
编制索引往往耗时较长,且通常在后台执行。在此期间,IDE 的功能会受到限制,仅保留基本文本编辑、版本控制等不依赖索引的操作。
核心服务类为 DumbService,它提供了 API 来查询 IDE 当前是处于'哑'模式(不允许访问索引)还是'智能'模式(所有索引已构建完毕)。此外,它还支持在索引准备好之前延迟代码执行。
创建索引时需注意以下限制:
- 若不需要基于文件的索引聚合功能,仅需根据特定文件内容计算数据并缓存至磁盘,可考虑使用轻量级方案。
- 避免在索引期间急切地计算整个项目的数据,这会显著拖慢索引速度,且部分文件可能永远不需要这些数据。
- 数据计算应支持按需延迟重新计算,以免性能受损。此时可使用
VirtualFileGist或PsiFileGist等工具。
例如,VirtualFileGist 可用于计算 UI 特定部分显示的图像尺寸或位深度;PsiFileGist 则在 Java 中提供简单的属性支持。
文件索引实现
文件索引采用 Map/Reduce 架构设计。每个索引都有特定的键和值类型,具体实现需继承 FileBasedIndexExtension 类并通过 com.intellij.fileBasedIndex 扩展点注册。
一个标准的文件索引实现包含以下关键部分:
- getIndexer():返回
DataIndexer实例,负责根据文件内容构建键值对。 - getKeyDescriptor():返回
KeyDescriptor,负责比较密钥并以二进制格式存储。最常用的是EnumeratorStringDescriptor,专为高效存储标识符设计。 - getValueExternalizer():返回
DataExternalizer,负责以二进制格式存储值。 - getInputFilter():限制索引范围至特定文件组,推荐使用
DefaultFileTypeSpecificInputFilter。 - getName():返回唯一的索引 ID。
- getVersion():返回索引实现的版本。若当前版本与构建时的版本不一致,系统会自动重建索引。
如果文件没有关联的值(即值类型为 Void),可扩展 ScalarIndexExtension;若每个文件仅有一个值,则可扩展 SingleEntryFileBasedIndexExtension。
访问文件索引
通过 FileBasedIndex 类访问文件索引,其常用方法包括:
- getAllKeys() / processAllKeys():获取指定项目中找到的所有键列表。为优化性能,建议让
FileBasedIndexExtension.traceKeyHashToVirtualFileMapping()返回 true。 - getValues():获取与特定键关联的所有值,但不包含文件信息。

