文章目录
实现索引构建
一条搜索信息,就包含了标题、描述、展示 URL。这些信息就来自于要解析的
HTML
因此当前的解析
HTML
操作,就是要把这个HTML
文件的标题、描述、URL 给获取到- 描述可以视为是正文的一段摘要
- 因此要想得到描述,就得先得到整个正文
- 所以我们先解析正文,后面再说描述
要实现这个功能,基本的框架为:
public void run(){
// 2. 针对上面罗列出的文件路径,打开路径,读取文件内容,进行解析,并构建索引
for(File f : fileList) {
System.out.println("开始解析: "+ f.getAbsolutePath());
// 通过这个方法来解析单个 HTML 文件
parseHTML(f);
}
}
- 实现这个功能,我们封装一个
parseHTML()
方法。此方法需要完成:- 解析出
HTML
的标题 - 解析出
HTML
对应的URL
- 解析出
HTML
对应的正文(有了正文才有后续的描述)
- 解析出
private void parseHTML(File f) {
// 1. 解析出 HTML 的标题
String title = parseTitle(f);
// 2. 解析出 HTML 对应的 URL
String url = parseUrl(f);
// 3. 解析出 HTML 对应的正文(有了正文才有后续的描述)
String content = parseContent(f);
}
- 由于代码比较复杂,我们将三个任务都分给不同的方法进行完成
整个过程为:
- 为了解析
HTML
,我们创建一个parseHTML
方法- 解析
HTML
之后,我们发现还要:- 解析标题,我们又创建了一个
parseTitle
方法- 解析
URL
,我们又创建了一个parseUrl
方法- 解析正文,我们又创建了一个
parseContent
方法
解析标题
我们可以通过获取文件名,来获取具具体的标题信息
private String parseTitle(File f) {
f.getName();
}
getName () 和 getAbsolutePath () 的区别
我们可以写个代码测试一下:
public class TestGetName {
public static void main(String[] args) {
File f = new File("D:\\My Computer\\02_Stricky\\02_Code\\01 比特Java班资料\\Java docs\\api\\java\\util\\ArrayList.html");
System.out.println(f.getAbsolutePath());
System.out.println(f.getName());
}
}
/**
D:\My Computer\02_Stricky\02_Code\01 比特Java班资料\Java docs\api\java\util\ArrayList.html
ArrayList.html
*/
getAbsolutePath
得到的是完整路径getName
得到的是完整路径最后的一截
截掉 .html
搜索结果的标题里面,是展示一个
ArrayList.html
好,还是展示ArrayList
好?
- 展示后者更好
- 大家都是
html
,加上也没什么意义- 各大搜索引擎里面的标题里面也没有
.html
所以我们就需要把当前得到的字符串进行截取,去掉后面的 .html
部分
- 这里我们使用
substring()
方法
substring()
方法的两种版本
- 只传一个参数
- 从
begin
开始截取,一直到结尾
- 传两个参数
- 从
begin
开始截取,到end
停止- 前闭后开
ArrayList.html
- 总长度:
14
.html
长度:5
.
这个位置的下标,就是总长度 - “.html
” 的长度- 总长度 - 后半部分的长度 ==> 前半部分的长度 ==> 正是后半部分开始的第一个字符的下标
f.getName().substring(0, f.getName().length() - ".html".length())
.html
虽然是字符串常量,但是他同样也是一个String
类型,所以可以用.length
求长度
Java
中的计算长度,有多种不同的风格:
- 针对数组:
.length
属性- 针对字符串:
.length()
方法- 针对
List
等集合:.size()
方法
完整代码逻辑
private String parseTitle(File f) {
String name = f.getName();
return name.substring(0, name.length() - ".html".length());
}
- 这样就可以直接通过文件名,获取到标题信息
解析 URL
在真实的搜索引擎中,展示 URL 和跳转 URL 是不同的 URL。但是我们当前情况就可以按照一个 URL 来处理
- 使用一个 URL,既作为展示 URL,也作为点击 URL
对于各大搜索引擎来说:
- 广告结果的话,需要根据点击计费
- 自然点击结果的话,需要根据点击来优化用户体验
实现 URL 拼接
Java API
文档,存在两份:
- 线上文档:
https://docs.oracle.com/javase/8/docs/api/index.html
- 线下文档:
D:\My Computer\02_Stricky\02_Code\01 比特 Java 班资料\docs\api\index.html
我们所期望的结果就是:用户点击搜索结果的时候,就能够跳转到对应的线上文档的页面。
- 我们最终的跳转 URL 以:
https://docs.oracle.com/javase/8/docs/api/
为固定前缀,然后根据当前本地文档所在的路径,去和前缀进行拼接 - 我们是可以通过
getAbsolutePath()
获取到本地文档路径的,形如D:\\My Computer\\02_Stricky\\02_Code\\01 比特Java班资料\\docs\\api\\java\\util\\ArrayList.html
,然后把后半部分提取出来:java\\util\\ArrayList.html
,再和前面的固定前缀进行拼接
public class TestURL {
private static final String INPUT_PATH = "D:\\My Computer\\02_Stricky\\02_Code\\01 比特Java班资料\\docs\\api\\";
public static void main(String[] args) {
File file = new File("D:\\My Computer\\02_Stricky\\02_Code\\01 比特Java班资料\\docs\\api\\java\\util\\ArrayList.html");
// 先获取到一个固定的前缀
String part1 = "https://docs.oracle.com/javase/8/docs/api/";
String part2 = file.getAbsolutePath().substring(INPUT_PATH.length());
String result = part1 + part2;
System.out.println(result);
}
}
//运行结果:
//https://docs.oracle.com/javase/8/docs/api/java\util\ArrayList.html
- 浏览器自身有容错能力,虽然在拼接出的 URL 中既有
\
,也有/
,但是仍然能正常访问
完整代码逻辑
private String parseUrl(File f) {
String part1 = "https://docs.oracle.com/javase/8/docs/";
String part2 = f.getAbsolutePath().substring(INPUT_PATH.length());
return part1 + part2;
}
测试代码
public class TestURL {
private static final String INPUT_PATH = "D:\\My Computer\\02_Stricky\\02_Code\\01 比特Java班资料\\docs\\api\\";
public static void main(String[] args) {
File file = new File("D:\\My Computer\\02_Stricky\\02_Code\\01 比特Java班资料\\docs\\api\\java\\util\\ArrayList.html");
// 先获取到一个固定的前缀
String part1 = "https://docs.oracle.com/javase/8/docs/api/";
String part2 = file.getAbsolutePath().substring(INPUT_PATH.length());
String result = part1 + part2;
System.out.println(result);
}
}
解析正文
一个完整的 HTML
文件,包含了
HTML
标签- 内容(Java 文档)
接下来,进行解析正文的操作,核心就是去掉HTML
文件中的标签
实现思路
实现去标签,有很多方法:
- 可以通过正则表达式来实现这里的去标签操作
[!quote] 正则表达式
- 可以认为是一种计算机中进行字符串匹配/处理的常见手段
- 核心就是通过一些特殊符号来描述字符串的特征,然后看某个字符串是否符合这些特征
去除 HTML
标签这个环节中,虽然正则表达式可以解决问题,但是用起来很麻烦,因此我们可以使用更简单粗暴的方式来实现这里的逻辑
- 依次读取
HTML
中的每个字符,然后针对判定每个字符- 若是
<
,那么就从这个位置开始,直到遇到>
位置,都不把这些字符放在结果中 - 若遇到的字符串不是
<
,就直接把当前的字符拷贝到一个结果中(StringBuilder
) - 在期间我们可以弄一个标志位
flag
,为true
就拷贝,为false
就不拷贝
- 若是
万一内容中存在
<
或者>
怎么办呢?
- 不会出现这种情况
HTML
中要求,<
使用<
来代替;>
使用>
来代替
读取内容操作的实现
我们在读文件的时候,有的时候是按照“字节“来读取,有的时候是按照“字符“来读取。在 Java 标准库中,既提供了能够按照字节读取的类(FileInputStream
),也提供了能按照字符来读取的类(FileReader
)
- 此时我们是按照字符来读取的,所以使用
FileReader
public String parseContent(File f) {
StringBuilder content = new StringBuilder();
try(FileReader fileReader = new FileReader(f)) {
boolean isCopy = true;
while (true) {
int ret= fileReader.read();
if(ret == -1) {
break;
}
char c = (char)ret;
if(isCopy){
if(c == '<'){
isCopy = false;
continue;
}
content.append(c);
}else {
if(c == '>'){
isCopy = true;
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
return content.toString();
}
- 使用一个
StringBuilder
类型的变量content
进行字符串的操作,方便后面进行字符拼接- 因为
StringBuilder
类型的变量直接使用append()
方法就可以在原content
后面加上字符
- 因为
- 将
new fileReader
的操作放在try
之后,可以省略关闭文件的操作 - 在循环中,
read()
的返回值ret == -1
的时候,代表读取操作结束,直接跳出循环。read()
的返回类型为int
,就是为了方便判断何时读取结束(等于-1
的时候)- 否则一直进行字符的读取操作,并且需要将
int
类型的ret
强转为char
,好进行后续的字符操作
isCopy
是开关,用来控制是否进行append
操作的false
(关锁):当识别到<
的时候就关锁,关锁后一定要进行continent
操作,跳出此次循环,不然就会恒执行append
操作。true
(开锁):当识别到>
的时候就开锁,进行字符的append
操作
- 最后要返回
content
里面的字符串
观察运行结果可以看到,正文里面包含了大量的换行操作。实际上当前获取到这个正文,目的是为了后面能够生成描述信息(一段话,肯定不能有空行)
- 所以我们肯定要把空行给去掉
我们只需要在append
操作前面,加上一个处理换行操作的语句就可以了
if(c == '\n' || c == '\r'){
// 为了去掉换行/回车,把换行/回车替换成空格即可
c = ' ';
}
完整代码逻辑
public String parseContent(File f) {
StringBuilder content = new StringBuilder();
try(FileReader fileReader = new FileReader(f)) {
boolean isCopy = true;
while (true) {
int ret= fileReader.read();
if(ret == -1) {
break;
}
char c = (char)ret;
if(isCopy){
if(c == '<'){
isCopy = false;
continue;
}
if(c == '\n' || c == '\r'){
// 为了去掉换行/回车,把换行/回车替换成空格即可
c = ' ';
}
content.append(c);
}else {
if(c == '>'){
isCopy = true;
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
return content.toString();
}
测试代码
public class TestParseContent {
public static void main(String[] args) throws FileNotFoundException {
Parser parser = new Parser();
File file = new File("D:\\My Computer\\02_Stricky\\02_Code\\01 比特Java班资料\\docs\\api\\java\\util\\ArrayList.html");
String result = parser.parseContent(file);
System.out.println(result);
}
}