当前位置: 首页 > news >正文

Springboot整合全文检索引擎Lucene

文章目录

  • 前言
  • Lucene的介绍
  • springboot项目中如何整合Lucene简单用法
    • 1. 引入依赖
    • 2. 其它用到的类
    • 2. 创建索引
    • 3. 简单搜索
    • 4. 更新索引
    • 5. 删除索引
    • 6. 删除全部索引
  • Springboot整合Lucene复杂搜索
    • 1. 同时标题和内容中查找关键词
    • 2. 搜索结果高亮显示关键词
    • 3. 分页搜索
    • 4. 多关键词联合搜索
  • 关于IK扩展分词
    • 什么分词?什么是扩展分词?
    • 自定义分词和停止分词

前言

如何自己构建一个单机的博客检索引擎?

本章代码已分享至Gitee: https://gitee.com/lengcz/springbootlucene01

Lucene的介绍

Lucene是一个开源的全文搜索引擎工具包,它提供了用于创建、索引和搜索大量文本的API和工具。Lucene最初是由Doug Cutting于1999年创建,目的是提供一个高效、可扩展的搜索引擎。

Lucene的主要特点包括:

  1. 高性能:Lucene使用倒排索引来加速搜索,这使得它能够快速地检索和排序文档。

  2. 可扩展性:Lucene支持大规模文本索引和搜索操作,并可以通过水平扩展来处理更大量级的数据。

  3. 多语言支持:Lucene支持多种语言的文本索引和搜索,包括中文。

  4. 高级搜索功能:除了基本的文本搜索,Lucene还提供了许多高级搜索功能,例如模糊搜索、通配符搜索、范围搜索等。

  5. 定制化:Lucene提供了丰富的API和工具,可以根据需求进行定制化开发。

在Lucene中进行中文搜索时,需要注意以下几点:

  1. 分词:中文文本通常不用空格进行分词,因此需要使用中文分词器来对文本进行切分。Lucene提供了一些中文分词器,例如IK Analyzer、SmartChineseAnalyzer等。

  2. 中文编码:Lucene默认使用Unicode编码进行搜索,但是如果要处理中文文本,需要使用UTF-8编码。

  3. 中文排序:中文的排序规则与英文不同,Lucene提供了一些中文排序器来处理中文排序。

总之,Lucene是一个功能强大、灵活可定制的全文搜索引擎工具包,可以用于构建各种类型的中文搜索应用。

springboot项目中如何整合Lucene简单用法

1. 引入依赖

 <!-- Lucene核心库 --><dependency><groupId>org.apache.lucene</groupId><artifactId>lucene-core</artifactId><version>7.6.0</version></dependency><!-- Lucene的查询解析器 --><dependency><groupId>org.apache.lucene</groupId><artifactId>lucene-queryparser</artifactId><version>7.6.0</version></dependency><!-- Lucene的默认分词器库 --><dependency><groupId>org.apache.lucene</groupId><artifactId>lucene-analyzers-common</artifactId><version>7.6.0</version></dependency><!-- Lucene的高亮显示 --><dependency><groupId>org.apache.lucene</groupId><artifactId>lucene-highlighter</artifactId><version>7.6.0</version></dependency><!-- ik分词器 --><dependency><groupId>com.jianggujin</groupId><artifactId>IKAnalyzer-lucene</artifactId><version>8.0.0</version></dependency>

2. 其它用到的类

import lombok.Data;
import lombok.Getter;
import lombok.Setter;import java.io.Serializable;@Setter
@Getter
public class BlogTitle implements Serializable {private Long id;private String title;private String description;public BlogTitle() {}public BlogTitle(Long id, String title, String description) {this.id = id;this.title = title;this.description = description;}
}

2. 创建索引

 /*** 模拟数据源,正常需要读取数据库或者其它数据源内容* @return*/private List<BlogTitle> getBlogList() {List<BlogTitle> list = new ArrayList<>();list.add(new BlogTitle(1000L, "Springboot 零基础入门", "springboot基础(1):IDEA构建Springboot项目"));list.add(new BlogTitle(1001L, "springboot基础(1):IDEA构建Springboot项目", "IDEA 构建Springboot项目\n" +"第一种、普通方法构建Springboot项目"));list.add(new BlogTitle(1002L, "springboot基础(2):IDEA查看当前项目的依赖结构图", "pom文件里,右键>>Diagrams>>Show dependencies"));list.add(new BlogTitle(1003L, "Mysql核心教程", "初始化MySQL 在命令提示符窗口中运行mysqld --initialize-insecure,如果没有出现报错,则证明data目录初始化成功。 mysqld --initialize-insecure 1 此时当我们再打开查看MyS..."));list.add(new BlogTitle(1004L, "SqlServer2000如何安装", "SQL Server是微软公司开发的数据库产品,SQL Server 2000被广泛使用,很多电子商务网站、企业内部信息化平台等都是基于SQL Server产品上。 今天的商业环境要求不同类型的数据库解决方案。性能、可伸缩性及可靠性是基本要求,而进入市场时间也非常关键。除这些"));list.add(new BlogTitle(1005L, "明朝那点事儿", "明朝(1368年―1644年),中国历史上的朝代,由明太祖朱元璋所建。初期建都南京,明成祖时期迁都北京。传十六帝,共计276年。元末爆发红巾军起义,朱元璋加入郭子兴起义军。1364年称吴王。1368年初称帝,国号大明,定都南京。1421年朱棣迁都北京,以南京为陪.."));list.add(new BlogTitle(1006L, "厨房日记", "不过,我没做过饭呀!对了,找妈妈帮忙。我找来妈妈,一起走进厨房。我想做西红柿炒鸡蛋,便从冰箱里拿出两个鸡蛋、两个西红柿,又从柜子里取出一个碗和一个盘子。做菜的..."));list.add(new BlogTitle(1007L, "一往无前", "从底层技术自研到全场景覆盖,重估小米新十年|雷军|苹果|小...\n" +"2024年7月23日 2020年,雷军在小米集团成立十周年的演讲上,为小米定下“技术为本、性价比为纲、做最酷的产品”三大铁律。随后的三年,尽管小米短暂陷入低谷,但集团上下的动作未.."));list.add(new BlogTitle(1008L, "百草园", "著名武术巨星马保国喜欢吃苹果,西瓜,葡萄,热爱传扬武术,具有良好的武德"));return list;}/*** 构架一条记录,更新索引时候用* @param id* @return*/private BlogTitle getBlogListById(Long id) {return new BlogTitle(1000L, "Springboot 零基础入门", "springboot基础(1):IDEA构建Springboot项目的操作步骤如下");}/*** 查询指定id* @param id* @return*/private BlogTitle selectById(Long id) {List<BlogTitle> blogList = getBlogList();for (BlogTitle blogTitle : blogList) {if (blogTitle.getId().equals(id)) {return blogTitle;}}return null;}/*** 查询指定id* @param id* @return*/private BlogTitle selectById(String id) {return selectById(Long.parseLong(id));}/*** 创建索引** @return*/@GetMapping("/createIndex")public String createIndex() {List<BlogTitle> list = getBlogList();//查询数据源,这里直接构造一些数据// 创建文档的集合Collection<Document> docs = new ArrayList<>();for (BlogTitle blogTitle : list) {// 创建文档对象Document document = new Document();// StringField: 这个 Field 用来构建一个字符串Field,不分析,会索引,Field.Store控制存储// LongPoint、IntPoint 等类型存储数值类型的数据。会分析,会索引,不存储,如果想存储数据还需要使用 StoredField// StoredField: 这个 Field 用来构建不同类型,不分析,不索引,会存储// TextField: 如果是一个Reader, 会分析,会索引,,Field.Store控制存储document.add(new StringField("id", String.valueOf(blogTitle.getId()), Field.Store.YES));// Field.Store.YES, 将原始字段值存储在索引中。这对于短文本很有用,比如文档的标题,它应该与结果一起显示。// 值以其原始形式存储,即在存储之前没有使用任何分析器。document.add(new TextField("title", blogTitle.getTitle(), Field.Store.YES));// Field.Store.NO,可以索引,分词,不将字段值存储在索引中。// 个人理解:说白了就是为了省空间,如果回表查询,其实无所谓,如果不回表查询,需要展示就要保存,设为YES,无需展示,设为NO即可。document.add(new TextField("description", blogTitle.getDescription(), Field.Store.NO));docs.add(document);}// 引入IK分词器,如果需要解决上面版本冲突报错的问,使用`new MyIKAnalyzer()`即可Analyzer analyzer = new IKAnalyzer();// 索引写出工具的配置对象IndexWriterConfig conf = new IndexWriterConfig(analyzer);// 设置打开方式:OpenMode.APPEND 会在索引库的基础上追加新索引。OpenMode.CREATE会先清空原来数据,再提交新的索引conf.setOpenMode(IndexWriterConfig.OpenMode.CREATE);// 索引目录类,指定索引在硬盘中的位置,我的设置为D盘的indexDir文件夹// 创建索引的写出工具类。参数:索引的目录和配置信息try (Directory directory = FSDirectory.open(FileSystems.getDefault().getPath("d:\\indexDir"));IndexWriter indexWriter = new IndexWriter(directory, conf)) {// 把文档集合交给IndexWriterindexWriter.addDocuments(docs);// 提交indexWriter.commit();} catch (Exception e) {log.error("创建索引失败", e);return "创建索引失败";}return "创建索引成功";}

3. 简单搜索

这里示例只搜索了title标题部分的内容,改成description则搜索内容部分

/*** 简单搜索*/@RequestMapping("/searchText")public List<BlogTitle> searchText(String text) throws IOException, ParseException {Directory directory = FSDirectory.open(FileSystems.getDefault().getPath("d:\\indexDir"));// 索引读取工具IndexReader reader = DirectoryReader.open(directory);// 索引搜索工具IndexSearcher searcher = new IndexSearcher(reader);// 创建查询解析器,两个参数:默认要查询的字段的名称,分词器QueryParser parser = new QueryParser("title", new IKAnalyzer());// 创建查询对象Query query = parser.parse(text);// 获取前十条记录TopDocs topDocs = searcher.search(query, 10);// 获取总条数log.info("本次搜索共找到" + topDocs.totalHits + "条数据");// 获取得分文档对象(ScoreDoc)数组.SocreDoc中包含:文档的编号、文档的得分ScoreDoc[] scoreDocs = topDocs.scoreDocs;List<BlogTitle> list = new ArrayList<>();for (ScoreDoc scoreDoc : scoreDocs) {// 取出文档编号int docId = scoreDoc.doc;// 根据编号去找文档Document doc = reader.document(docId);String id = doc.get("id");BlogTitle content = selectById(id);list.add(content);}return list;}

4. 更新索引

当有记录发生变化时,需要更新某条记录

 /*** 更新索引** @return*/@GetMapping("/updateIndex")public String update() {// 创建配置对象IndexWriterConfig conf = new IndexWriterConfig(new IKAnalyzer());// 创建目录对象// 创建索引写出工具try (Directory directory = FSDirectory.open(FileSystems.getDefault().getPath("d:\\indexDir"));IndexWriter writer = new IndexWriter(directory, conf)) {// 获取更新的数据,这里只是演示BlogTitle blogTitle = getBlogListById(1001L);// 创建新的文档数据Document doc = new Document();doc.add(new StringField("id", blogTitle.getId()+"", Field.Store.YES));doc.add(new TextField("title", blogTitle.getTitle(), Field.Store.YES));doc.add(new TextField("description", blogTitle.getDescription(), Field.Store.YES));writer.updateDocument(new Term("id", blogTitle.getId()+""), doc);// 提交writer.commit();} catch (Exception e) {log.error("更新索引失败", e);return "更新索引失败";}return "更新索引成功";}

5. 删除索引

某条记录不存在了,需要删除

 /*** 删除索引** @return*/@GetMapping("/deleteIndex")public String deleteIndex() {// 创建配置对象IndexWriterConfig conf = new IndexWriterConfig(new IKAnalyzer());// 创建目录对象// 创建索引写出工具try (Directory directory = FSDirectory.open(FileSystems.getDefault().getPath("d:\\indexDir"));IndexWriter writer = new IndexWriter(directory, conf)) {// 根据词条进行删除writer.deleteDocuments(new Term("id", "1001"));// 提交writer.commit();} catch (Exception e) {log.error("删除索引失败", e);return "删除索引失败";}return "删除索引成功";}

6. 删除全部索引

想要废弃全部索引,删除全部索引

 /*** 删除全部索引** @return*/@GetMapping("/deleteAllIndex")public String deleteAllIndex() {// 创建配置对象IndexWriterConfig conf = new IndexWriterConfig(new IKAnalyzer());// 创建目录对象// 创建索引写出工具try (Directory directory = FSDirectory.open(FileSystems.getDefault().getPath("d:\\indexDir"));IndexWriter writer = new IndexWriter(directory, conf)) {// 删除全部索引writer.deleteAll();// 提交writer.commit();} catch (Exception e) {log.error("删除索引失败", e);return "删除索引失败";}return "删除索引成功";}

Springboot整合Lucene复杂搜索

1. 同时标题和内容中查找关键词

例如 想要搜索关键词,而关键词既可能在标题中,也可能在文本内容中。

/*** 一个关键词,在多个字段里面搜索*/@RequestMapping("/searchTextMore")public List<BlogTitle> searchTextMore(String text) throws IOException, ParseException {String[] str = {"title", "description"};Directory directory = FSDirectory.open(FileSystems.getDefault().getPath("d:\\indexDir"));// 索引读取工具IndexReader reader = DirectoryReader.open(directory);// 索引搜索工具IndexSearcher searcher = new IndexSearcher(reader);// 创建查询解析器,两个参数:默认要查询的字段的名称,分词器MultiFieldQueryParser parser = new MultiFieldQueryParser(str, new IKAnalyzer());// 创建查询对象Query query = parser.parse(text);// 获取前十条记录TopDocs topDocs = searcher.search(query, 100);// 获取总条数log.info("本次搜索共找到" + topDocs.totalHits + "条数据");// 获取得分文档对象(ScoreDoc)数组.SocreDoc中包含:文档的编号、文档的得分ScoreDoc[] scoreDocs = topDocs.scoreDocs;List<BlogTitle> list = new ArrayList<>();for (ScoreDoc scoreDoc : scoreDocs) {// 取出文档编号int docId = scoreDoc.doc;// 根据编号去找文档Document doc = reader.document(docId);String id = doc.get("id");BlogTitle content = selectById(id);list.add(content);}return list;}

2. 搜索结果高亮显示关键词

/*** 搜索结果高亮显示*/@RequestMapping("/searchTextHighlighter")public List<BlogTitle> searchTextHighlighter(String text) throws IOException, ParseException, InvalidTokenOffsetsException {String[] str = {"title", "description"};Directory directory = FSDirectory.open(FileSystems.getDefault().getPath("d:\\indexDir"));// 索引读取工具IndexReader reader = DirectoryReader.open(directory);// 索引搜索工具IndexSearcher searcher = new IndexSearcher(reader);// 创建查询解析器,两个参数:默认要查询的字段的名称,分词器MultiFieldQueryParser parser = new MultiFieldQueryParser(str, new IKAnalyzer());// 创建查询对象Query query = parser.parse(text);// 获取前100条记录TopDocs topDocs = searcher.search(query, 100);// 获取总条数log.info("本次搜索共找到" + topDocs.totalHits + "条数据");//高亮显示SimpleHTMLFormatter simpleHTMLFormatter = new SimpleHTMLFormatter("<span style='color:red'>", "</span>");Highlighter highlighter = new Highlighter(simpleHTMLFormatter, new QueryScorer(query));//高亮后的段落范围在100字内Fragmenter fragmenter = new SimpleFragmenter(100);highlighter.setTextFragmenter(fragmenter);// 获取得分文档对象(ScoreDoc)数组.SocreDoc中包含:文档的编号、文档的得分ScoreDoc[] scoreDocs = topDocs.scoreDocs;List<BlogTitle> list = new ArrayList<>();for (ScoreDoc scoreDoc : scoreDocs) {// 取出文档编号int docId = scoreDoc.doc;// 根据编号去找文档Document doc = reader.document(docId);String id = doc.get("id");BlogTitle content = selectById(id);//处理高亮字段显示String title = highlighter.getBestFragment(new IKAnalyzer(), "title", doc.get("title"));if (title == null) {title = content.getTitle();}// 因为创建索引的时候description设置的Field.Store.NO,所以这里doc没有description数据,取不出来值,设为YES则可以,可以断点看一下,直接设置content.getDescription()也可以高亮显示
//            String description = highlighter.getBestFragment(new IKAnalyzer(), "description", doc.get("description"));
//            if (description == null) {
//                description = content.getDescription();
//            }
//            content.setDescription(description);content.setDescription(content.getDescription());content.setTitle(title);list.add(content);}return list;}

3. 分页搜索

/*** 分页搜索*/@RequestMapping("/searchTextPage")public List<BlogTitle> searchTextPage(String text) throws IOException, ParseException, InvalidTokenOffsetsException {String[] str = {"title", "description"};int page = 1;int pageSize = 5;Directory directory = FSDirectory.open(FileSystems.getDefault().getPath("d:\\indexDir"));// 索引读取工具IndexReader reader = DirectoryReader.open(directory);// 索引搜索工具IndexSearcher searcher = new IndexSearcher(reader);// 创建查询解析器,两个参数:默认要查询的字段的名称,分词器MultiFieldQueryParser parser = new MultiFieldQueryParser(str, new IKAnalyzer());// 创建查询对象Query query = parser.parse(text);// 分页获取数据TopDocs topDocs = searchByPage(page, pageSize, searcher, query);// 获取总条数log.info("本次搜索共找到" + topDocs.totalHits + "条数据");//高亮显示SimpleHTMLFormatter simpleHTMLFormatter = new SimpleHTMLFormatter("<span style='color:red'>", "</span>");Highlighter highlighter = new Highlighter(simpleHTMLFormatter, new QueryScorer(query));//高亮后的段落范围在100字内Fragmenter fragmenter = new SimpleFragmenter(100);highlighter.setTextFragmenter(fragmenter);// 获取得分文档对象(ScoreDoc)数组.SocreDoc中包含:文档的编号、文档的得分ScoreDoc[] scoreDocs = topDocs.scoreDocs;List<BlogTitle> list = new ArrayList<>();for (ScoreDoc scoreDoc : scoreDocs) {// 取出文档编号int docId = scoreDoc.doc;// 根据编号去找文档Document doc = reader.document(docId);BlogTitle content = selectById(doc.get("id"));//处理高亮字段显示String title = highlighter.getBestFragment(new IKAnalyzer(), "title", doc.get("title"));if (title == null) {title = content.getTitle();}String description = highlighter.getBestFragment(new IKAnalyzer(), "description", content.getDescription());content.setDescription(description);content.setTitle(title);list.add(content);}return list;}private TopDocs searchByPage(int page, int perPage, IndexSearcher searcher, Query query) throws IOException {TopDocs result;if (query == null) {log.info(" Query is null return null ");return null;}ScoreDoc before = null;if (page != 1) {TopDocs docsBefore = searcher.search(query, (page - 1) * perPage);ScoreDoc[] scoreDocs = docsBefore.scoreDocs;if (scoreDocs.length > 0) {before = scoreDocs[scoreDocs.length - 1];}}result = searcher.searchAfter(before, query, perPage);return result;}

4. 多关键词联合搜索

/*** 多关键词搜索*/@GetMapping("/searchTextMoreParam")public List<BlogTitle> searchTextMoreParam(String text) throws IOException, ParseException, InvalidTokenOffsetsException {String[] str = {"title", "description"};Directory directory = FSDirectory.open(FileSystems.getDefault().getPath("d:\\indexDir"));// 索引读取工具IndexReader reader = DirectoryReader.open(directory);// 索引搜索工具IndexSearcher searcher = new IndexSearcher(reader);//多条件查询构造BooleanQuery.Builder builder = new BooleanQuery.Builder();// 条件一MultiFieldQueryParser parser = new MultiFieldQueryParser(str, new IKAnalyzer());// 创建查询对象Query query = parser.parse(text);builder.add(query, BooleanClause.Occur.MUST);// 条件二// TermQuery不使用分析器所以建议匹配不分词的Field域(StringField, )查询,比如价格、分类ID号等。这里只能演示个ID了。。。Query termQuery = new TermQuery(new Term("id", "1001"));builder.add(termQuery, BooleanClause.Occur.MUST);// 获取前十条记录TopDocs topDocs = searcher.search(builder.build(), 100);// 获取总条数log.info("本次搜索共找到" + topDocs.totalHits + "条数据");//高亮显示SimpleHTMLFormatter simpleHTMLFormatter = new SimpleHTMLFormatter("<span style='color:red'>", "</span>");Highlighter highlighter = new Highlighter(simpleHTMLFormatter, new QueryScorer(query));//高亮后的段落范围在100字内Fragmenter fragmenter = new SimpleFragmenter(100);highlighter.setTextFragmenter(fragmenter);// 获取得分文档对象(ScoreDoc)数组.SocreDoc中包含:文档的编号、文档的得分ScoreDoc[] scoreDocs = topDocs.scoreDocs;List<BlogTitle> list = new ArrayList<>();for (ScoreDoc scoreDoc : scoreDocs) {// 取出文档编号int docId = scoreDoc.doc;// 根据编号去找文档Document doc = reader.document(docId);BlogTitle content = selectById(doc.get("id"));//处理高亮字段显示String title = highlighter.getBestFragment(new IKAnalyzer(), "title", doc.get("title"));if (title == null) {title = content.getTitle();}String description = highlighter.getBestFragment(new IKAnalyzer(), "description", content.getDescription());content.setDescription(description);content.setTitle(title);list.add(content);}return list;}

关于IK扩展分词

什么分词?什么是扩展分词?

什么是分词?
例如“书上的苹果掉下来砸中了牛顿”。句子中的苹果就构成一个分词,苹和前面的文字的不能组词构成“的苹”,也不能组词叫“果掉”,但是苹和果构成一个组词,叫做苹果。
什么是扩展分词?
就是希望增加分词,而分词器本身不带有的,例如我市的著名武术巨星马保国同志在巴黎奥运会取得了马拉松金牌的好成绩。分词器并不会提前内置马保国的名字,但是马保国是一个人名,构成一个分词,在搜索的时候单独搜索其中任何一个字都没有意义,需要搜索整个关键词,才构成实际价值。因此分词中添加马保国,搜索时IK分词会将其解释为一个分词。

自定义分词和停止分词

  1. 需要在resources目录下新建文件IKAnalyzer.cfg.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties><comment>IKAnalyzer扩展配置</comment><!--用户的扩展字典 --><entry key="ext_dict">extend.dic</entry><!--用户扩展停止词字典 --><entry key="ext_stopwords">stop.dic</entry>
</properties>
  1. 添加分词文件
    extend.dic
马保国
王小羽
  1. 添加停止分词

与分词的意思相反,就是破坏构成的分词,不希望既有分词构建分词
stop.dic

的
地
吗

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 在HarmonyOS中使用RelativeContainer实现相对布局
  • EmguCV学习笔记 C# 第4章 图像处理
  • SpringBoot依赖之Spring Data Redis 一 String类型
  • 【数据结构入门】二叉树之堆的实现
  • 百日筑基第五十七天-虚拟线程
  • 前端框架(三件套)
  • git cherry-pick命令使用分享
  • Android UI:PopupWindow:API
  • 《机器学习》 逻辑回归 大批量数据的下采样 <8>
  • git本地仓库同步到远程仓库
  • 黑客入门教程(非常详细)从零基础入门到精通,看完这一篇就够了
  • EasyExcel_通过模板导出(多sheet、列表、图片)
  • Linux ps命令详解
  • 【NI国产替代】NI‑9235四分之一桥应变计,8通道C系列应变/桥输入模块
  • 基于LSTM的交通流量预测算法及Python实现
  • 【译】理解JavaScript:new 关键字
  • 11111111
  • Linux各目录及每个目录的详细介绍
  • Linux学习笔记6-使用fdisk进行磁盘管理
  • MaxCompute访问TableStore(OTS) 数据
  • Object.assign方法不能实现深复制
  • uva 10370 Above Average
  • vagrant 添加本地 box 安装 laravel homestead
  • Vue学习第二天
  • 大主子表关联的性能优化方法
  • 得到一个数组中任意X个元素的所有组合 即C(n,m)
  • 诡异!React stopPropagation失灵
  • 基于Javascript, Springboot的管理系统报表查询页面代码设计
  • 简单数学运算程序(不定期更新)
  • 今年的LC3大会没了?
  • 悄悄地说一个bug
  • 融云开发漫谈:你是否了解Go语言并发编程的第一要义?
  • 线上 python http server profile 实践
  • postgresql行列转换函数
  • Spring第一个helloWorld
  • 我们雇佣了一只大猴子...
  • ​中南建设2022年半年报“韧”字当头,经营性现金流持续为正​
  • # centos7下FFmpeg环境部署记录
  • # 利刃出鞘_Tomcat 核心原理解析(七)
  • ( )的作用是将计算机中的信息传送给用户,计算机应用基础 吉大15春学期《计算机应用基础》在线作业二及答案...
  • (06)Hive——正则表达式
  • (19)夹钳(用于送货)
  • (C语言)逆序输出字符串
  • (PADS学习)第二章:原理图绘制 第一部分
  • (ZT) 理解系统底层的概念是多么重要(by趋势科技邹飞)
  • (附源码)ssm捐赠救助系统 毕业设计 060945
  • (附源码)ssm智慧社区管理系统 毕业设计 101635
  • (回溯) LeetCode 78. 子集
  • (接口自动化)Python3操作MySQL数据库
  • (十二)Flink Table API
  • (五)大数据实战——使用模板虚拟机实现hadoop集群虚拟机克隆及网络相关配置
  • .form文件_一篇文章学会文件上传
  • .Net 6.0--通用帮助类--FileHelper
  • .NET Core 网络数据采集 -- 使用AngleSharp做html解析
  • .NET/C# 使用反射注册事件