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

索引

通过索引可以快速查找元素,例如:在代码库中,查找包含某个单词或某个方法的文件。插件开发者可以使用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开发之闹钟的实现代码

    下面是“Java开发之闹钟的实现代码”完整攻略: 一、准备工作 确定闹钟的功能需求,如:设定时间,响铃提示等; 选定合适的Java开发IDE,如Eclipse或IntelliJ IDEA; 确定使用的Java版本,本项目中使用Java 8。 二、项目搭建 新建Java项目,并创建一个Clock类; 创建一个定时器Timer,并设定定时任务,如下: timer…

    Java 2023年5月19日
    00
  • Java 构造方法的使用详解

    Java 构造方法的使用详解 什么是构造方法? 构造方法是一种特殊的方法,它在创建对象时被调用。在 Java 中,每个类都有至少一个构造方法,如果在类中没有定义构造方法,Java 会提供一个默认的构造方法。 使用构造方法的主要好处是可以确保对象在创建时就被初始化,并且避免了对象创建后状态不确定的情况。 构造方法的语法 构造方法的语法格式如下: [public…

    Java 2023年5月19日
    00
  • 微信小程序webSocket的使用方法

    接下来我将详细讲解微信小程序中使用WebSocket的方法。主要分为以下几个步骤: 1. 引入WebSocket API 在小程序页面js文件里,需要引入WebSocket API,代码如下: // 引入WebSocket API const socket = require(‘../../utils/websocket.js’) 其中websocket.j…

    Java 2023年5月23日
    00
  • 深入JAVA对象深度克隆的详解

    深入JAVA对象深度克隆的详解 对象深度克隆是指克隆一个对象及其子对象,同时新对象与原对象是互不干扰的,对新对象的任何修改都不会影响原对象。在Java中,实现对象深克隆通常使用“序列化”和“反序列化”技术。本篇文章将详细讲解如何实现Java对象的深度克隆。 实现Java对象深度克隆 方法一:序列化与反序列化实现克隆 序列化对象可以用ObjectOutputS…

    Java 2023年5月26日
    00
  • 深入Ajax代理的Java Servlet的实现详解

    “深入Ajax代理的Java Servlet的实现详解”是一篇介绍如何使用Java Servlet实现Ajax代理的文章。本文一共分为以下几个部分: Ajax代理的概念及作用 Java Servlet的基础知识 使用Java Servlet实现Ajax代理的步骤 示例说明 1. Ajax代理的概念及作用 Ajax代理是一种通过服务器中转Ajax请求的技术。在…

    Java 2023年6月16日
    00
  • java对象初始化代码详解

    Java对象初始化代码详解 在Java中,创建一个对象时需要初始化其各个属性,保证在其它地方使用时能够正常执行。对象初始化通常包含在构造函数中,而构造函数是一个特殊的方法,其名称与类名相同,用于创建对象并初始化。 该文将从以下几个方面详细讲解Java对象初始化代码的实现。 对象属性初始化 在Java中,为了保证对象能够正常使用,需要对其各个属性进行初始化。J…

    Java 2023年5月23日
    00
  • java连接orcale数据库示例分享

    下面是详细的攻略。 Java连接Oracle数据库 准备工作 在开始之前,我们需要确保已经完成以下准备工作: 安装Oracle数据库:在官网中下载并安装Oracle数据库,安装完成后配置好环境变量。 下载Oracle JDBC驱动:在官网中下载对应版本的JDBC驱动,将其复制到Java项目中。 示例1:使用JDBC API进行数据库操作 以下是连接Oracl…

    Java 2023年6月1日
    00
  • 在IDEA中安装MyBatis Log Plugin插件,执行mybatis的sql语句(推荐)

    接下来我将详细讲解在IDEA中安装MyBatis Log Plugin插件的过程及使用方法。 步骤一:安装MyBatis Log Plugin插件 打开IDEA,从菜单栏选择“File” > “Settings”(或者使用快捷键“Ctrl + Alt + S”)。 在弹出的窗口中选择“Plugins”,然后点击“Browse repositories”…

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