Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
OpenDocCN
think-dast-zh
提交
f19aef01
T
think-dast-zh
项目概览
OpenDocCN
/
think-dast-zh
9 个月 前同步成功
通知
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 搜索 >>
提交
f19aef01
编写于
9月 23, 2017
作者:
W
wizardforcel
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
ch14
上级
b4f26c31
变更
1
隐藏空白更改
内联
并排
Showing
1 changed file
with
179 addition
and
3 deletion
+179
-3
14.md
14.md
+179
-3
未找到文件。
14.md
浏览文件 @
f19aef01
# 第十四章 持久化
> 原文:[Chapter 14 Persistence](http://greenteapress.com/thinkdast/html/thinkdast015.html)
> 译者:[飞龙](https://github.com/wizardforcel)
> 协议:[CC BY-NC-SA 4.0](http://creativecommons.org/licenses/by-nc-sa/4.0/)
> 自豪地采用[谷歌翻译](https://translate.google.cn/)
在接下来的几个练习中,我们将返回到网页搜索引擎的构建。为了回顾,搜索引擎的组件是:
抓取:我们需要一个程序,可以下载一个网页,解析它,并提取文本和任何其他页面的链接。
索引:我们需要一个索引,可以查找检索项并找到包含它的页面。
检索:我们需要一种方法,从索引中收集结果,并识别与检索项最相关的页面。
+
抓取:我们需要一个程序,可以下载一个网页,解析它,并提取文本和任何其他页面的链接。
+
索引:我们需要一个索引,可以查找检索项并找到包含它的页面。
+
检索:我们需要一种方法,从索引中收集结果,并识别与检索项最相关的页面。
如果你做了练习 8.3,你使用 Java 映射实现了一个索引。在本练习中,我们将重新审视索引器,并创建一个新版本,将结果存储在数据库中。
...
...
@@ -146,5 +154,173 @@ frequency of word2: 1
下一节中我会解释代码的工作原理。
## 14.4 Redis 数据类型
Redis 基本上是一个从键到值的映射,键是字符串,值可以是字符串,也可以是几种数据类型之一。最基本的 Redis 数据类型是字符串。我将用斜体书写 Redis 类型,来区别于 Java 类型。
为了向数据库添加一个字符串,请使用
`jedis.set`
,类似于
`Map.put`
; 参数是新的键和相应的值。为了查找一个键并获取其值,请使用
`jedis.get`
:
```
java
jedis
.
set
(
"mykey"
,
"myvalue"
);
String
value
=
jedis
.
get
(
"mykey"
);
```
在这个例子中,键是
`"mykey"`
,值是
`"myvalue"`
。
Redis 提供了一个集合结构,类似于 Java 的
`Set<String>`
。为了向 Redis 集合添加元素,你可以选择一个键来标识集合,然后使用
`jedis.sadd`
:
```
java
jedis
.
sadd
(
"myset"
,
"element1"
,
"element2"
,
"element3"
);
boolean
flag
=
jedis
.
sismember
(
"myset"
,
"element2"
);
```
你不必用单独的步骤来创建集合。如果不存在,Redis 会创建它。在这种情况下,它会创建一个名为
`myset`
的集合,包含三个元素。
`jedis.sismember`
方法检查元素是否在一个集合中。添加元素和检查成员是常数时间的操作。
Redis 还提供了一个列表结构,类似于 Java 的
`List<String>`
。
`jedis.rpush`
方法在末尾(右端)向列表添加元素:
```
java
jedis
.
rpush
(
"mylist"
,
"element1"
,
"element2"
,
"element3"
);
String
element
=
jedis
.
lindex
(
"mylist"
,
1
);
```
同样,你不必在开始添加元素之前创建结构。此示例创建了一个名为
`mylist`
的列表,其中包含三个元素。
`jedis.lindex`
方法使用整数索引,并返回列表中指定的元素。添加和访问元素是常数时间的操作。
最后,Redis 提供了一个哈希结构,类似于 Java 的
`Map<String, String>`
。
`jedis.hset`
方法为哈希表添加新条目:
```
java
jedis
.
hset
(
"myhash"
,
"word1"
,
Integer
.
toString
(
2
));
String
value
=
jedis
.
hget
(
"myhash"
,
"word1"
);
```
此示例创建一个名为的
`myhash`
哈希表,其中包含一个条目,该条目从将键
`word1`
映射到值
`"2"`
。
键和值都是字符串,所以如果我们要存储
`Integer`
,在我们调用
`hset`
之前,我们必须将它转换为
`String`
。当我们使用
`hget`
查找值时,结果是
`String`
,所以我们可能必须将其转换回
`Integer`
。
使用 Redis 的哈希表可能会令人困惑,因为我们使用一个键来标识我们想要的哈希表,然后用另一个键标识哈希表中的值。在 Redis 的上下文中,第二个键被称为“字段”,这可能有助于保持清晰。所以类似
`myhash`
的“键”标志一个特定的哈希表,然后类似
`word1`
的“字段”标识一个哈希表中的值。
对于许多应用程序,Redis 哈希表中的值是整数,所以 Redis 提供了一些特殊的方法,比如
`hincrby`
将值作为数字来处理:
```
java
jedis
.
hincrBy
(
"myhash"
,
"word2"
,
1
);
```
这个方法访问
`myhash`
,获取
`word2`
的当前值(如果不存在则为
`0`
),将其递增
`1`
,并将结果写回哈希表。
在哈希表中,设置,获取和递增条目是常数时间的操作。
你可以在
<http://thinkdast.com/redistypes>
上阅读 Redis 数据类型的更多信息。
## 14.5 练习 11
这个时候,你可以获取一些信息,你需要使用它们来创建搜索引擎的索引,它将结果储存在 Redis 数据库中。
现在运行
`ant JedisIndexTest`
。它应该失败,因为你有一些工作要做!
`JedisIndexTest`
测试了这些方法:
+
`JedisIndex`
,这是构造器,它接受
`Jedis`
对象作为参数。
+
`indexPage`
,它将一个网页添加到索引中;它需要一个
`StringURL`
和一个
`jsoup Elements`
对象,该对象包含应该建立索引的页面元素。
+
`getCounts`
,它接收检索词,并返回
`Map<String, Integer>`
,包含检索词到它在页面上的出现次数的映射。
以下是如何使用这些方法的示例:
```
java
WikiFetcher
wf
=
new
WikiFetcher
();
String
url1
=
"http://en.wikipedia.org/wiki/Java_(programming_language)"
;
Elements
paragraphs
=
wf
.
readWikipedia
(
url1
);
Jedis
jedis
=
JedisMaker
.
make
();
JedisIndex
index
=
new
JedisIndex
(
jedis
);
index
.
indexPage
(
url1
,
paragraphs
);
Map
<
String
,
Integer
>
map
=
index
.
getCounts
(
"the"
);
```
如果我们在结果
`map`
中查看
`url1`
,我们应该得到
`339`
,这是 Java 维基百科页面(即我们保存的版本)中,
`the`
出现的次数。
如果我们再次索引相同的页面,新的结果将替换旧的结果。
将数据结构从 Java 翻译成 Redis 的一个建议是:记住 Redis 数据库中的每个对象都以唯一的键标识,它是一个字符串。如果同一数据库中有两种对象,则可能需要向键添加前缀来区分它们。例如,在我们的解决方案中,我们有两种对象:
+
我们将
`URLSet`
定义为 Redis 集合,它包含
`URL`
,
`URL`
又包含给定检索词。每个
`URLSet`
的键的起始是
`"URLSet:"`
,所以要获取包含单词
`the`
的 URL,我们使用键
`"URLSet:the"`
来访问该集合。
+
我们将
`TermCounter`
定义为 Redis 哈希表,将出现在页面上的每个检索词映射到它的出现次数。
`TermCounter`
每个键的开头都以
`"TermCounter:"`
开头,以我们正在查找的页面的 URL 结尾。
在我的实现中,每个术语都有一个
`URLSet`
,每个索引页面都有一个
`TermCounter`
。我提供两个辅助方法,
`urlSetKey`
和
`termCounterKey`
来组装这些键。
## 14.6 更多建议(如果你需要的话)
到了这里,你拥有了完成练习所需的所有信息,所以如果准备好了就可以开始了。但是我有几个建议,你可能想先阅读它:
+
对于这个练习,我提供的指导比以前的练习少。你必须做出一些设计决策;特别是,你将必须弄清楚如何将问题分解成,你可以一次性测试的部分,然后将这些部分组合成一个完整的解决方案。如果你尝试一次写出整个项目,而不测试较小的部分,调试可能需要很长时间。
+
使用持久性数据的挑战之一是它是持久的。存储在数据库中的结构可能会在每次运行程序时发生更改。如果你弄乱了数据库,你将不得不修复它或重新开始,然后才能继续。为了帮助你控制住自己,我提供的方法叫
`deleteURLSets`
,
`deleteTermCounters`
和
`deleteAllKeys`
,你可以用它来清理数据库,并重新开始。你也可以使用
`printIndex`
来打印索引的内容。
+
每次调用 Jedis 的方法时,你的客户端会向服务器发送一条消息,然后服务器执行你请求的操作并发回消息。如果执行许多小操作,可能需要很长时间。你可以通过将一系列操作分组为一个
`Transaction`
,来提高性能。
例如,这是一个简单的
`deleteAllKeys`
版本:
```
java
public
void
deleteAllKeys
()
{
Set
<
String
>
keys
=
jedis
.
keys
(
"*"
);
for
(
String
key:
keys
)
{
jedis
.
del
(
key
);
}
}
```
每次调用
`del`
时,都需要从客户端到服务器的双向通信。如果索引包含多个页面,则该方法需要很长时间来执行。我们可以使用
`Transaction`
对象来加速:
```
java
public
void
deleteAllKeys
()
{
Set
<
String
>
keys
=
jedis
.
keys
(
"*"
);
Transaction
t
=
jedis
.
multi
();
for
(
String
key:
keys
)
{
t
.
del
(
key
);
}
t
.
exec
();
}
```
`jedis.multi`
返回一个
`Transaction`
对象,它提供
`Jedis`
对象的所有方法。但是当你调用
`Transaction`
的方法时,它不会立即执行该操作,并且不与服务器通信。在你调用
`exec`
之前,它会保存一批操作。然后它将所有保存的操作同时发送到服务器,这通常要快得多。
## 14.7 几个设计提示
现在你真的拥有了你需要的所有信息;你应该开始完成练习。但是如果你卡住了,或者如果你真的不知道如何开始,你可以再来一些提示。
在运行测试代码之前,不要阅读以下内容,尝试一些基本的 Redis 命令,并在
`JedisIndex.java`
中编写几个方法。
好的,如果你真的卡住了,这里有一些你可能想要处理的方法:
```
java
/**
* 向检索词相关的集合中添加 URL
*/
public
void
add
(
String
term
,
TermCounter
tc
)
{}
/**
* 查找检索词并返回 URL 集合
*/
public
Set
<
String
>
getURLs
(
String
term
)
{}
/**
* 返回检索词出现在给定 URL 中的次数
*/
public
Integer
getCount
(
String
url
,
String
term
)
{}
/**
* 将 TermCounter 的内容存入 Redis
*/
public
List
<
Object
>
pushTermCounterToRedis
(
TermCounter
tc
)
{}
```
这些是我在解决方案中使用的方法,但它们绝对不是将项目分解的唯一方法。所以如果他们有帮助,请接受这些建议,但是如果没有,请忽略它们。
对于每种方法,请考虑首先编写测试。当你弄清楚如何测试一个方法时,你经常会了解如何编写它。
祝你好运!
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录