Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
OpenDocCN
think-dast-zh
提交
91d4384a
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 搜索 >>
提交
91d4384a
编写于
9月 20, 2017
作者:
W
wizardforcel
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
ch9
上级
0aeb04af
变更
1
隐藏空白更改
内联
并排
Showing
1 changed file
with
157 addition
and
0 deletion
+157
-0
9.md
9.md
+157
-0
未找到文件。
9.md
0 → 100644
浏览文件 @
91d4384a
# 第九章 `Map`接口
> 原文:[Chapter 9 The Map interface](http://greenteapress.com/thinkdast/html/thinkdast010.html)
> 译者:[飞龙](https://github.com/wizardforcel)
> 协议:[CC BY-NC-SA 4.0](http://creativecommons.org/licenses/by-nc-sa/4.0/)
> 自豪地采用[谷歌翻译](https://translate.google.cn/)
在接下来的几个练习中,我介绍了
`Map`
接口的几个实现。其中一个基于哈希表,这可以说是所发明的最神奇的数据结构。另一个是类似的
`TreeMap`
,不是很神奇,但它有附加功能,它可以按顺序迭代元素。
你将有机会实现这些数据结构,然后我们将分析其性能。
但是在我们可以解释哈希表之前,我们将从一个
`Map`
开始,它使用键值对的
`List`
来简单实现。
## 9.1 实现`MyLinearMap`
像往常一样,我提供启动代码,你将填写缺少的方法。这是
`MyLinearMap`
类定义的起始:
```
java
public
class
MyLinearMap
<
K
,
V
>
implements
Map
<
K
,
V
>
{
private
List
<
Entry
>
entries
=
new
ArrayList
<
Entry
>();
```
该类使用两个类型参数,
`K`
是键的类型,
`V`
是值的类型。
`MyLinearMap`
实现
`Map`
,这意味着它必须提供
`Map`
接口中的方法。
`MyLinearMap`
对象具有单个实例变量,
`entries`
,这是一个
`Entry`
的
`ArrayList`
对象。每个
`Entry`
都包含一个键值对。这里是定义:
```
java
public
class
Entry
implements
Map
.
Entry
<
K
,
V
>
{
private
K
key
;
private
V
value
;
public
Entry
(
K
key
,
V
value
)
{
this
.
key
=
key
;
this
.
value
=
value
;
}
@Override
public
K
getKey
()
{
return
key
;
}
@Override
public
V
getValue
()
{
return
value
;
}
}
```
`Entry`
没有什么,只是一个键和一个值的容器。该定义内嵌在
`MyLinearList`
中,因此它使用相同类型的参数,
`K`
和
`V`
。
这就是你做这个练习所需的所有东西,所以让我们开始吧。
## 9.2 练习 7
在本书的仓库中,你将找到此练习的源文件:
+
`MyLinearMap.java`
包含练习的第一部分的起始代码。
+
`MyLinearMapTest.java`
包含
`MyLinearMap`
的单元测试。
你还会找到 Ant 构建文件
`build.xml`
。
运行
`ant build`
来编译源文件。然后运行
`ant MyLinearMapTest`
;几个测试应该失败,因为你有一些任务要做。
首先,填写
`findEntry`
的主体。这是一个辅助方法,不是
`Map`
接口的一部分,但是一旦你让它工作,你可以在几种方法中使用它。给定一个目标键(Key),它应该搜索条目(Entry)并返回包含目标的条目(按照键,而不是值),或者如果不存在则返回
`null`
。请注意,我提供了
`equals`
,正确比较两个键并处理
`null`
。
你可以再次运行
`ant MyLinearMapTest`
,但即使你的
`findEntry`
是正确的,测试也不会通过,因为
`put`
不完整。
填充
`put`
。你应该阅读
`Map.put`
的文档,
<http://thinkdast.com/listput>
,以便你知道应该做什么。你可能希望从一个版本开始,其中
`put`
始终添加新条目,并且不会修改现有条目;这样你可以先测试简单的情况。或者如果你更加自信,你可以一次写出整个东西。
一旦你
`put`
正常工作,测试
`containsKey`
应该通过。
阅读
`Map.get`
的文档,
<http://thinkdast.com/listget>
,然后填充方法。再次运行测试。
最后,阅读
`Map.remove`
的文档,
<http://thinkdast.com/maprem>
并填充方法。
到了这里,所有的测试都应该通过。恭喜!
## 9.3 分析`MyLinearMap`
这一节中,我展示了上一个练习的答案,并分析核心方法的性能。这里是
`findEntry`
和
`equals`
。
```
java
private
Entry
findEntry
(
Object
target
)
{
for
(
Entry
entry:
entries
)
{
if
(
equals
(
target
,
entry
.
getKey
()))
{
return
entry
;
}
}
return
null
;
}
private
boolean
equals
(
Object
target
,
Object
obj
)
{
if
(
target
==
null
)
{
return
obj
==
null
;
}
return
target
.
equals
(
obj
);
}
```
`equals`
的运行时间可能取决于
`target`
键和键的大小 ,但通常不取决于条目的数量,
`n`
。那么
`equals`
是常数时间。
在
`findEntry`
中,我们可能会很幸运,并在一开始就找到我们要找的键,但是我们不能指望它。一般来说,我们要搜索的条目数量与
`n`
成正比,所以
`findEntry`
是线性的。
大部分的
`MyLinearMap`
核心方法使用
`findEntry`
,包括
`put`
,
`get`
,和
`remove`
。这就是他们的样子:
```
java
public
V
put
(
K
key
,
V
value
)
{
Entry
entry
=
findEntry
(
key
);
if
(
entry
==
null
)
{
entries
.
add
(
new
Entry
(
key
,
value
));
return
null
;
}
else
{
V
oldValue
=
entry
.
getValue
();
entry
.
setValue
(
value
);
return
oldValue
;
}
}
public
V
get
(
Object
key
)
{
Entry
entry
=
findEntry
(
key
);
if
(
entry
==
null
)
{
return
null
;
}
return
entry
.
getValue
();
}
public
V
remove
(
Object
key
)
{
Entry
entry
=
findEntry
(
key
);
if
(
entry
==
null
)
{
return
null
;
}
else
{
V
value
=
entry
.
getValue
();
entries
.
remove
(
entry
);
return
value
;
}
}
```
`put`
调用
`findEntry`
之后,其他一切都是常数时间。记住这个
`entries`
是一个
`ArrayList`
,所以降魔为添加元素平均是常数时间。如果键已经在映射中,我们不需要添加条目,但我们必须调用
`entry.getValue`
和
`entry.setValue`
,而这些都是常数时间。把它们放在一起,
`put`
是线性的。
同样,
`get`
也是线性的。
`remove`
稍微复杂一些,因为
`entries.remove`
可能需要从一开始或中间删除
`ArrayList`
的一个元素,并且需要线性时间。但是没关系:两个线性运算仍然是线性的。
总而言之,核心方法都是线性的,这就是为什么我们将这个实现称为
`MyLinearMap`
(嗒嗒!)。
如果我们知道输入的数量很少,这个实现可能会很好,但是我们可以做得更好。实际上,
`Map`
所有的核心方法都是常数时间的实现。当你第一次听到这个消息时,可能似乎觉得不可能。实际上我们所说的是,你可以在常数时间内大海捞针,不管海有多大。这是魔法。
我们不是将条目存储在一个大的
`List`
中,而是把它们分解成许多短的列表。对于每个键,我们将使用哈希码(在下一节中进行说明)来确定要使用的列表。
使用大量的简短列表比仅仅使用一个更快,但正如我将解释的,它不会改变增长级别;核心功能仍然是线性的。但还有一个技巧:如果我们增加列表的数量来限制每个列表的条目数,就会得到一个恒定时间的映射。你会在下一个练习中看到细节,但是首先要了解哈希!
在下一章中,我将介绍一种解决方案,分析
`Map`
核心方法的性能,并引入更有效的实现。
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录