文章目录
- 实现索引构建
- 解析标题
- getName () 和 getAbsolutePath () 的区别
- 截掉 .html
- 完整代码逻辑
- 解析 URL
- 实现 URL 拼接
- 完整代码逻辑
- 测试代码
- 解析正文
- 实现思路
- 读取内容操作的实现
- 完整代码逻辑
- 测试代码
实现索引构建
-
一条搜索信息,就包含了标题、描述、展示 URL。这些信息就来自于要解析的
HTML
-
因此当前的解析
HTML
操作,就是要把这个HTML
文件的标题、描述、URL 给获取到- 描述可以视为是正文的一段摘要
- 因此要想得到描述,就得先得到整个正文
- 所以我们先解析正文,后面再说描述
要实现这个功能,基本的框架为:
java">public void run(){ // 2. 针对上面罗列出的文件路径,打开路径,读取文件内容,进行解析,并构建索引 for(File f : fileList) { System.out.println("开始解析: "+ f.getAbsolutePath()); // 通过这个方法来解析单个 HTML 文件 parseHTML(f); }
}
- 实现这个功能,我们封装一个
parseHTML()
方法。此方法需要完成:- 解析出
HTML
的标题 - 解析出
HTML
对应的URL
- 解析出
HTML
对应的正文(有了正文才有后续的描述)
- 解析出
java">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
方法
解析标题
我们可以通过获取文件名,来获取具具体的标题信息
java">private String parseTitle(File f) { f.getName();
}
getName () 和 getAbsolutePath () 的区别
我们可以写个代码测试一下:
java">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
” 的长度- 总长度 - 后半部分的长度 ==> 前半部分的长度 ==> 正是后半部分开始的第一个字符的下标
java">f.getName().substring(0, f.getName().length() - ".html".length())
.html
虽然是字符串常量,但是他同样也是一个String
类型,所以可以用.length
求长度
Java
中的计算长度,有多种不同的风格:
- 针对数组:
.length
属性- 针对字符串:
.length()
方法- 针对
List
等集合:.size()
方法
完整代码逻辑
java">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
,再和前面的固定前缀进行拼接
java">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 中既有
\
,也有/
,但是仍然能正常访问
完整代码逻辑
java">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;
}
测试代码
java">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
java">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
操作前面,加上一个处理换行操作的语句就可以了
java">if(c == '\n' || c == '\r'){// 为了去掉换行/回车,把换行/回车替换成空格即可c = ' ';
}
完整代码逻辑
java">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();
}
测试代码
java">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); }
}