From 0aeb04af68ff0f384fdc5969a03d9ab394252c69 Mon Sep 17 00:00:00 2001 From: wizardforcel <562826179@qq.com> Date: Tue, 12 Sep 2017 19:34:10 +0800 Subject: [PATCH] ch8 --- 8.md | 253 ++++++++++++++++++++++++++++++++++++++++++++++++++++ img/8-1.jpg | Bin 0 -> 22174 bytes 2 files changed, 253 insertions(+) create mode 100644 8.md create mode 100644 img/8-1.jpg diff --git a/8.md b/8.md new file mode 100644 index 0000000..eb21741 --- /dev/null +++ b/8.md @@ -0,0 +1,253 @@ +# 第八章 索引器 + +> 原文:[Chapter 8 Indexer](http://greenteapress.com/thinkdast/html/thinkdast009.html) + +> 译者:[飞龙](https://github.com/wizardforcel) + +> 协议:[CC BY-NC-SA 4.0](http://creativecommons.org/licenses/by-nc-sa/4.0/) + +> 自豪地采用[谷歌翻译](https://translate.google.cn/) + +目前,我们构建了一个基本的 Web 爬虫;我们下一步将是索引。在网页搜索的上下文中,索引是一种数据结构,可以查找检索词并找到该词出现的页面。此外,我们想知道每个页面上显示检索词的次数,这将有助于确定与该词最相关的页面。 + +例如,如果用户提交检索词“Java”和“编程”,我们将查找两个检索词并获得两组页面。带有“Java”的页面将包括 Java 岛屿,咖啡昵称以及编程语言的网页。具有“编程”一词的页面将包括不同编程语言的页面,以及该单词的其他用途。通过选择具有两个检索词的页面,我们希望消除不相关的页面,并找到 Java 编程的页面。 + +现在我们了解索引是什么,它执行什么操作,我们可以设计一个数据结构来表示它。 + +## 8.1 数据结构选取 + +索引的基本操作是查找;具体来说,我们需要能够查找检索词并找到包含它的所有页面。最简单的实现将是页面的集合。给定一个检索词,我们可以遍历页面的内容,并选择包含检索词的内容。但运行时间与所有页面上的总字数成正比,这太慢了。 + +一个更好的选择是一个映射(字典),它是一个数据结构,表示键值对的集合,并提供了一种方法,快速查找键以及相应值。例如,我们将要构建的第一个映射是`TermCounter`,它将每个检索词映射为页面中出现的次数。键是检索词,值是计数(也称为“频率”)。 + +Java 提供了`Map`的调用接口,它指定映射应该提供的方法;最重要的是: + ++ `get(key)`:此方法查找一个键并返回相应的值。 ++ `put(key, value)`:该方法向`Map`添加一个新的键值对,或者如果该键已经在映射中,它将替换与`key`关联的值。 + +Java 提供了几个`Map`实现,包括我们将关注的两个,`HashMap`以及`TreeMap`。在即将到来的章节中,我们将介绍这些实现并分析其性能。 + +除了检索词到计数的映射`TermCounter`之外,我们将定义一个被称为`Index`的类,它将检索词映射为出现的页面的集合。而这又引发了下一个问题,即如何表示页面集合。同样,如果我们考虑我们想要执行的操作,它们就指导了我们的决定。 + +在这种情况下,我们需要组合两个或多个集合,并找到所有这些集合中显示的页面。你可以将此操作看做集合的交集:两个集合的交集是出现在两者中的一组元素。 + +你可能猜到了,Java 提供了一个`Set`接口,来定义集合应该执行的操作。它实际上并不提供设置交集,但它提供了方法,使我们能够有​​效地实现交集和其他结合操作。核心的`Set`方法是: + ++ `add(element)`:该方法将一个元素添加到集合中;如果元素已经在集合中,则它不起作用。 ++ `contains(element)`:该方法检查给定元素是否在集合中。 + +Java 提供了几个`Set`实现,包括`HashSet`和`TreeSet`。 + +现在我们自顶向下设计了我们的数据结构,我们将从内到外实现它们,从`TermCounter`开始。 + +## 8.2 `TermCounter` + +`TermCounter`是一个类,表示检索词到页面中出现次数的映射。这是类定义的第一部分: + +```java +public class TermCounter { + + private Map map; + private String label; + + public TermCounter(String label) { + this.label = label; + this.map = new HashMap(); + } +} +``` + +实例变量`map`包含检索词到计数的映射,并且`label`标识检索词的来源文档;我们将使用它来存储 URL。 + +为了实现映射,我选择了`HashMap`,它是最常用的`Map`。在几章中,你将看到它是如何工作的,以及为什么它是一个常见的选择。 + +`TermCounter`提供`put`和`get`,定义如下: + +```java + public void put(String term, int count) { + map.put(term, count); + } + + public Integer get(String term) { + Integer count = map.get(term); + return count == null ? 0 : count; + } +``` + +`put`只是一个包装方法;当你调用`TermCounter`的`put`时,它会调用内嵌映射的`put`。 + +另一方面,`get`做了一些实际工作。当你调用`TermCounter`的`get`时,它会在映射上调用`get`,然后检查结果。如果该检索词没有出现在映射中,则`TermCount.get`返回`0`。`get`的这种定义方式使`incrementTermCount`的写入更容易,它需要一个检索词,并增加关联该检索词的计数器。 + +```java + public void incrementTermCount(String term) { + put(term, get(term) + 1); + } +``` + +如果这个检索词未见过,则`get`返回`0`;我们设为`1`,然后使用`put`向映射添加一个新的键值对。如果该检索词已经在映射中,我们得到旧的计数,增加`1`,然后存储新的计数,替换旧的值。 + +此外,`TermCounter`还提供了这些其他方法,来帮助索引网页: + +```java + public void processElements(Elements paragraphs) { + for (Node node: paragraphs) { + processTree(node); + } + } + + public void processTree(Node root) { + for (Node node: new WikiNodeIterable(root)) { + if (node instanceof TextNode) { + processText(((TextNode) node).text()); + } + } + } + + public void processText(String text) { + String[] array = text.replaceAll("\\pP", " "). + toLowerCase(). + split("\\s+"); + + for (int i=0; i> index = + new HashMap>(); + + public void add(String term, TermCounter tc) { + Set set = get(term); + + // if we're seeing a term for the first time, make a new Set + if (set == null) { + set = new HashSet(); + index.put(term, set); + } + // otherwise we can modify an existing Set + set.add(tc); + } + + public Set get(String term) { + return index.get(term); + } +``` + +实例变量`index`是每个检索词到一组`TermCounter`对象的映射。每个`TermCounter`表示检索词出现的页面。 + +`add`方法向集合添加新的`TermCounter`,它与检索词关联。当我们索引一个尚未出现的检索词时,我们必须创建一个新的集合。否则我们可以添加一个新的元素到一个现有的集合。在这种情况下,`set.add`修改位于`index`里面的集合,但不会修改`index`本身。我们唯一修改`index`的时候是添加一个新的检索词。 + +最后,`get`方法接受检索词并返回相应的`TermCounter`对象集。 + +这种数据结构比较复杂。回顾一下,`Index`包含`Map`,将每个检索词映射到`TermCounter`对象的`Set`,每个`TermCounter`包含一个`Map`,将检索词映射到计数。 + +![](img/8-1.jpg) + +图 8.1 `Index`的对象图 + +图 8.1 是展示这些对象的对象图。`Index`对象具有一个名为`index` 的`Map`实例变量。在这个例子中,`Map`只包含一个字符串,`"Java"`,它映射到一个`Set`,包含两个`TermCounter`对象的,代表每个出现单词“Java”的页面。 + +每个`TermCounter`包含`label`,它是页面的 URL,以及`map`,它是`Map`,包含页面上的单词和每个单词出现的次数。 + +`printIndex`方法展示了如何解压缩此数据结构: + +```java + public void printIndex() { + // loop through the search terms + for (String term: keySet()) { + System.out.println(term); + + // for each term, print pages where it appears and frequencies + Set tcs = get(term); + for (TermCounter tc: tcs) { + Integer count = tc.get(term); + System.out.println(" " + tc.getLabel() + " " + count); + } + } + } +``` + +外层循环遍历检索词。内层循环迭代`TermCounter`对象。 + +运行`ant build`来确保你的源代码已编译,然后运行`ant Index`。它下载两个维基百科页面,对它们进行索引,并打印结果;但是当你运行它时,你将看不到任何输出,因为我们已经将其中一个方法留空。 + +你的工作是填写`indexPage`,它需要一个 URL(一个`String`)和一个`Elements`对象,并更新索引。下面的注释描述了应该做什么: + +```java +public void indexPage(String url, Elements paragraphs) { + // 生成一个 TermCounter 并统计段落中的检索词 + + // 对于 TermCounter 中的每个检索词,将 TermCounter 添加到索引 +} +``` + +它能工作之后,再次运行`ant Index`,你应该看到如下输出: + +```java +... +configurations + http://en.wikipedia.org/wiki/Programming_language 1 + http://en.wikipedia.org/wiki/Java_(programming_language) 1 +claimed + http://en.wikipedia.org/wiki/Java_(programming_language) 1 +servletresponse + http://en.wikipedia.org/wiki/Java_(programming_language) 2 +occur + http://en.wikipedia.org/wiki/Java_(programming_language) 2 +``` + +当你运行的时候,检索词的顺序可能有所不同。 + +同样,运行`ant TestIndex`来确定完成了这部分练习。 diff --git a/img/8-1.jpg b/img/8-1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..6e86d29438d99d525facf1fde57d373f3643e4cf GIT binary patch literal 22174 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 literal 0 HcmV?d00001 -- GitLab