❣博主主页: 33的博客❣
▶️文章专栏分类:项目日记◀️
🚚我的代码仓库: 33的代码仓库🚚
🫵🫵🫵关注我带你了解更多项目内容
1.前言
通过这几个月的学习,已经能熟练运用Java的知识解决一些实际问题了,接下来就利用所学知识做一个站内搜索引擎。搜索引擎的核心功能就是查找到一组和用户输入的次相关联的相关联的网页。
2.搜索引擎
搜索引擎分为站内搜索引擎和互联网搜索引擎。
站内搜索引擎:在一个特定网站或网络服务中实现的搜索功能,用于搜索该网站或服务内的内容。
互联网搜索引擎:则是指搜索整个互联网上的内容,例如谷歌、百度、必应等搜索引擎。
当我们搜索某一个词时会展示相关联的信息:
对于一个搜索引擎来说,需要获得许多网页,那么搜索引擎的网页是如何获取到的呢?主要设计到“爬虫”这样的程序。假设已经爬取到了1亿个网页,再根据用户的输入信息,在这些网页中进行暴力搜索。但暴力搜索的效率是非常低的,这个时候我们就需要一种特殊的数据结构:倒排索引。
文档:每一个待搜索的网页
正排索引:根据文档id查找文档内容
倒排索引:根据搜索词查看文档id
3.项目模块设计
实现一个针对JAVA文档的搜索引擎,首先需要获取java相关文档,再进行倒排索引和正排索引。我们可以使用“爬虫”把这些文档获取到,但针对java文档来说有更简单的方案:直接从官网下载文档压缩包。Java API文档下载官网
模块划分:
索引模块
1.扫描下载好的文档,分析文档内容,构建正排索引和倒排索引,并把索引内容保存到文件种
2.加载制作好的索引,并提供API实现查正排和倒排
搜索模块
1.调用索引模块实现一个搜索的完整过程
输入:用户查询词
输出:完整的搜索结果(包含很多条记录,每一条记录包含标题url、标题、正文)
web模块
实现一个简单的web程序,能过通过网页的形式和用户进行交互
4.分词操作
用户在搜索引擎中输入的不一定真的是一个词,可能是一句话。如果是一句话那么就需要把一句话拆分为几个词例如:
我喜欢吃火锅!可以拆分为:我/喜欢/吃/火锅/!/
分词原理:
- 基于词库
尝试把所有的词都进行穷举,把这些穷举的结果放入词典中。然后就可以依次取句子的内容,每隔一个字取词典中查一下,每隔两个字去词典查一下… - 基于统计
通过人工标注/直接统计收集到很多的“语料库”。
在Java中能够实现分词的第三方库有很多,我们使用ansj,可以去maven仓库下载:ansj。
5.索引模块
创建一个类parser来实现索引模块
基本步骤:
1.枚举出JAVA API中的所有html文档
2.解析HTML文档(解析标题、url、正文)
3.把内存构造的索引保存到磁盘
5.1枚举所有的HTML文件
private static final String INPUT_PATH="D:\\doc_searcher_index\\jdk-17.0.11_doc-all\\docs\\api";//JAVA API下载路径
public void run() {
//1.枚举出INPUT_PATH下的所有html文件
ArrayList<File> fileList=new ArrayList<>();
enumFile(INPUT_PATH,fileList);
//2.解析文档内容
//3.把内存构造的索引保存到磁盘
}
private static void enumFile(String inputPath, ArrayList<File> fileList) {
//1.遍历所有目录,并把root下的所有目录罗列处理
File root=new File(inputPath);
File[] files=root.listFiles();
//递归遍历所有目录筛选出以html结尾的
for (File f:files){
if(f.isDirectory()){
enumFile(f.getAbsolutePath(),fileList);
}else if(f.getAbsolutePath().endsWith(".html")) {
fileList.add(f);
}
}
}
5.2解析HTML
private void parseHTML(File f) {
//1.解析HTML标题
String title=parseTitle(f);
//2.解析HTML的URL
String url=parseUrl(f);
//3.解析HTML的正文
String content=parseContentByRegex(f);
}
5.2.1解析title
当我们去解析一个html文件的title的时候,我们会发现巧妙的发现文件的title和文件名一致。
所以获取title时,我们只需要获取文件名再去除“.html”后缀即可。
public static String parseTitle(File f) {
String title=f.getName();
return title.substring(0,title.length()-".html".length());
}
5.2.2解析url
我们对比在线文档和离线文档的url区别:
所以,可以直接进行字符串的拼接:
public static String parseUrl(File f) {
String part1="https://docs.oracle.com/en/java/javase/17/docs/api/";
String part2=f.getAbsolutePath().substring(INPUT_PATH.length());
return part1+part2;
}
5.2.3解析正文
在html中有许多标签,我们并不需要这些标签以及scrip我们利用可以利用正则表达式进行替换。在进行匹配的时候我们要使用非贪婪匹配使用?即匹配符合要求得最短内容。
public String parseContentByRegex(File f){
//读取文件内容放入String
String content=readFile(f);
//替换script
content=content.replaceAll("<script.*?>(.*?)</script>"," ");
//替换普通html标签
content=content.replaceAll("<.*?>"," ");
//把多个空格合并成一个
content=content.replaceAll("\\s+"," ");
return content;
}
在读取文件文件的时候可以选取BufferedReader也可以选取FileReader那么为什么我们要选取BufferedReader?
BufferedReader与FileReader区别
- FileReader是用于读取字符流的类,而BufferedReader是对FileReader的包装类,它提供了缓冲机制,可以一次读取多个字符,以提高读取效率。
- 使用FileReader读取文件时,每次读取一个字符,性能较低;而使用BufferedReader读取文件时,可以指定缓冲区大小,一次性读取多个字符,减少了IO操作,提高了性能。
- BufferedReader提供了readLine()方法,可以一次读取一行文本,方便处理文本文件;而FileReader没有提供读取一行文本的方法,需要自行实现。
缓冲机制:
BufferedReader的缓冲机制是指它在读取文件时,会将读取的数据先存储在内存的缓冲区中,待缓冲区填满或需要时再进行实际的IO读取操作。这样可以减少实际的IO操作次数,提高读取文件的效率。
具体来说,BufferedReader内部维护了一个字符数组作为缓冲区,当调用read()方法时,它会首先检查缓冲区中是否还有数据,如果有则直接从缓冲区中读取;如果缓冲区已经为空,则会通过底层输入流(如FileReader)进行IO操作,读取一定数量的数据填充到缓冲区中,并从缓冲区返回数据。这种缓冲机制可以减少磁盘IO的次数,提高读取文件的效率,特别是在读取大文件或进行频繁读取操作时表现更为明显。
private String readFile(File f) {
try(BufferedReader bufferedReader=new BufferedReader(new FileReader(f))) {
StringBuilder content=new StringBuilder();
while (true){
int ret=bufferedReader.read();
if (ret==-1){
break;
}
char c=(char) ret;
if (c=='\n'||c=='\r'){
c=' ';
}
content.append(c);
}
return content.toString();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
6.总结
这篇文章主要介绍了项目的各个项目背景,模块设计,以及实现了索引解析模块,在解析模块我们要学会利用正则表达式进行一些替换操作,以及在读取文件时不同类的选择,再下一篇文章中将继续实现索引制作模块。
下期预告:项目日记(二)