`

搜索引擎lecene缓存

 
阅读更多

Lucene的缓存机制和解决方案

概述

1Filter Cache

2field缓存

3、结论

4.LuceneBase缓存解决方案

 

 

 

概述

lucene的缓存可分为两类:filter cachefield cache

filter cache的实现类为CachingWrapperFilter,用来缓存其他Filter的查询结果。

field cache的实现类是FieldCache,缓存用于排序的field的值。

简单来说,filter Cache用于查询缓存,field cache用于排序。

这两种缓存的生存周期都是在一个IndexReader实例内,因此提高Lucene查询性能的关键在于如何维护和使用同一个IndexReader(IndexSearcher)

 

Filter Cache

从严格意义上来说,lucene没有查询类似数据库服务器的数据高速缓存。luceneFilter缓存实现类是CachingWrapperFilter,它缓存了查出来的bits。另外lucene还提供了FilterManager,一个单例对象,用来缓存Filter本身。

下面是CachingWrapperFilter的具体实现:

 

public class CachingWrapperFilter extends Filter {

protected Filter filter;

 

protected transient Map cache;//这是作为缓存使用的map

 

public CachingWrapperFilter(Filter filter) {

this.filter = filter;

}

 

public BitSet bits(IndexReader reader) throws IOException {

if (cache == null) {

cache = new WeakHashMap();//采用WeakHashMap实现,由JVM回收内存

}

 

synchronized (cache) { // check cache

BitSet cached = (BitSet) cache.get(reader);//keyIndexReadervalueBitSet,所以该缓存生存周期在一个IndexReader

 

if (cached != null) {

return cached;

}

 

}

 

//若没有找到缓存,则重新读取

final BitSet bits = filter.bits(reader);

synchronized (cache) { // update cache

cache.put(reader, bits);

}

 

return bits;

}

 

FilterManager里,采用Filter.hashCode()作为key的,所以使用的时候应该在自定义的Filter类中重载hashCode()方法。

 

 

例子:Filter filter=FilterManager.getInstance().getFilter(new CachingWrapperFilter(new MyFilter()));如果该filter已经存在,在FilterManager返回该Filter的缓存(带有bit缓存),否则返回本身(不带bit缓存的)。

 

 

FilterManager里有个定时线程,会定期清理缓存,以防造成内存溢出错误。

 

 

field缓存

field缓存是用来排序用的。lucene会将需要排序的字段都读到内存来进行排序,所占内存大小和文档数目相关。经常有人用lucene做排序出现内存溢出的问题,一般是因为每次查询都启动新的searcher实例进行查询,当并发大的时候,造成多个Searcher实例同时装载排序字段,引起内存溢出。

 

Field缓存的实现类是FieldCacheImpl,下面我们看看排序时怎么用到Field缓存的:

IndexSearcher类里的方法,有关排序的查询都会调用到此方法:

public TopFieldDocs search(Weight weight, Filter filter, final int nDocs,Sort sort)throws IOException {

 

TopFieldDocCollector collector =

new TopFieldDocCollector(reader, sort, nDocs);//排序操作由TopFieldDocCollector实现

 

search(weight, filter, collector);//开始查询,查询结果回调Collector.collect()方法时实现排序

 

return (TopFieldDocs)collector.topDocs();//返回TopFieldDocs对象,这个对象和TopDocs的差异在于TopFieldDocs里包含排序字段的信息,包括字段名和字段值。其中TopFieldDocsScoreDoc[]的实例是FieldDoc[]

 

}

 

 

下面看看TopFieldDocCollector.collect()是怎么实现的:

 

public void collect(int doc, float score) {

if (score > 0.0f) {

totalHits++;

if (reusableFD == null)

reusableFD = new FieldDoc(doc, score);s

else {

reusableFD.score = score;

reusableFD.doc = doc;

}

 

reusableFD = (FieldDoc) hq.insertWithOverflow(reusableFD);//hqFieldSortedHitQueue对象,一个PriorityQueue的子类,insertWithOverflow()实现一个固定大小的排序队列,排序靠后的对象被挤出队列

 

}

 

}

FieldSortedHitQueue是通过重载lessThan()方法来实现排序功能的:

*/

 

protected boolean lessThan (final Object a, final Object b) {

final ScoreDoc docA = (ScoreDoc) a;

final ScoreDoc docB = (ScoreDoc) b;

// run comparators

 

final int n = comparators.length;

int c = 0;

for (int i=0; i<n && c==0; ++i) {

c = (fields[i].reverse) ? comparators[i].compare (docB, docA)

: comparators[i].compare (docA, docB);//通过comparators[]来进行排序,我们剩下的任务就是看看这些comparator[]是怎么构造的,怎么使用的Fieldcache

 

}

 

// avoid random sort order that could lead to duplicates (bug #31241):

if (c == 0)

return docA.doc > docB.doc;

return c > 0;

 

}

 

 

comparators实在FieldSortedHitQueue的构造函数里创建的:

 

public FieldSortedHitQueue (IndexReader reader, SortField[] fields, int size)throws IOException {

final int n = fields.length;

comparators = new ScoreDocComparator[n];

this.fields = new SortField[n];

 

for (int i=0; i<n; ++i) {

String fieldname = fields[i].getField();

comparators[i] = getCachedComparator (reader, fieldname, fields[i].getType(), fields[i].getLocale(), fields[i].getFactory());//调用getCachedComparator方法获得缓存的comparatorscomparatorScoreDocComparator的实例

 

if (comparators[i].sortType() == SortField.STRING) {

this.fields[i] = new SortField (fieldname, fields[i].getLocale(), fields[i].getReverse());

} else {

this.fields[i] = new SortField (fieldname, comparators[i].sortType(), fields[i].getReverse());

}

 

}

 

initialize (size);

 

}

 

 

下面看看getCachedComparator ()的实现:

static final FieldCacheImpl.Cache Comparators = new FieldCacheImpl.Cache(){

。。。

 

}

 

 

static ScoreDocComparator getCachedComparator (IndexReader reader, String field, int type, Locale locale, SortComparatorSource factory)throws IOException {

//以下两种不需要读取字段

if (type == SortField.DOC) return ScoreDocComparator.INDEXORDER;//按索引顺序排序

 

if (type == SortField.SCORE) return ScoreDocComparator.RELEVANCE;//按相关度排序

 

FieldCacheImpl.Entry entry = (factory != null)? new FieldCacheImpl.Entry (field, factory)

: new FieldCacheImpl.Entry (field, type, locale);

 

//其他类型的排序需要读取字段到缓存中

 

return (ScoreDocComparator)Comparators.get(reader, entry);//Comparators 是一个FieldCache的实例

 

}

 

 

Comparators.get()方法根据排序字段类型的不同,返回ScoreDocComparator的不同实现,下面我们看看String类型的实现,就可以知道什么时候调用fieldCache了:

 

static ScoreDocComparator comparatorString (final IndexReader reader, final String fieldname)

 

throws IOException {

 

final String field = fieldname.intern();

 

//下面代码读取缓存,得到字段值和文档id的对应关系,如果缓存不存在,则读取索引文件。缓存的生命周期是和IndexReader一样,所以不同查询使用同一个Searcher,可以保证排序缓存只有一个,不会出现内存溢出的问题

 

final FieldCache.StringIndex index = FieldCache.DEFAULT.getStringIndex (reader, field);

 

return new ScoreDocComparator () {

 

 

public final int compare (final ScoreDoc i, final ScoreDoc j) {

 

final int fi = index.order[i.doc];//index.order[]的值是按自定义字段的排序,数组的索引是lucene docid;可以看看getStringIndex的具体实现来看看这些值是怎么读进来的,这里就不详细说明了

 

final int fj = index.order[j.doc];

 

if (fi < fj) return -1;

 

if (fi > fj) return 1;

 

return 0;

 

}

 

 

public Comparable sortValue (final ScoreDoc i) {

 

return index.lookup[index.order[i.doc]];

 

}

 

 

public int sortType() {

 

return SortField.STRING;

 

}

 

};

 

}

 

 

结论

lucene使用上述的两个缓存机制已经能解决绝大部分的问题了。solrlucene之上封装,又增加了另外的缓存,但应该说作用不太大,反而使代码变得很复杂了。

 

缓存解决方案

Lucene缓存的生存周期都是在一个IndexReader实例内,因此提高Lucene查询性能的关键在于如何维护和使用同一个IndexReader(IndexSearcher)

因此我们需要新写一个SingleIndexSearcher(源代码见下)类,该类继承IndexSearcher,作用为实现IndexSearcher的单例模式。

 

LuceneBase加入类SingleIndexSearcher并将IndexSearcher对象的生成都用SingleIndexSearcher. getInstance()方法。

  

缓存Filter用法:Filter filter = new CachingWrapperFilter(new FieldFilter(field, value));

  

   Filter filter = FilterManager.getInstance().getFilter(new CachingWrapperFilter(new FieldFilter(field, value)));

 

 

 

/**

 * IndexSearcher单例模式的实现 采取单例模式是要充分利用Lucene的缓存,同时防止多个IndexSearcher对象导致内存溢出和并发问题

 *

 * @author 路卫杰

 * @version 1.0, 2010-8-4

 * @see IndexSearcher

 */

public class SingleIndexSearcher extends IndexSearcher {

       /** 私有静态SingleIndexSearcher对象 */

       private static IndexSearcher instance;

       static{           

               try {

                     instance = new SingleIndexSearcher(Configure.getProperties().getProperty("ZkAnalyzerPath"));

                     System.out.println("构造");

               } catch (CorruptIndexException e) {

                     e.printStackTrace();

              } catch (IOException e) {

                     e.printStackTrace();

              }

       }

      

 

       /**

        * 构造方法

        *

        * @param path

        *            索引路径

        * @throws IOException

        * @throws CorruptIndexException

        */  

       public SingleIndexSearcher(String path) throws CorruptIndexException, IOException{         

              super(path);

       }

 

       /**

        * 获得单例

        */

       public static IndexSearcher getInstance() {

              return instance;

       }

}

 

 

 

搜索速度比较

搜索相同关键字和过滤器次数(次)  一般过滤器(ms)    缓存过滤器(ms)   缓存排序(ms)

                                1                          2407            2438          2093

                                5                          4750            2531          2219

                              10                         8110            2672          2313

                              20                        14750            2922          2593

                              50                        34498            3672          3250

                            100                       67546            4844          4407

 

 

分享到:
评论

相关推荐

    lecene原理与代码分析

    在当今信息技术的快速发展背景下,全文检索技术作为高效处理和检索海量非结构化数据的核心方法之一,已经广泛应用于各种搜索引擎和数据库系统中。Apache Lucene,作为一个高效、基于Java的全文检索库,其作用不可...

    lecene.net 对多种文件索引并搜索

    在IT行业中,搜索引擎技术是至关重要的,特别是在大数据和信息检索领域。Apache Lucene是一个高性能、全文本搜索库,被广泛应用于各种系统中。Lucene.NET是Lucene的.NET版本,它为.NET开发者提供了强大的文本搜索...

    大型门户网站实现的十四大技术小结

    基于LUCENE的搜索引擎研究与实现&gt; 二、缓存 实现页面级的URL缓存 &lt;基于网络爬虫的有效URL缓存&gt; 三、生成静态文件 比如说,一个首页可能会员由多个部分组成,每个模块生成一个静态文件,然后才用服务器端包含SSI...

    毕设单片机实战项目基于esp8266的高考倒计时.zip

    【项目资源】: 单片机项目适用于从基础到高级的各种项目,特别是在性能要求较高的场景中,比如操作系统开发、嵌入式编程和底层系统编程。如果您是初学者,可以从简单的控制台程序开始练习;如果是进阶开发者,可以尝试涉及硬件或网络的项目。 【项目质量】: 所有源码都经过严格测试,可以直接运行。 功能在确认正常工作后才上传。 【适用人群】: 适用于希望学习不同技术领域的小白或进阶学习者。 可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。 【附加价值】: 项目具有较高的学习借鉴价值,也可直接拿来修改复刻。 对于有一定基础或热衷于研究的人来说,可以在这些基础代码上进行修改和扩展,实现其他功能。 【沟通交流】: 有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 鼓励下载和使用,并欢迎大家互相学习,共同进步。 # 注意 1. 本资源仅用于开源学习和技术交流。不可商用等,一切后果由使用者承担。 2. 部分字体以及插图等来自网络,若是侵权请联系删除。

    毕设工坊:专注于计算机毕业设计项目的交流与资源共享平台,涵盖各类技术文档、代码示例及实战经验分享,助力学子顺利完成学业挑战

    毕设工坊:专注于计算机毕业设计项目的交流与资源共享平台,涵盖各类技术文档、代码示例及实战经验分享,助力学子顺利完成学业挑战。

    【window 可视化nvm管理node版本 nvm-desktop】

    【window 可视化nvm管理node版本 nvm-desktop】

    《基于YOLOv8的玉器识别系统》(包含源码、完整数据集、可视化界面、部署教程)简单部署即可运行。功能完善、操作简单,适合毕设或课程设计.zip

    资源内项目源码是来自个人的毕业设计,代码都测试ok,包含源码、数据集、可视化页面和部署说明,可产生核心指标曲线图、混淆矩阵、F1分数曲线、精确率-召回率曲线、验证集预测结果、标签分布图。都是运行成功后才上传资源,毕设答辩评审绝对信服的保底85分以上,放心下载使用,拿来就能用。包含源码、数据集、可视化页面和部署说明一站式服务,拿来就能用的绝对好资源!!! 项目备注 1、该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的,请放心下载使用! 2、本项目适合计算机相关专业(如计科、人工智能、通信工程、自动化、电子信息等)的在校学生、老师或者企业员工下载学习,也适合小白学习进阶,当然也可作为毕设项目、课程设计、大作业、项目初期立项演示等。 3、如果基础还行,也可在此代码基础上进行修改,以实现其他功能,也可用于毕设、课设、作业等。 下载后请首先打开README.txt文件,仅供学习参考, 切勿用于商业用途。

    (源码)基于microbit编程语言的mymicrobit扩展插件项目.zip

    # 基于microbit编程语言的mymicrobit扩展插件项目 ## 项目简介 这是一个基于microbit编程语言的mymicrobit扩展插件项目。该项目旨在提供额外的功能和特性,以扩展microbit编程环境。通过此插件,用户可以轻松地在MakeCode环境中进行编程,实现对micro:bit设备的更多控制和功能实现。 ## 项目的主要特性和功能 1. 扩展性提供了丰富的积木块和代码库,允许用户轻松实现复杂的编程逻辑和功能扩展。 2. 图形化编程支持通过积木块形式的图形化编程,降低编程门槛,方便初学者快速上手。 3. 实时预览提供了积木块的实时预览功能,方便用户直观地了解代码块的逻辑和功能。 4. 与MakeCode无缝集成可以直接在MakeCode环境中导入和使用,无需额外的配置和安装。 ## 安装使用步骤

    毕设单片机实战项目基于ESP8266的局域网图片刷新显示系统.zip

    【项目资源】: 单片机项目适用于从基础到高级的各种项目,特别是在性能要求较高的场景中,比如操作系统开发、嵌入式编程和底层系统编程。如果您是初学者,可以从简单的控制台程序开始练习;如果是进阶开发者,可以尝试涉及硬件或网络的项目。 【项目质量】: 所有源码都经过严格测试,可以直接运行。 功能在确认正常工作后才上传。 【适用人群】: 适用于希望学习不同技术领域的小白或进阶学习者。 可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。 【附加价值】: 项目具有较高的学习借鉴价值,也可直接拿来修改复刻。 对于有一定基础或热衷于研究的人来说,可以在这些基础代码上进行修改和扩展,实现其他功能。 【沟通交流】: 有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 鼓励下载和使用,并欢迎大家互相学习,共同进步。 # 注意 1. 本资源仅用于开源学习和技术交流。不可商用等,一切后果由使用者承担。 2. 部分字体以及插图等来自网络,若是侵权请联系删除。

    《基于YOLOv8的印章分析系统》(包含源码、完整数据集、可视化界面、部署教程)简单部署即可运行。功能完善、操作简单,适合毕设或课程设计.zip

    资源内项目源码是来自个人的毕业设计,代码都测试ok,包含源码、数据集、可视化页面和部署说明,可产生核心指标曲线图、混淆矩阵、F1分数曲线、精确率-召回率曲线、验证集预测结果、标签分布图。都是运行成功后才上传资源,毕设答辩评审绝对信服的保底85分以上,放心下载使用,拿来就能用。包含源码、数据集、可视化页面和部署说明一站式服务,拿来就能用的绝对好资源!!! 项目备注 1、该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的,请放心下载使用! 2、本项目适合计算机相关专业(如计科、人工智能、通信工程、自动化、电子信息等)的在校学生、老师或者企业员工下载学习,也适合小白学习进阶,当然也可作为毕设项目、课程设计、大作业、项目初期立项演示等。 3、如果基础还行,也可在此代码基础上进行修改,以实现其他功能,也可用于毕设、课设、作业等。 下载后请首先打开README.txt文件,仅供学习参考, 切勿用于商业用途。

    p111基于django的企业员工管理系统.zip

    项目资源包含:可运行源码+sql文件 适用人群:学习不同技术领域的小白或进阶学习者;可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。 项目具有较高的学习借鉴价值,也可拿来修改、二次开发。 有任何使用上的问题,欢迎随时与博主沟通,博主看到后会第一时间及时解答。 开发语言:Python 框架:django Python版本:python3.8 数据库:mysql 5.7 数据库工具:Navicat 开发软件:PyCharm 浏览器:谷歌浏览器

    第三章-局域网-思维导图

    第三章-局域网-思维导图

    机械工程PT300机械故障仿真测试台:高校教学与科研用精密振动分析及故障诊断实验系统了您提供的规范

    内容概要:PT300机械故障综合模拟实验台由瓦仑尼安教学设备有限公司生产,旨在帮助用户深入了解振动特征知识及复杂转子振动频谱分析,实现精密振动分析和精准故障诊断。该实验台能模拟轴承故障、不平衡、不对中、设备松动、转子摩擦等多种机械故障现象,可进行不同转速下的轴承故障频率识别、转子静动平衡模拟试验、设备启停机测试等实验。设备采用高效节能ABB三相交流电动机,配备高精度转速控制和测量模块,确保运行稳定。此外,实验台还设有透明防震安全罩和互锁开关,保障实验安全。; 适合人群:高校师生、科研人员等需要学习或研究机械故障诊断相关理论知识和实践技能的人群。; 使用场景及目标:①用于高校等教育机构的教学,辅助学生理解机械故障诊断的理论知识和实践技能;②满足科研人员进行机械故障诊断算法验证、故障特征分析等科研需求。; 其他说明:PT300机械故障综合模拟实验台的每个部件均经过高精度加工,确保在不同振动状态下稳定运行。用户可根据期望分析特定部件的故障特征。设备尺寸为735mm(长)×310mm(宽)×350mm(高),保修一年,且提供免费操作指导服务。

    Android毕设实战项目基于Android+Django+sqlit3开发.zip

    【项目资源】: 适用于从基础到高级的各种项目,特别是在性能要求较高的场景中,比如操作系统开发、嵌入式编程和底层系统编程。如果您是初学者,可以从简单的控制台程序开始练习;如果是进阶开发者,可以尝试涉及硬件或网络的项目。 【项目质量】: 所有源码都经过严格测试,可以直接运行。 功能在确认正常工作后才上传。 【适用人群】: 适用于希望学习不同技术领域的小白或进阶学习者。 可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。 【附加价值】: 项目具有较高的学习借鉴价值,也可直接拿来修改复刻。 对于有一定基础或热衷于研究的人来说,可以在这些基础代码上进行修改和扩展,实现其他功能。 【沟通交流】: 有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 鼓励下载和使用,并欢迎大家互相学习,共同进步。 # 注意 1. 本资源仅用于开源学习和技术交流。不可商用等,一切后果由使用者承担。 2. 部分字体以及插图等来自网络,若是侵权请联系删除。

    【光子晶体模拟】基于COMSOL弱形式PDE的三维光子晶体能带结构计算与优化:电磁场切向连续性处理及带隙分析系统设计使用COMSOL

    内容概要:本文详细介绍了使用COMSOL Multiphysics的弱形式接口对三维光子晶体进行数值模拟的方法和技巧。文章通过具体的代码示例,解释了如何构建光子晶体的介电常数分布、设置弱形式PDE、处理电磁场切向连续性、应用Floquet周期边界条件以及特征值求解等关键步骤。特别强调了弱形式接口相比传统物理场接口的优势,如灵活性和对复杂边界的处理能力。文中还分享了一些实用的经验和注意事项,如布洛赫边界条件的实现、特征值求解器参数的优化配置以及网格划分的技巧。 适合人群:具备一定电磁学和数值模拟基础的研究人员或工程师,尤其是对光子晶体仿真感兴趣的读者。 使用场景及目标:①理解并掌握COMSOL弱形式接口在光子晶体仿真中的应用;②学习如何通过弱形式设置处理复杂的电磁场问题;③提高对光子晶体能带结构和带隙特性的认识;④掌握特征值求解和网格划分的最佳实践。 阅读建议:由于本文涉及较多的具体代码和物理概念,建议读者在阅读过程中结合COMSOL软件进行实际操作,同时查阅相关电磁理论书籍以加深理解。此外,对于文中提到的一些具体参数设置和技巧,可以通过尝试不同的配置来巩固所学知识。

    (源码)基于Arduino平台的INSPTComputacion2项目.zip

    # 基于Arduino平台的INSPTComputacion2项目 ## 项目简介 INSPTComputacion2是一个基于Arduino平台的开发项目。该项目旨在通过Arduino的硬件和软件能力,实现一系列计算和交互功能。通过此项目,用户可以体验到Arduino在嵌入式系统、物联网和微控制器等领域的强大功能。 ## 项目的主要特性和功能 该项目的主要特性和功能包括但不限于以下几点 1. 嵌入式系统开发利用Arduino的硬件资源,开发嵌入式系统应用。 2. 物联网应用实现Arduino与物联网技术的结合,进行数据采集、传输和控制。 3. 交互设计通过Arduino实现人机交互,如按钮控制、LED显示等。 4. 数据处理利用Arduino进行数据处理和分析,如温度、湿度等环境数据的采集和处理。 ## 安装使用步骤 以下是在已下载本项目源码文件后的安装使用步骤 1. 确保已安装Arduino IDE软件。

    毕业设计物联网实战项目基于云且连接 Internet 的新式应用程序。 可用于建立Web应用、 IoT物联网、移动后端等。.zip

    【项目资源】: 物联网项目适用于从基础到高级的各种项目,特别是在性能要求较高的场景中,比如操作系统开发、嵌入式编程和底层系统编程。如果您是初学者,可以从简单的控制台程序开始练习;如果是进阶开发者,可以尝试涉及硬件或网络的项目。 【项目质量】: 所有源码都经过严格测试,可以直接运行。 功能在确认正常工作后才上传。 【适用人群】: 适用于希望学习不同技术领域的小白或进阶学习者。 可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。 【附加价值】: 项目具有较高的学习借鉴价值,也可直接拿来修改复刻。 对于有一定基础或热衷于研究的人来说,可以在这些基础代码上进行修改和扩展,实现其他功能。 【沟通交流】: 有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 鼓励下载和使用,并欢迎大家互相学习,共同进步。 # 注意 1. 本资源仅用于开源学习和技术交流。不可商用等,一切后果由使用者承担。 2. 部分字体以及插图等来自网络,若是侵权请联系删除。

    毕业设计物联网实战项目基于touchgfx,调度基于freertos.zip

    【项目资源】: 物联网项目适用于从基础到高级的各种项目,特别是在性能要求较高的场景中,比如操作系统开发、嵌入式编程和底层系统编程。如果您是初学者,可以从简单的控制台程序开始练习;如果是进阶开发者,可以尝试涉及硬件或网络的项目。 【项目质量】: 所有源码都经过严格测试,可以直接运行。 功能在确认正常工作后才上传。 【适用人群】: 适用于希望学习不同技术领域的小白或进阶学习者。 可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。 【附加价值】: 项目具有较高的学习借鉴价值,也可直接拿来修改复刻。 对于有一定基础或热衷于研究的人来说,可以在这些基础代码上进行修改和扩展,实现其他功能。 【沟通交流】: 有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 鼓励下载和使用,并欢迎大家互相学习,共同进步。 # 注意 1. 本资源仅用于开源学习和技术交流。不可商用等,一切后果由使用者承担。 2. 部分字体以及插图等来自网络,若是侵权请联系删除。

    Python数据结构-学习笔记

    Python数据结构-学习笔记

Global site tag (gtag.js) - Google Analytics