【从0做项目】Java搜索引擎(5)
阿华代码,不是逆风,就是我疯
你们的点赞收藏是我前进最大的动力!!
希望本文内容能够帮助到你!!
文章导读
阿华将发布项目复盘系列的文章,旨在:
1:手把手细致带大家从0到1做一个完整的项目,保证每2~3行代码都有详细的注解
2:通过文字+画图的方式,对项目进行整个复盘,更好的理解以及优化项目
3:总结自己的优缺点,扎实java相关技术栈,增强文档编写能力
零:项目结果展示
简述:在我的搜索引擎网站,用户进行关键字搜索,就可以查询到与这个关键字相关的java在线文档,(包含标题,关键字附近的简述,url),用户点击标题,即可跳转到相关在线文档,适用于JDK17版本。
一:导读
在篇章(4)中我们对制作索引引入了多线程进行优化,并且解决了线程安全问题。
1:文档4中遗留问题解答
解释第一次和第二次制作索引时间悬殊大的原因
这里我们第一次制作索引的时候其实是从硬盘中进行文件的读取解析操作。当第一次文件操作完毕后,这些文件就会在我们的系统缓冲区当中,第二次读就是从内存中解析了,所以快超多!
2:本文简述
二:完成文档搜索功能DocSearch
1:思路
第一步:我们对用户查询的内容进行分词
第二步:遍历分词结果,然后去倒排索引中查找(解释一下:根据多个key,来获取多个ArrayList<Weight>),每一个Weight里面有docId和weight权重值嘛
第三步:遍历分词结果,每一个分词,都会拿到一个Weight集合,我们把所有Weight按照权重的降序进行合并到一个List集合中去
第四步:遍历最后合并的List集合,去查正排,构建我们要返回的doc文档结果
2:代码展示
这里是优化后的版本,后续优化篇我会进行讲解。大家注意被注掉的代码,尤其是allTermResult这个引用。
public List<Result> search(String query){
//1:对query分词
List<Term> oldTerms = ToAnalysis.parse(query).getTerms();//未过滤的分词结果集合
List<Term> terms = new ArrayList<>();//过滤后的分词结果集合
//针对分词结果,使用暂停词表进行过滤
for(Term term : oldTerms){
if(stopWords.contains(term.getName())){
continue;
}
terms.add(term);
}
//2:对分词查倒排
List<List<Weight>> termResult = new ArrayList<>();
// List<Weight> allTermResult = new ArrayList<>();
for (Term term : terms){
String word = term.getName();
List<Weight> invertedList = index.getInverted(word);//如果查不到就返回一个null
if(invertedList == null){
continue;
}
// allTermResult.addAll(invertedList);//把集合中所有Weight对象都扔到allTermResult中
termResult.add(invertedList);
}
//3:[合并]对多个分词结果处发出的相同文档,进行权重合并
List<Weight> allTermResult = mergeResult(termResult);
//4: 按权重降序排序
allTermResult.sort(new Comparator<Weight>() {
@Override
public int compare(Weight o1, Weight o2) {
return o2.getWeight() - o1.getWeight();//降序排列
}
});
//5:查正排,构造出想要的Result,返回结果
List<Result> results = new ArrayList<>();
for(Weight weight : allTermResult){//对每一个Weight都构建result,可能最后的结果会很多,但是用户一般只看第一页查询出来的信息,一般懒得翻页
DocInfo docInfo = index.getDocInfo(weight.getDocId());//获取当前Weight对应的文档信息
Result result = new Result();
result.setTitle(docInfo.getTitle());
result.setUrl(docInfo.getUrl());
// result.setDesc(docInfo.getContent());//很明显把正文全部返回不合理
result.setDesc(GenDesc(docInfo.getContent(),oldTerms));//搞个正文简述,这个词前60个字符为起始,往后截取160个
results.add(result);
}
return results;
}
3:代码注意点
4:返回结果包装
这里我们主要返回的是文档的标题,url,以及正文。
(1)思考:返回全部正文是否合理?
很明显是不合理的,看搜狗搜索下面的正文描述是一个简要概述,这里我们采取的返回正文的策略是——以这个词为核心,从它前60个字符为起始位置,往后截取160个字符,这一段内容作为返回的正文结果
(2)思考:用户查询带空格或者标点符号等无关词怎么办
这里先卖个关子,后续优化篇章会全部干掉!
三:正文简述处理
思路:从它前60个字符为起始位置,往后截取160个字符
特殊情况进行考虑
1:分词在正文中没有出现
小伙伴们是不是觉得很奇怪,搜索的内容在正文中都没出现,你还给我返回这个文档,离谱!极端情况下是会存在的,这个分词在标题中出现了,但是正文中没有出现,这里的特殊情况,我们特殊处理。
处理方式:大于160个字符,就返回前160个,否则全部返回
2:分词下标小于60
按照这个策略:先确定这个词的位置下标,如果它的下标 < 60说明,他前面没有60个字符嘛。此时在判断从开头到结尾长度是否大于160,若是,则截取;否返回全部正文
3:返回结果
最后我们返回的是这个文档的一段描述
private String GenDesc(String content , List<Term> terms){
//遍历分词结果,看哪个
int firstPos = -1;
for(Term term : terms){
String word = term.getName();
// firstPos = content.toLowerCase().indexOf(" " + word + " ");//避免包含关系 例:查array 结果查到ArrayList
//使用正则表达式,indexOf不支持正则,我们曲线救国!!
content = content.toLowerCase().replaceAll("\\b" + word + "\\b" , " " + word + " ");//匹配标点符号和空格,全部替换成空格
firstPos = content.indexOf(" " + word + " ");//女少
if(firstPos >= 0){
break;//找到了位置,可能这个content中会包含多个term,我们只取第一个
}
}
//用户输入的词,在正文中没有出现,虽然有点扯,但还是处理一下这种情况
if(firstPos == -1){
if(content.length() > 160){
return content.substring(0,160) + "...";
}
return content;//我去测试了一下,还真有这种情况离谱!说明查的词在标题中出现了,但是正文没出现666,这里也要对正文的长度做一下判断
}
//截取一部分正文
String desc = "";
int descBeg = firstPos < 60 ? 0 : firstPos - 60;
if(descBeg + 160 > content.length()){
desc = content.substring(descBeg);
}else {
desc = content.substring(descBeg , descBeg + 160) + "...";
}
for(Term term : terms){
String word = term.getName();
desc = desc.replaceAll("(?i) " + word + " " , "<i> " + word + "</i> ");//正则忽略大小写全字段匹配,那头单词和尾单词呢?
}
return desc;
}
4:注意点
这里我们为了避免包含的一种关系——比如:查的是array 结果查到ArrayList,使用空格进行分割匹配。因为单词与单词之间是用空格分隔开来的嘛
但是这种策略是有问题的:当这个词出现的第一个或者最后一个位置的时候,就g了,后面会在优化篇章进行处理。
四:返回结果类
@Data
public class Result {
private String title;
private String url;
private String desc;//描述正文的概括
}
五:构造方法中进行索引结构加载
public DocSearcher() {
try {
index.load();//通过构造加载索引到内存中
loadStopWords();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
六:结果展示
Result对象中包含咱们的标题,url,正文。
但是有个问题,看这里var 巴拉巴拉一堆,很明显这个正文描述是有问题的?一看就是把前端代码整进来了,说明我们的解析html文档中的解析正文还是有问题的?为什么js的代码会整进来呢?这里先埋个伏笔!我们下期见。