`

Compass2.2 学习笔记

阅读更多
分类: 检索搜索 36人阅读 评论(0) 收藏 举报
 

学习Compass是个很快速的过程,它不像其他框架需要花很多时间学习它的API和了解它的工作流程.似乎Compass就是两个框架的组合版本.

为什么这样说呢?看下它的工作流程就知道了:

Compass

这个结构和Hibernate很相像,无非就是Hibernate把JDBC封装了一把.所以从结构上来说,只要我们了解了Hibernate,就已经对Compass有了了解.那么Hibernate需要提供API和配置文件来对JDBC进行操作,那么Compass呢?Compass不仅从结构上模仿了Hibernate,就连API风格也不尽相同.我们把它和Hibernate的API做个对比就知道了:

Hibernate API Compass API
Configration cfg = new Configuration().configue(); CompassConfiguration cfg = new CompassConfiguration().configure();
SessionFactory sf = cfg.buildSessionFactory(); Compass compass = cfg.buildCompass();
Session session = sf.openSession(); CompassSession session = compass.openSession();
Transaction tx = session.beginTransaction(); CompassTransaction tx = session.beginTransaction();
session.xxx(); // do something session.xxx(); // do something
tx.commit(); tx.commit();
session.close(); session.close();

再把两个session的核心方法比较一下:

Hibernate session API CompassSession API  
save(Object) create(Object) 建立索引
saveOrUpdate(Object) save(Object) 保存或更新
delete(Object) delete(Class, ids...) 删除索引
get() get() 获取
createQuery(hql).list() find(String) 使用查询字符串查询

所以说,Compass与Hibernate极为相似,Compass总结起来就两句话:

  • Hibernate的风格
  • Lucene的思想

    Compass2.2的版本所用的Lucene2.4.1,Lucene2.4与目前的Lucene3.0相比:

  • API的差别很大,代码不能通用
  • 3.0效率更高

    但是Compass用起来舒服,学习成本不高,所以这里用Compass2.2对索引库进行操作.

    所需jar包

    image

    Compass配置文件

    先从Compass的文档中复制配置模板:

     

    再对其进行详细配置:

    <compass name="default">
     
     <!-- 1,连接信息 -->
     <connection>
     <file path="./indexDir/" />
     </connection>
     
     <!-- 2,声明映射信息 -->
     <mappings>
     <class name="cn.domain.Article" />
     </mappings>
     
    </compass>

    在Lucene里面,如果将对象的数据存储在索引库里面,需要先把对象转换为Document,这个工作在Compass里面会自动将其转为Document,在Lucene里面还需要指定哪些Field是可以搜索的,并且是否存储在缓存数据里面,那么Compass也要提供相应的配置才行,Compass如何做到呢?它会在主配置文件(compass.cfg.xml)中通过反射得到Class,得到该类上所有的注解信息,这就是映射信息的作用.用Article作为测试类,属性如下:

    public class Article {
     
     private Integer id;
     private String title;
     private String content;

    再生成对应的getter、setter方法,我们要指明这个类的数据是可以被搜索到的,需要在类上添加注解:

    @Searchable
    public class Article {

    这就表明这是一个可搜索对象,Compass会通过反射创造出该类的实例,所以一定要有无参的构造函数,并且有删除索引的需求,还有Field的配置等,对可搜索对象的所有要求为:

    要有默认的构造方法

  • 在类上要有:@Searchable
  • 要有一个唯一标识符的属性:@SearchableId
  • 其他的属性用:@SearchableProperty

    第二个条件主要是针对删除和更新的,因为之前对Compass的核心方法和Hibernate的核心方法进行了对比,删除索引是用delete()方法,delete()方法不是接收一个对象,因为你把对象给它,它也不知道对象与与对象之间是以什么为区别的,所以给它一个Class,它通过在主配置文件中的映射信息,反射出该类, 再用你提供的第二个参数,也就是唯一标识符,来找到索引,从而进行删除和更新的操作,这就是@SearchableId的作用.

    在Lucene里面将对象转为Document对象的时候,需要保存的数据用Field对象来封装,封装的时候,要提供是否存储数据以及是否为数据建立索引等配置,这就是@SearchableProperty的作用.完整的对类的配置为下,以id作为唯一标识符:

    @Searchable
    public class Article {
     
     // 对于唯一标识符,默认不可以进行搜索,除非明确的指定name属性。
     // Compass2.2对于数字是使用toString()策略,对于数字属性应指定format属性。
     // 如format="00000",表示数字存为5个字符,如果不足5个,前面用若干个0补齐。
     @SearchableId(name = "id", format = "00000000")
     private Integer id;
     
     // name属性可以不写,默认当前属性(成员变量)的名称。
     @SearchableProperty(name = "title", store = Store.YES, index = Index.ANALYZED)
     private String title;
     
     @SearchableProperty(name = "content", store = Store.YES, index = Index.ANALYZED)
     private String content;

    注意导包不要导错了,全是org.compass.annotations包中的.属性配置基本和Lucene里面一样.唯一需要注意的就是对数字的处理.如果在Compass的使用过程中出来问题,或者找不到索引之类的,一定要记得看你的数据类型.

    创建索引的方式基本与Lucene相同,毕竟Compass是对Lucene的封装,思想是属于Lucene的.就像Hibernate使用Session需要先得到SessionFactory一样,Compass也需要获得SessionFactory,并且也是由Configuration对象来取得的:

    public class ArticleDao {
     
     private Compass compassSessionFactory;
     
     public ArticleDao() {
     CompassConfiguration cfg = new CompassConfiguration().configure();
     compassSessionFactory = cfg.configure().buildCompass();
     }

    在Hibernate中,对于Session,SessionFactory只需要一个就够了,对于一个索引库,全局只有一个Compass对象就可以了:

    @Test
    public void createIndex(){
     //准备好测试数据
     Article article = new Article();
     article.setId(1);
     article.setTitle("baby");
     article.setContent("love you");
     
     //获得Session
     CompassSession session = compassSessionFactory.openSession();
     //这个事务是针对索引库的事务
     CompassTransaction tx = session.beginTransaction();
     session.save(article);
     tx.commit();
     session.close(); //一定要记得关闭
    }

    简单的查询,就像Lucene的查询字符串一样.

    @Test
    public void searchIndex() {
     //获得Session
     CompassSession session = compassSessionFactory.openSession();
     //这个事务是针对索引库的事务
     CompassTransaction tx = session.beginTransaction();
     //找到一条结果数
     // Article article = session.load(Article.class, 1);
     // System.out.println(article.getId());
     // System.out.println(article.getTitle());
     // System.out.println(article.getContent());
     String queryString = "love";
     CompassHits hits = session.find(queryString);
     int count = hits.length();
     for (int i = 0; i < count; i++) {
     Article article = (Article) hits.data(i);
     System.out.println(article.getId());
     System.out.println(article.getTitle());
     System.out.println(article.getContent());
     }
     tx.commit();
     session.close(); //一定要记得关闭
    }

    这只是一个简单增加索引和查询索引的例子,权当做Hello World吧.

    每次使用Session都要从SessionFactory中获取,无疑是很麻烦的,不如把SessionFactory维护在工具类里面,全局只有一个,在用一个静态方法得到一个Session.

    @Test
    public void deleteIndex(){
     CompassSession session = CompassUtils.getSession();
     CompassTransaction tx = session.beginTransaction();
     session.delete(Article.class,1);
     tx.commit();
     session.close();
    }
     
    @Test
    public void updateIndex(){
     CompassSession session = CompassUtils.getSession();
     CompassTransaction tx = session.beginTransaction();
     Article article = session.get(Article.class, 1);
     article.setContent("love love");
     session.save(article);
     tx.commit();
     session.close();
    }

    注意,如果唯一标识符是数字,并且没有在实体类里面对唯一标识符使用format属性,则以上代码获取不到!

     


    默认的分词是标准分词器,这个分词器对中文采取的是单字分词,根本没办法使用.一般做法是用第三方的分词器,常用的有极易分词、庖丁分词、IKAnalyzer等.要使用分词器,需要在Compass的主配置文件里面进行配置,这里采用极易分词:

    <!-- 3,其他配置 -->
    <settings>
     <!-- 配置分词器类型 -->
     <setting name="compass.engine.analyzer.default.type" value="jeasy.analysis.MMAnalyzer"></setting>
    </settings>

    这个分词器对中文支持比较好,采用的是词库分词,准确度较高.

     


    接下来还要配置高亮器,配置高亮器主要有三点:前缀、后缀、高亮显示的摘要.同样是在主配置文件里面进行配置:

    <!-- 3,其他配置 -->
    <settings>
     <!-- 高亮配置:摘要的长度(字符数量,默认为100) -->
     <setting name="compass.engine.highlighter.default.fragmenter.simple.size" value="20" />
     <!-- 高亮配置:显示效果的前缀 -->
     <setting name="compass.engine.highlighter.default.formatter.simple.pre" value="&lt;span class='keyword'&gt;" />
     <!-- 高亮配置:显示效果的后缀 -->
     <setting name="compass.engine.highlighter.default.formatter.simple.post" value="&lt;/span&gt;" />
     
     <!-- 配置分词器类型 -->
     <setting name="compass.engine.analyzer.default.type" value="jeasy.analysis.MMAnalyzer"></setting>
    </settings>

    注意,前缀和后缀中的标签,要转义,不然会和配置文件中的符号产生冲突!

    @Test
    public void highlighterIndex() {
     CompassSession session = CompassUtils.getSession();
     CompassTransaction tx = session.beginTransaction();
     // 查询条件
     String queryString = "love";
     CompassHits hits = session.find(queryString);
     int count = hits.length();
     for (int i = 0; i < count; i++) {
     Article article = (Article) hits.data(i);
     // -------------------------
     // 进行高亮操作,一次高亮一个属性值
      // 返回高亮后的一段文本摘要,原属性值不会改变
       // 如果当前高亮的属性值中没有出现关键字,则返回null.
     String text = hits.highlighter(i).fragment("content");
     if(text != null){
     // 使用的高亮后的文本替原文本
     article.setContent(text);
     }
     System.out.println(article.getId());
     System.out.println(article.getTitle());
     System.out.println(article.getContent());
     }
     tx.commit();
     session.close();
    }

    一般来说搜索结果的排序和Lucene里面是一样的,按照相关度得分进行排序,在实际应用中,往往需要人工操纵得分,比如给了推广费之类的,既然有这种需求,自然就有这种做法,和Lucene的不同之处在于,Lucene在添加索引时控制相关度得分的比重,Compass也是在添加索引时控制,不过需要在实体类里面增加一个属性和它的getter、setter方法,这个属性决定了所有这个类的数据的相关度得分的比重,也就是说可以让只要是这个实体类的数据,就能比别的类的数据得分要高,并且只要是设置了这个属性,就能在添加索引时控制相关度得分的比重:

    @SearchableBoostProperty
    private float boostValue = 1F;

    在实体里面增加这个属性,属性名无所谓,类型是float,最重要的是要有getter、setter方法和@SearchableBoostProperty,必须要有这处注解,Lucene才能在你调用相关方法的时候,通过反射获得有这个注解的属性名,在调用它的getter方法,得到值,那么在程序中就要增加一句代码:

    //准备好测试数据
    Article article = new Article();
    article.setId(3);
    article.setTitle("baby");
    article.setContent("love you");
    //设定比重,默认为1L
    article.setBoostValue(2L);

    其他地方都是一样的.除了对相关度得分的排序以外,还可能在搜索时使用某个属性的值进行排序.这也是可以的:

    @Test
    public void sortIndex() {
     CompassSession session = CompassUtils.getSession();
     CompassTransaction tx = session.beginTransaction();
     // 查询条件
     String queryString = "love";
     //CompassHits hits = session.find(queryString);
     CompassQuery query = session.queryBuilder().queryString(queryString).toQuery();
     query.addSort("id");
     CompassHits hits = query.hits();
     int count = hits.length();
     for (int i = 0; i < count; i++) {
     Article article = (Article) hits.data(i);
     System.out.println(article.getId());
     System.out.println(article.getTitle());
     System.out.println(article.getContent());
     }
     tx.commit();
     session.close();
    }

    只需要在得到搜索结果之前做一点改动.先得到CompassQuery对象,调用这个对象的addSort()方法增加排序规则.像上述一样,则是按升序进行排序,也可以替换成下面这句使用降序排列:

    query.addSort("id",SortDirection.REVERSE);

    注意,指定参与排序的字段时,参与排序的字段必须要是Index.NOT_ANALYZED,否则分词后无法找到!

    有时候也会使用到过滤器对搜索结果进行过滤.

    @Test
    public void filterIndex() {
     CompassSession session = CompassUtils.getSession();
     CompassTransaction tx = session.beginTransaction();
     //查询条件
     String queryString = "love";
     //得到CompassQuery对象,准备查询
     CompassQuery query = session.queryBuilder().queryString(queryString).toQuery();
     /**
     * 增加过滤条件,between第三、四个参数分别代表是否包含下边界和上边界
     */
     CompassQueryFilter filter = session.queryFilterBuilder().between("id", 2, 3, true, true);
     //添加过滤器
     query.setFilter(filter);
     CompassHits hits = query.hits();
     int count = hits.length();
     for (int i = 0; i < count; i++) {
     Article article = (Article) hits.data(i);
     System.out.println(article.getId());
     System.out.println(article.getTitle());
     System.out.println(article.getContent());
     }
     tx.commit();
     session.close();
    }

    但是使用过滤器是效率很低的做法,使用查询字符串可以获得更高的效率,而且可以达到同样的效果.下面就对各种查询进行简要说明:

    @Test
    public void search() throws Exception {
     CompassSession session = CompassUtils.openSession();
     CompassTransaction tx = session.beginTransaction();
     
     // 查询方式一:使用查询字符串(可以有查询语法)
     // CompassHits hits = session.find(queryString);
     // -----------------------------------------------------------------
     // 查询方式二:构建CompassQuery对象
     // 1,查询所有
     CompassQuery query1 = session.queryBuilder().matchAll();
     
     // 2,关键词查询
     CompassQuery query2 = session.queryBuilder().term("title", "lucene");
     
     // 3,范围查询
     CompassQuery query3 = session.queryBuilder().between("id", 5, 15, true);
     
     // 4,通配符查询
     CompassQuery query4 = session.queryBuilder().wildcard("title", "lu*n?");
     
     // 5,短语查询
     // 设定精确间隔的匹配方式,写法1
     CompassMultiPhraseQueryBuilder qb = session.queryBuilder().multiPhrase("title");
     qb.add("lucene", 0); // 第一个词位置从0开始
     qb.add("工作", 2); // 第二个词位置从2开始
     CompassQuery query5 = qb.toQuery();
     // 设定精确间隔的匹配方式,写法2
     CompassQuery query6 = session.queryBuilder().multiPhrase("title")//
     .add("lucene", 0) // 第一个词位置从0开始
     .add("工作", 2) // 第二个词位置从2开始
     .toQuery();
     // 设定最多间隔的匹配方式
     CompassQuery query7 = session.queryBuilder().multiPhrase("title")//
     .add("lucene") //
     .add("工作") //
     .setSlop(5) // 指定的词之间的最长间隔不超过5个词
     .toQuery();
     // 6,布尔查询
     CompassQuery query = session.queryBuilder().bool()//
     // .addMust(query)
     // .addMustNot(query)
     // .addShould(query)
     .addMust(query1)//
     .addMustNot(query3)//
     .toQuery();
     
     // -------------------------------
     CompassHits hits = query.hits();
     
     // 处理结果
     List<Article> list = new ArrayList<Article>();
     int count = hits.length(); // 总结果数
     
     for (int i = 0; i < hits.length(); i++) {
     Article article = (Article) hits.data(i);
     list.add(article);
     }
     
     tx.commit();
     session.close();
     // -----------------------------------------------------------------
     // 显示结果
     System.out.println("====== 符合条件的总记录数为:" + count + " ======");
     for (Article a : list) {
     System.out.println("--------------> id = " + a.getId());
     System.out.println("title = " + a.getTitle());
     System.out.println("content= " + a.getContent());
     }
    }

    基本上还是Lucene的那一套,只是简单的封装一下而已.


  • 分享到:
    评论

    相关推荐

      SNS单模无芯光纤仿真与传感器结构特性分析——基于Rsoft beamprop模块

      内容概要:本文主要探讨了SNS单模无芯光纤的仿真分析及其在通信和传感领域的应用潜力。首先介绍了模间干涉仿真的重要性,利用Rsoft beamprop模块模拟不同模式光在光纤中的传播情况,进而分析光纤的传输性能和模式特性。接着讨论了光纤传输特性的仿真,包括损耗、色散和模式耦合等参数的评估。随后,文章分析了光纤的结构特性,如折射率分布、包层和纤芯直径对性能的影响,并探讨了镀膜技术对光纤性能的提升作用。最后,进行了变形仿真分析,研究外部因素导致的光纤变形对其性能的影响。通过这些分析,为优化光纤设计提供了理论依据。 适合人群:从事光纤通信、光学工程及相关领域的研究人员和技术人员。 使用场景及目标:适用于需要深入了解SNS单模无芯光纤特性和优化设计的研究项目,旨在提高光纤性能并拓展其应用场景。 其他说明:本文不仅提供了详细的仿真方法和技术细节,还对未来的发展方向进行了展望,强调了SNS单模无芯光纤在未来通信和传感领域的重要地位。

      发那科USM通讯程序socket-rece

      发那科USM通讯程序socket-set

      嵌入式八股文面试题库资料知识宝典-WIFI.zip

      嵌入式八股文面试题库资料知识宝典-WIFI.zip

      JS+HTML源码与image

      源码与image

      物流行业车辆路径优化:基于遗传算法和其他优化算法的MATLAB实现及应用

      内容概要:本文详细探讨了物流行业中路径规划与车辆路径优化(VRP)的问题,特别是针对冷链物流、带时间窗的车辆路径优化(VRPTW)、考虑充电桩的车辆路径优化(EVRP)以及多配送中心情况下的路径优化。文中不仅介绍了遗传算法、蚁群算法、粒子群算法等多种优化算法的理论背景,还提供了完整的MATLAB代码及注释,帮助读者理解这些算法的具体实现。此外,文章还讨论了如何通过MATLAB处理大量数据和复杂计算,以得出最优的路径方案。 适合人群:从事物流行业的研究人员和技术人员,尤其是对路径优化感兴趣的开发者和工程师。 使用场景及目标:适用于需要优化车辆路径的企业和个人,旨在提高配送效率、降低成本、确保按时交付货物。通过学习本文提供的算法和代码,读者可以在实际工作中应用这些优化方法,提升物流系统的性能。 其他说明:为了更好地理解和应用这些算法,建议读者参考相关文献和教程进行深入学习。同时,实际应用中还需根据具体情况进行参数调整和优化。

      嵌入式八股文面试题库资料知识宝典-C and C++ normal interview_8.doc.zip

      嵌入式八股文面试题库资料知识宝典-C and C++ normal interview_8.doc.zip

      基于灰狼优化算法的城市路径规划Matlab实现——解决TSP问题

      内容概要:本文介绍了基于灰狼优化算法(GWO)的城市路径规划优化问题(TSP),并通过Matlab实现了该算法。文章详细解释了GWO算法的工作原理,包括寻找猎物、围捕猎物和攻击猎物三个阶段,并提供了具体的代码示例。通过不断迭代优化路径,最终得到最优的城市路径规划方案。与传统TSP求解方法相比,GWO算法具有更好的全局搜索能力和较快的收敛速度,适用于复杂的城市环境。尽管如此,算法在面对大量城市节点时仍面临运算时间和参数设置的挑战。 适合人群:对路径规划、优化算法感兴趣的科研人员、学生以及从事交通规划的专业人士。 使用场景及目标:①研究和开发高效的路径规划算法;②优化城市交通系统,提升出行效率;③探索人工智能在交通领域的应用。 其他说明:文中提到的代码可以作为学习和研究的基础,但实际应用中需要根据具体情况调整算法参数和优化策略。

      嵌入式八股文面试题库资料知识宝典-Intel3.zip

      嵌入式八股文面试题库资料知识宝典-Intel3.zip

      嵌入式八股文面试题库资料知识宝典-2019京东C++.zip

      嵌入式八股文面试题库资料知识宝典-2019京东C++.zip

      嵌入式八股文面试题库资料知识宝典-北京光桥科技有限公司面试题.zip

      嵌入式八股文面试题库资料知识宝典-北京光桥科技有限公司面试题.zip

      物理学领域十字形声子晶体的能带与传输特性研究及应用

      内容概要:本文详细探讨了十字形声子晶体的能带结构和传输特性。首先介绍了声子晶体作为新型周期性结构在物理学和工程学中的重要地位,特别是十字形声子晶体的独特结构特点。接着从散射体的形状、大小、排列周期等方面分析了其对能带结构的影响,并通过理论计算和仿真获得了能带图。随后讨论了十字形声子晶体的传输特性,即它对声波的调控能力,包括传播速度、模式和能量分布的变化。最后通过大量实验和仿真验证了理论分析的正确性,并得出结论指出散射体的材料、形状和排列方式对其性能有重大影响。 适合人群:从事物理学、材料科学、声学等相关领域的研究人员和技术人员。 使用场景及目标:适用于希望深入了解声子晶体尤其是十字形声子晶体能带与传输特性的科研工作者,旨在为相关领域的创新和发展提供理论支持和技术指导。 其他说明:文中还对未来的研究方向进行了展望,强调了声子晶体在未来多个领域的潜在应用价值。

      嵌入式系统开发_USB主机控制器_Arduino兼容开源硬件_基于Mega32U4和MAX3421E芯片的USB设备扩展开发板_支持多种USB外设接入与控制的通用型嵌入式开发平台_.zip

      嵌入式系统开发_USB主机控制器_Arduino兼容开源硬件_基于Mega32U4和MAX3421E芯片的USB设备扩展开发板_支持多种USB外设接入与控制的通用型嵌入式开发平台_

      e2b8a-main.zip

      e2b8a-main.zip

      少儿编程scratch项目源代码文件案例素材-火柴人跑酷(2).zip

      少儿编程scratch项目源代码文件案例素材-火柴人跑酷(2).zip

      【HarmonyOS分布式技术】远程启动子系统详解:跨设备无缝启动与智能协同的应用场景及未来展望

      内容概要:本文详细介绍了HarmonyOS分布式远程启动子系统,该系统作为HarmonyOS的重要组成部分,旨在打破设备间的界限,实现跨设备无缝启动、智能设备选择和数据同步与连续性等功能。通过分布式软总线和分布式数据管理技术,它能够快速、稳定地实现设备间的通信和数据同步,为用户提供便捷的操作体验。文章还探讨了该系统在智能家居、智能办公和教育等领域的应用场景,展示了其在提升效率和用户体验方面的巨大潜力。最后,文章展望了该系统的未来发展,强调其在技术优化和应用场景拓展上的无限可能性。 适合人群:对HarmonyOS及其分布式技术感兴趣的用户、开发者和行业从业者。 使用场景及目标:①理解HarmonyOS分布式远程启动子系统的工作原理和技术细节;②探索该系统在智能家居、智能办公和教育等领域的具体应用场景;③了解该系统为开发者提供的开发优势和实践要点。 其他说明:本文不仅介绍了HarmonyOS分布式远程启动子系统的核心技术和应用场景,还展望了其未来的发展方向。通过阅读本文,用户可以全面了解该系统如何通过技术创新提升设备间的协同能力和用户体验,为智能生活带来新的变革。

      嵌入式八股文面试题库资料知识宝典-C and C++ normal interview_1.zip

      嵌入式八股文面试题库资料知识宝典-C and C++ normal interview_1.zip

      少儿编程scratch项目源代码文件案例素材-激光反弹.zip

      少儿编程scratch项目源代码文件案例素材-激光反弹.zip

      COMSOL相控阵检测技术在有机玻璃斜楔中检测工件内部缺陷的应用研究

      内容概要:本文详细介绍了COMSOL相控阵检测技术在有机玻璃斜楔上放置16阵元进行工件内部缺陷检测的方法。首先阐述了相控阵检测技术的基本原理,特别是通过控制各阵元的激发时间和相位来实现声波的聚焦和扫描。接着,重点解析了横孔缺陷的反射接收波,解释了波的折射现象及其背后的物理原因。最后,通过实例展示了COMSOL模拟声波传播过程的成功应用,验证了该技术的有效性和准确性。 适合人群:从事固体力学、无损检测领域的研究人员和技术人员,尤其是对相控阵检测技术和COMSOL仿真感兴趣的读者。 使用场景及目标:适用于需要精确检测工件内部缺陷的研究和工业应用场景,旨在提高检测精度和效率,确保产品质量和安全。 其他说明:文中提到的声速匹配现象有助于理解波在不同介质间的传播特性,这对优化检测参数设置有重要意义。

      少儿编程scratch项目源代码文件案例素材-极速奔跑者.zip

      少儿编程scratch项目源代码文件案例素材-极速奔跑者.zip

      嵌入式八股文面试题库资料知识宝典-微软_interview.zip

      嵌入式八股文面试题库资料知识宝典-微软_interview.zip

    Global site tag (gtag.js) - Google Analytics