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

这篇具有很好参考价值的文章主要介绍了索引和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

的文档。

示例:

  • VirtualFileGist :使用 ImageInfoIndex

 

  • 计算图片的大小尺寸并显示到UI上。

  • PsiFileGist : 使用 JavaSimplePropertyGist

  • 获取Java类的简单属性

# 提高索引性能

# 性能指标

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

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

# 避免使用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

# 实现文件索引

提示

UI Designer bound forms index

是一个相对简单的索引示例,它存储了 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
  • 接口。

  • 定义实现类PropertyImpl

,该类需要实现 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.toymoban.com/news/detail-437188.html

 

到了这里,关于索引和PSI存根 (Indexing and PSI Stubs)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 关于使用BETWEEN AND 使索引失效的解决方法

    由于业务需要,需要使用between and 查询数据, 在查询数据条数约占总条数五分之一以下时能够使用到索引,但超过五分之一时,则使用全表扫描了。速度极慢。 解决办法(联合索引+强制使用索引)

    2024年02月14日
    浏览(40)
  • 面试题:为啥索引可以让查询变快?

    人类存储信息的发展历程大致经历如下: 由于是个人凭着自己理解总结的,因此可能不一定精确,但是毋庸置疑的是,在当代,各大公司机构部门的数据都是维护在数据库当中的。数据库作为数据存储介质发展的最新产物,必然是具有许多优点的,其中一个很大的优点就是存

    2024年02月02日
    浏览(36)
  • 2.2 Dubbo的基本应用-本地存根 、本地伪装 、参数回调

    官网地址:  http://dubbo.apache.org/zh/docs/v2.7/user/examples/local-stub/ 本地存根,   名字很抽象,   但实际上不难理解,   本地存根就是一   段逻辑,   这段逻辑是在服务消费端执行的   ,   这段逻辑一   般都是由服务提供者提供,   服务提供者可以利用这种机制在 服务消费者

    2024年02月11日
    浏览(31)
  • 通过es索引生命周期策略删除日志索引

    在es 7.x版本之后,多了个索引生命周期的概念,可以一系列的设置,给新生成的索引绑定生命周期策略,到期后,索引自动删除。 也可以通过linux定时任务实现,请查看另一篇文章《通过linux定时任务删除es日志索引》 创建索引生命周期策略 创建索引模板,与生命周期策略绑

    2024年02月09日
    浏览(49)
  • sql索引分析-插入了 a、b、c、d 四个字段作为索引,只要带上了a,那么任何排列的组合,都可以走索引。

    1、如果创建了一个索引   ALTER TABLE `table_A` ADD KEY `nid_sn_key`(`a`,`b`,`c`,`d`) USING BTREE; 第一种情况: explain SELECT * FROM `table_A` WHERE `a` = \\\"xxx\\\"; explain SELECT * FROM `table_A` WHERE `a` != \\\"xxx\\\"; 会走索引 第二种情况: explain SELECT * FROM `table_A` WHERE `b` = \\\"xxx\\\"; 不走索引 等等,如果单独查询c,

    2024年02月09日
    浏览(39)
  • PSI算法经典论文算法概述

    隐私求交是多方安全计算中的密码学技术,它允许数据持有方通过比较加密集合计算得到交集,且任何一方都不会获得其他信息。PSI还存在一种变体,即CS场景。客户端可以获取其与服务器的交集但是服务器无法学习到该集合。如果在一个小的可以预测的域上通过密码哈希比

    2024年02月12日
    浏览(45)
  • 完美解决idea一直indexing,无法操作的问题

    今天主要分享一下在使用idea 2020.3版本开发maven项目的时候,一直出现有效件index, 有时候是scaning indexing, 有时候是update indexing, indexing的时候,idea基本上就没办法操作了,连跳入到类或方法里都跳不了。不厌其烦。 于是开始在网上找解决方法: 得到的90%及以上的结果,都是

    2024年01月17日
    浏览(43)
  • 内网可以通过https来访问,外网不可以通过https来访问,怎么办

    如果您的内网可以通过HTTPS来访问,但外网无法通过HTTPS进行访问,可能有几种原因导致这个问题: 防火墙配置:请确保您的防火墙正确配置,以允许外部访问您的服务器的HTTPS端口(通常为443端口)。检查防火墙规则并确保已配置允许传入和传出的HTTPS流量。 网络地址转换(

    2024年02月07日
    浏览(44)
  • 单元测试探析:什么是Stubs、Mocks、Spies、Dummies?带你了解4个核心工具

    在单元测试中,对象之间的依赖往往交织到一起,需要拆成各个单元才能逐个击破,这也是单元测试的目的。如何将这些交织到一起的对象拆开,需要一些工具,这些工具业内人们称其为“测试替身”。 本文作者介绍了单元测试中的4个“测试替身”工具,即Stubs、Mocks,、Sp

    2024年02月16日
    浏览(39)
  • 『MySQL快速上手』-⑩-索引特性

    提高数据库的性能,索引是物美价廉的东西了。不用加内存,不用改程序,不用调sql,只要执行正确的 create index ,查询速度就可能提高成百上千倍。但是天下没有免费的午餐,查询速度的提高是以插入、更新、删除的速度为代价的,这些写操作,增加了大量的IO。所以它的价

    2024年01月21日
    浏览(40)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包