Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
OpenDocCN
think-dast-zh
提交
b4f26c31
T
think-dast-zh
项目概览
OpenDocCN
/
think-dast-zh
8 个月 前同步成功
通知
0
Star
26
Fork
13
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
T
think-dast-zh
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
提交
Issue看板
前往新版Gitcode,体验更适合开发者的 AI 搜索 >>
提交
b4f26c31
编写于
9月 23, 2017
作者:
W
wizardforcel
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
ch14.
上级
165f5b41
变更
1
隐藏空白更改
内联
并排
Showing
1 changed file
with
150 addition
and
0 deletion
+150
-0
14.md
14.md
+150
-0
未找到文件。
14.md
0 → 100644
浏览文件 @
b4f26c31
# 第十四章 持久化
在接下来的几个练习中,我们将返回到网页搜索引擎的构建。为了回顾,搜索引擎的组件是:
抓取:我们需要一个程序,可以下载一个网页,解析它,并提取文本和任何其他页面的链接。
索引:我们需要一个索引,可以查找检索项并找到包含它的页面。
检索:我们需要一种方法,从索引中收集结果,并识别与检索项最相关的页面。
如果你做了练习 8.3,你使用 Java 映射实现了一个索引。在本练习中,我们将重新审视索引器,并创建一个新版本,将结果存储在数据库中。
如果你做了练习 7.4,你创建了一个爬虫,它跟踪它找到的第一个链接。在下一个练习中,我们将制作一个更通用的版本,将其查找到的每个链接存储在队列中,并对其进行排序。
然后,最后,你将处理检索问题。
在这些练习中,我提供较少的起始代码,你将做出更多的设计决策。这些练习也更加开放。我会提出一些最低限度的目标,你应该尝试实现它们,但如果你想挑战自己,有很多方法可以让你更深入。
现在,让我们开始编写一个新版本的索引器。
## 14.1 Redis
索引器的之前版本,将索引存储在两个数据结构中:
`TermCounter`
将检索词映射为网页上显示的次数,以及
`Index`
将检索词映射为出现的页面集合。
这些数据结构存储在正在运行的 Java 程序的内存中,这意味着当程序停止运行时,索引会丢失。仅在运行程序的内存中存储的数据称为“易失的”,因为程序结束时会消失。
在创建它的程序结束后,仍然存在的数据称为“持久的”。通常,存储在文件系统中的文件,以及存储在数据库中的数据是持久的。
使数据持久化的一种简单方法是,将其存储在文件中。在程序结束之前,它可以将其数据结构转换为 JSON 格式(
<http://thinkdast.com/json>
),然后将它们写入文件。当它再次启动时,它可以读取文件并重建数据结构。
但这个解决方案有几个问题:
+
读取和写入大型数据结构(如 Web 索引)会很慢。
+
整个数据结构可能不适合单个运行程序的内存。
+
如果程序意外结束(例如,由于断电),则自程序上次启动以来所做的任何更改都将丢失。
一个更好的选择是提供持久存储的数据库,并且能够读取和写入数据库的部分,而无需读取和写入整个数据。
有多种数据库管理系统(DBMS)提供不同的功能。你可以在
<http://thinkdast.com/database>
阅读概述。
我为这个练习推荐的数据库是 Redis,它提供了类似于 Java 数据结构的持久数据结构。具体来说,它提供:
字符串列表,与 Java 的
`List`
类似。
哈希,类似于 Java 的
`Map`
。
字符串集合,类似于 Java 的
`Set`
。
> 译者注:另外还有类似于 Java 的`LinkedHashSet`的有序集合。
Redis 是一个“键值数据库”,这意味着它包含的数据结构(值)由唯一的字符串(键)标识。Redis 中的键与 Java 中的引用相同:它标识一个对象。我们稍后会看到一些例子。
## 14.2 Redis 客户端和服务端
Redis 通常运行为远程服务;其实它的名字代表“REmote DIctionary Server”(远程字典服务,字典其实就是映射)。为了使用 Redis,你必须在某处运行 Redis 服务器,然后使用 Redis 客户端连接到 Redis 服务器。有很多方法可用于设置服务器,也有许多你可以使用的客户端。对于这个练习,我建议:
不要自己安装和运行服务器,请考虑使用像 RedisToGo(
<http://thinkdast.com/redistogo>
)这样的服务,它在云主机运行 Redis。他们提供了一个免费的计划(配置),有足够的资源用于练习。
对于客户端,我推荐 Jedis,它是一个 Java 库,提供了使用 Redis 的类和方法。
以下是更详细的说明,以帮助你开始使用:
+
在 RedisToGo 上创建一个帐号,网址为
<http://thinkdast.com/redissign>
,并选择所需的计划(可能是免费的起始计划)。
+
创建一个“实例”,它是运行 Redis 服务器的虚拟机。如果你单击“实例”选项卡,你将看到你的新实例,由主机名和端口号标识。例如,我有一个名为
`dory-10534`
的实例。
+
单击实例名称来访问配置页面。记下页面顶部附近的网址,如下所示:
```
redis://redistogo:1234567feedfacebeefa1e1234567@dory.redistogo.com:10534
```
这个 URL 包含服务器的主机名称
`dory.redistogo.com`
,端口号
`10534`
和连接到服务器所需的密码,它是中间较长的字母数字的字符串。你将需要此信息进行下一步。
## 14.3 制作基于 Redis 的索引
在本书的仓库中,你将找到此练习的源文件:
+
`JedisMaker.java`
包含连接到 Redis 服务器并运行几个 Jedis 方法的示例代码。
+
`JedisIndex.java`
包含此练习的起始代码。
+
`JedisIndexTest.java`
包含
`JedisIndex`
的测试代码。
+
`WikiFetcher.java`
包含我们在以前的练习中看到的代码,用于阅读网页并使用
`jsoup`
进行解析。
你还将需要这些文件,你在以前的练习中碰到过:
`Index.java`
使用 Java 数据结构实现索引。
`TermCounter.java`
表示从检索项到其频率的映射。
`WikiNodeIterable.java`
迭代
`jsoup`
生成的 DOM 树中的节点。
如果你有这些文件的有效版本,你可以使用它们进行此练习。如果你没有进行以前的练习,或者你对你的解决方案毫无信心,则可以从
`solutions `
文件夹复制我的解决方案。
第一步是使用 Jedis 连接到你的 Redis 服务器。
`JedisMaker.java`
展示了如何实现。它从文件读取你的 Redis 服务器的信息,连接到它并使用你的密码登录,然后返回一个可用于执行 Redis 操作的 Jedis 对象。
如果你打开
`JedisMaker.java`
,你应该看到
`JedisMaker`
类,它是一个帮助类,它提供静态方法
`make`
,它创建一个 Jedis 对象。一旦该对象认证完毕,你可以使用它来与你的 Redis 数据库进行通信。
`JedisMaker`
从名为
`redis_url.txt`
的文件读取你的 Redis 服务器信息,你应该放在目录
`src/resources`
中:
+
使用文本编辑器创建并编辑
`ThinkDataStructures/code/src/resources/redis_url.txt`
。
+
粘贴服务器的 URL。如果你使用的是 RedisToGo,则 URL 将如下所示:
```
redis://redistogo:1234567feedfacebeefa1e1234567@dory.redistogo.com:10534
```
因为此文件包含你的 Redis 服务器的密码,你不应将此文件放在公共仓库中。为了帮助你避免意外避免这种情况,仓库包含
`.gitignore`
文件,使文件难以(但不是不可能)放入你的仓库。
现在运行
`ant build`
来编译源文件,以及
`ant JedisMaker`
来运行
`main`
中的示例代码:
```
java
public
static
void
main
(
String
[]
args
)
{
Jedis
jedis
=
make
();
// String
jedis
.
set
(
"mykey"
,
"myvalue"
);
String
value
=
jedis
.
get
(
"mykey"
);
System
.
out
.
println
(
"Got value: "
+
value
);
// Set
jedis
.
sadd
(
"myset"
,
"element1"
,
"element2"
,
"element3"
);
System
.
out
.
println
(
"element2 is member: "
+
jedis
.
sismember
(
"myset"
,
"element2"
));
// List
jedis
.
rpush
(
"mylist"
,
"element1"
,
"element2"
,
"element3"
);
System
.
out
.
println
(
"element at index 1: "
+
jedis
.
lindex
(
"mylist"
,
1
));
// Hash
jedis
.
hset
(
"myhash"
,
"word1"
,
Integer
.
toString
(
2
));
jedis
.
hincrBy
(
"myhash"
,
"word2"
,
1
);
System
.
out
.
println
(
"frequency of word1: "
+
jedis
.
hget
(
"myhash"
,
"word1"
));
System
.
out
.
println
(
"frequency of word1: "
+
jedis
.
hget
(
"myhash"
,
"word2"
));
jedis
.
close
();
}
```
这个示例展示了数据类型和方法,你在这个练习中最可能使用它们。当你运行它时,输出应该是:
```
Got value: myvalue
element2 is member: true
element at index 1: element2
frequency of word1: 2
frequency of word2: 1
```
下一节中我会解释代码的工作原理。
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录