索引和PSI存根 (Indexing and PSI Stubs)

yizhihongxing

索引

通过索引可以快速查找元素,例如:在代码库中,查找包含某个单词或某个方法的文件。插件开发者可以使用IDE已有的索引来构建和使用自己的索引。

有以下2种索引:

  • 文件索引 :基于文件内容构建的索引。通过该索引可以直接搜索到符合指定条件的文件

  • Stud索引 :基于序列化Stub trees 构建。 Stub tree 是PSI tree 的子集,只包含PSI tree中外部可见的声明,以二进制格式存储。该索引可以搜索符合指定条件的PSI元素集。

提示

PSI tree: PSI树,例如在java类中,java类会被解构为一个一个的PsiElement(PSI元素),所有的PsiElement构建为一个PSI树,可以参考文档 PSI元素

提示

可以使用 Index Viewer

插件查看索引的内容和属性。

 

# Dumb mode

索引在后台进程中进行构建,在构建索引的过程中,DumbService

限制了 只能使用 文本编辑或版本控制等不需要使用索引的功能 ,如果调用了需要使用索引的功能,会抛出 IndexNotReadyException

异常。

通过DumbService 可以获取当前项目处于 dumb模式(该模式下不能访问索引)还是 smart模式(该模式下,索引构建完成,可以使用索引),也可以等待项目处于smart模式时再执行操作。

 
    //判断项目处于什么模式下
    // dumb模式(该模式下不能访问索引)
    //  smart模式(该模式下,索引构建完成,可以使用索引)
    DumbService.getInstance(project).isDumb();
    DumbService.isDumb(project);
    
    //等待项目处于 smart模式 时,再执行业务操作
    DumbService.getInstance(project).runWhenSmart(()->{//业务逻辑});
 

# Gists

有时候会碰到以下情况:

  • 不需要使用文件索引,只需要计算文件内容的相关数据,并将数据缓存到硬盘上

  • 不需要在索引构建期间执行计算(例如:它会拖慢索引的构建,或者只有一小部分文件曾经需要这些计算数据)

  • 可以根据请求延迟重新计算数据,而不会造成明显的性能损失。

大部分情况下可以使用 文件索引 ,但是通过 gists的API 可以延迟计算,并缓存到硬盘上, 可以参考 VirtualFileGist

PsiFileGist

的文档。

示例:

 

  • 获取Java类的简单属性

# 提高索引性能

# 性能指标

在2020.2及以后的版本中,索引的性能指标以json格式存储在了沙盒中的logs文件夹中,从2021.1版本开始,以HTML格式存储了另外的一些指标,参考下图:

索引和PSI存根 (Indexing and PSI Stubs)

沙盒路径查看文档:插件开发项目配置

# 避免使用AST

如果可能,尽量使用词法分析器信息而不是解析树。 如果不可能,请使用不会占用大量内存的经量级AST(LighterAST) ,这样可以提升遍历速度。可以通过将传过来的 FileContent参数 转换为 PsiDependentFileContent 然后调用 getLighterAST() 来获取 LighterAST,使用 LighterASTNodeVisitor

LightTreeUtil

来遍历自己需要用到的节点,

继承 LightStubBuilder

来实现轻量级 Stub 索引

If a custom language contains lazy-parseable elements that never or rarely contain any stubs, consider implementing StubBuilder.skipChildProcessingWhenBuildingStubs()

(preferably using Lexer/node text).

考虑使用 NanoXmlUtil

来索引 xml 文件

# 预创建Stubs

如果你使用的编程语言有大量的对所有用户都一样的组件库,你可以注册 PrebuiltStubsProvider

扩展 来预创建Stubs索引,避免每次安装都需要构建Stubs索引。

 文件索引

 

文件索引基于Map/Reduce

的数据结构。每一个索引都有一个指定类型的key,一个指定类型的value.

示例:word索引中,key就是word自己。value是跟key关联的,包含了word的context的对象(code, string literal, 或 comment)

在索引中,如果获取索引中某key的value的 type 等于 Void ,则表示不存在跟key相关的数据。

当一个索引实现类对一个文件创建了索引,那么这个索引实现类会收到该文件的内容,并返回一个Map, Map包含了该文件的索引key,及跟key相关的value

访问索引的时候,传入key,就能获取到跟key相关的所有文件。

提示

某些情况,可以不使用索引,而使用 Gist

# 实现文件索引

是一个相对简单的索引示例,它存储了 GUI Designer

.form中使用的class的全限定命令

注册索引实现类,如下:

 
<!--在plugin.xml里注册索引实现类-->
 <extensions defaultExtensionNs="com.intellij">
        <fileBasedIndex implementation="com.intellij.util.indexing.FileBasedIndexExtension 的子类"></fileBasedIndex>
 </extensions>
 

实现 com.intellij.util.indexing.FileBasedIndexExtension

分为以下几部分:

  • getIndexer() :返回 DataIndexer

  • ,它负责根据文件内容构建索引。

  • getKeyDescriptor() :返回以二进制的格式存储key的 KeyDescriptor

,最常用的是 KeyDescriptor的子类 EnumeratorStringDescriptor

  • ,它能提升存储效率。

  • getValueExternalizer() :返回以二进制格式存储value的 DataExternalizer

getInputFilter() :只过滤自己需要的文件。可以使用 DefaultFileTypeSpecificInputFilter

  • getName() :返回唯一的索引ID,推荐使用类的全限定命名,防止冲突,例如可以使用:com.example.myplugin.indexing.MyIndex

  • getVersion() :索引的版本。如果当前使用的索引版本和实现类的版本不一致,将会自动重新构建索引

如果获取到的value的type 等于 Void ,则表示不存在该文件。

通过继承 ScalarIndexExtension

可以实现简单的索引。如果索引的Value只有一个,可以继承 SingleEntryFileBasedIndexExtension

来实现

警告

为了保证相同的Value只存储一个,所有索引Value的实现类必须重写 equals() 和 hashCode() 方法。

通过 DataIndexer.map() 方法返回的数据必须只依赖用户输入的参数,不能依赖任何外部元素,否则,你的索引将不能实时更新

在开发的时候,debug索引时,可以把 intellij.idea.indices.debug/intellij.idea.indices.debug.extra.sanity 设置为true

# 访问文件索引

提示

注意:在项目处于 Dumb mode 时,不能访问索引

通过FileBasedIndex

来访问文件索引,主要包括以下操作:

  • getAllKeys()processAllKeys() 方法可以获取到项目的文件所有的key。可以让 FileBasedIndexExtension.traceKeyHashToVirtualFileMapping() 方法返回 true ,来优化性能

提示

保证能返回最新项目中所有的key,也可能有最新项目中不包含的key(项目更新时,会更新索引,但不会删除老索引)。

  • getValues() :获取跟Key有关的所有值,但是不包含值所在的文件

  • getContainingFiles() :获取包括Key的所有文件

  • processValues() :可以遍历和访问跟Key有关的所有文件

警告

索引不支持嵌套访问,可能会引起死锁。应该先从索引A中获取到所有结果,然后再在索引B中使用索引A的结果。

# 标准索引

IntelliJ Platform 提供了常用的索引,如下:

# Word Index

通常使用 PsiSearchHelper

来访问

# File Name Index

FilenameIndex

可以快速的根据文件名称查找文件

# File Type Index

FileTypeIndex

可以快速的根据文件类型查找文件

# 向原有的索引根中添加索引

继承 IndexableSetContributor

,并在plugin.xml里注册,如下:

 
  <extensions defaultExtensionNs="com.intellij">
       <indexedRootsProvider implementation="com.intellij.util.indexing.IndexableSetContributor 实现类"></indexedRootsProvider>
    </extensions>
 

Stub Trees

Stub Tree是PSI tree的子集,以紧凑的二进制格式进行存储。PSI tree有2种格式,一种是AST(Abstract Syntax Tree:抽象语法树,解析文件构建而成),别一种是从硬盘上反序列化的stub tree , 这2种格式可以互相切换。

Stub Tree只包含一部分节点。通常,它只包含外部文件有访问权限的(public protected default修饰的) 的节点。如果访问Stub Tree不包含的节点,或访问像 PSI element的文本Stub tree不支持的操作,都会引起文件的解析由PSI解析切换到 AST backing。

Stub Tree中的每个stub都是一个没有任何行为的bean class 。stub存储了相关PSI Element 相关信息的子集(例如名称,public,final等),还能获取到它的父子stubs

当你想要 stubs 支持你自定义的编辑语言时,你需要确定PSI tree 中的哪些元素需要被存储进 Stubs。通常,Stubs 需要存储外部文件有访问权限的(public protected default修饰的) 方法和成员变量,不需要存储外部文件无访问权限的局部变量,private修改的方法和变量等元素

# 实现Stub索引

一个编程语言想要支持stubs ,需要执行以下几步:

  • 改变编辑语言中 ParserDefinition.getFileNodeType() 返回的 Element type 为 自定义的IStubFileElementType

  • 的子类

  • plugin.xml 里,注册 com.intellij.stubElementTypeHolder 扩展。示例:JavaPsiPlugin.xml

中注册的 JavaStubElementTypes

  • ,摘要如下:

 
  <extensions defaultExtensionNs="com.intellij">
        <!--  JavaStubElementTypes 是一个接口, 包含了 编程语言解析器所用到的所有 IElementType 常量  -->
        <!--  externalIdPrefix:编辑语言中所有stub element type使用的公共前缀  -->
        <stubElementTypeHolder class="com.intellij.psi.impl.java.stubs.JavaStubElementTypes" externalIdPrefix="java."/>
    </extensions>
 

对于要存储在Stub tree中的每种Element type ,你需要参考下面示例的实现步骤:

  1. 定义 stub 接口 PropertyStub

,该接口需要继承StubElement

  • 定义实现类PropertyStubImpl

  • ,该接口需要实现上一步定义的接口

  • 定义 PSI Element接口 Property

, 该接口需要继承泛型为PropertyStub(第一步定义的接口)StubBasedPsiElement

,该类需要实现 Property接口(第3步定义的接口) ,同时也要继承泛型为PropertyStub(第一步定义的接口)StubBasedPsiElementBase

  • 接口。另外该实现类需要提供2个构造器,一个参数为 ASTNode 的构造器,和一个参数为 PropertyStub(第一步定义的接口) 的构造器 。

  • 定义 PropertyStubElementType

  • ,该类需要继承泛型为 PropertyStub(第一步定义的接口)Property接口(第3步定义的接口)IStubElementType接口 (ILightStubElementType<PropertyStub, Property> ),实现创建PSI的 createPsi() 方法和创建stub的createStub() 方法,实现用于序列化存储serialize() 方法和反序列化的deserialize() 方法

  • 解析代码的时候,使用 IStubElementType 的子类 PropertyStubElementType(第5步定义的) 作为 element type 常量。参考示例 PropertiesElementTypes

实现 Property.getKey()(示例:PropertyImpl.getKey()

  1. )方法, 确保 PSI element 接口中的所有方法在适当的时候访问stub数据 而不是 PSI tree

提示

如果使用 Grammar-Kit

来生成编程语言的 PSI ,可以通过 Stub indices support

文档了解如何让你的语法整合 stub

默认情况下,所有继承了 StubBasedPsiElementPSI element都会存到 stub tree 。如果不需要存储某类型元素,可以在该类型元素中重写 IStubElementType.shouldCreateStub() 方法并返回 false ,这个策略只作用于元素自身,如果该PSI元素的子元素继承了 StubBasedPsiElement,该子元素依然会存储到stub tree 中。

# 序列化数据

推荐使用 StubOutputStream.writeName()StubInputStream.readName() 方法来序列化元素名称等String类型的数据。这些方法可以保证在数据流中同一个数据只存储一次,从而减少 stub tree的占用空间。也可以参考 DataInputOutputUtil

如果需要改变存储Stub的二进制格式(例如:想存储额外的数据或一些新元素),你需要升级通过 IStubFileElementType.getStubVersion() 获取到的 stub 版本。版本升级后,为了避免 Stub 和项目的代码不匹配,会重新构建Stubs和Stub索引

必须要保证在 stub tree 中存储的数据只依赖于stub的文件的内容,不依赖于任何外部文件。这是因为外部文件更新后,stub tree 并不会重新构建,从而导致stub tree 中数据不准确。

# Stub索引

在构建 stub tree 的同时,可以把 stub elements 的相关数据存储到其它索引里,这些索引就可以用来查找 PSI elements 。和文件索引不一样,stub索引的value不支持存储自定义数据,只会存储 PSI elements 。stub索引中的key一般都是String类型(例如使用类名),如果需要,也可以使用其它类型。

stub index的类必须继承 AbstractStubIndex

。但是,大多数情况下,如果 stub index 的key的类型是string,可以通过继承 StringStubIndexExtension

来实现 ,然后在 plugin.xml 里注册为 com.intellij.stubIndex 类型的扩展。

向某个Stub索引中存储数据,需要实现 IStubElementType.indexStub() 方法 (示例:JavaClassElementType.indexStub()

)。该方法接收 IndexSink 参数,并存储每一个需要存储的元素的key和 索引id

可以使用下面2个方法来访问stub索引:

  • AbstractStubIndex.getAllKeys() :可以获取指定索引,指定项目的所有key (例如:获取项目中所有类的名称)

  • AbstractStubIndex.get() :可以获取指定作用域,指定key(key通常为类名)的 PSI elements集合

# 相关论坛

Lifecycle of stub creation

 

原文链接:https://www.cnblogs.com/qwop/p/17384236.html

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:索引和PSI存根 (Indexing and PSI Stubs) - Python技术站

(0)
上一篇 2023年5月9日
下一篇 2023年5月9日

相关文章

  • 使用Java将字符串在ISO-8859-1和UTF-8之间相互转换

    首先,我们需要了解一下ISO-8859-1和UTF-8。 ISO-8859-1是一种字符编码,能够表示大部分欧洲语言的字符。在ISO-8859-1中,每个字符占据一个字节,使用1个字节来表示一个字符。然而,ISO-8859-1不能表示非欧洲语言的字符,比如中文、日文等。 而UTF-8则是一种Unicode字符编码,能够表示世界上的所有字符。UTF-8使用1到…

    Java 2023年5月20日
    00
  • 基于Spring Data Jest的Elasticsearch数据统计示例

    我来为你详细讲解“基于Spring Data Jest的Elasticsearch数据统计示例”的完整攻略。 一、前言 在讲解具体实现之前,我们需要先了解一些背景知识。Elasticsearch 是目前非常流行的一个开源搜索引擎,具有高速、高伸缩性、分布式、全文搜索、分词等特点,它是基于 Apache Lucene 的实现,使用 Java 开发。Spring…

    Java 2023年5月20日
    00
  • JDBC板块精华整理20051226

    首先,“JDBC板块精华整理20051226”是一份关于Java数据库连接技术的精华整理资料,它详细介绍了JDBC的基本概念、用法和常见问题解决方法。以下是该攻略的完整内容: JDBC概述 JDBC(即Java Database Connectivity)是一套用于Java编程语言与各种类型的数据库进行连接和操作的API规范。它提供了一个标准的Java接口,…

    Java 2023年6月15日
    00
  • java8 统计字符串字母个数的几种方法总结(推荐)

    Java8 统计字符串字母个数的几种方法总结(推荐) 在Java8中,有许多快捷方法可以用来计算字符串中的字母个数。下面总结了几种使用Java8进行字符串字母统计的方法。 方法1:使用filter和count方法 可以使用Java8的Stream API中的Filter和Count方法来计算一个字符串中字母的数量。示例代码如下: String str = &…

    Java 2023年5月27日
    00
  • Spring Data JPA踩坑记录(@id @GeneratedValue)

    请允许我简单的介绍一下Spring Data JPA以及相关注解。 Spring Data JPA是Spring Framework中一个比较常用且易用的持久层框架,它允许我们使用JPA进行数据库访问操作,简化了数据库操作的代码,在项目的开发中更加高效便捷的实现了基础的CRUD操作。 相关注解有两种,@Id用于标识某个属性为实体类的主键,而@Generate…

    Java 2023年5月20日
    00
  • springboot之配置双kafka全过程

    下面是Spring Boot配置双Kafka全过程的攻略: 1. 添加Kafka依赖 在pom.xml文件中添加以下Kafka依赖: <dependency> <groupId>org.springframework.kafka</groupId> <artifactId>spring-kafka</ar…

    Java 2023年5月20日
    00
  • JAVA基础-GUI

    JAVA基础-GUI攻略 1. GUI概述 GUI即图形用户界面(Graphical User Interface),是用户与操作系统的交互界面。在Java中,使用Java Swing和JavaFX等框架来编写GUI应用程序。 Swing是一套Java原生的GUI控件,可以在几乎所有的Java平台上运行。JavaFX是Java平台的一个富客户端平台,提供了可…

    Java 2023年5月19日
    00
  • SpringMVC+MyBatis分页(最新)

    以下是关于“SpringMVC+MyBatis分页(最新)”的完整攻略,其中包含两个示例。 1. 前言 在Web应用程序中,分页是一种常见的需求。在SpringMVC和MyBatis中,可以使用PageHelper插件来实现分页。本攻略将详细讲解如何使用SpringMVC和MyBatis实现分页。 2. 添加PageHelper依赖 在使用PageHelpe…

    Java 2023年5月16日
    00
合作推广
合作推广
分享本页
返回顶部