Java Map集合

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
图1:使用JDeveloper创建并运行Map测试类
图2:在JDeveloper中使用执行监测器进行的性能监测查出应用程序中的瓶颈
核心Map
Java自带了各种Map类。这些Map类可归为三种类型:
通用Map,用于在应用程序中管理映射,通常在java.util程序包中实现
HashMap
Hashtable
它实际上是使用更快机制获取正值的同一函数。在1.4版中,HashMap类实现使用一个不同且更复杂的哈希函数,该函数基于Doug Lea的util.concurrent程序包(稍后我将更详细地再次介绍Doug Lea的类)。
图3:哈希工作原理
该图介绍了哈希映射的基本原理,但我们还没有对其进行详细介绍。我们的哈希函数将任意对象映射到一个数组位置,但如果两个不同的键映射到相同的位置,情况将会如何?这是一种必然发生的情况。在哈希映射的术语中,这称作冲突。Map处理这些冲突的方法是在索引位置处插入一个链接列表,并简单地将元素添加到此链接列表。因此,一个基于哈希的Map的基本put()方法可能如下所示
put(Object key, Object value)
将指定值与指定键相关联
clear()
从Map中删除所有映射
putAll(Map t)
将指定Map中的所有映射复制到此map
尽管您可能注意到,纵然假设忽略构建一个需要传递给putAll()的Map的开销,使用putAll()通常也并不比使用大量的put()调用更有效率,但putAll()的存在一点也不稀奇。这是因为,putAll()除了迭代put()所执行的将每个键值对添加到Map的算法以外,还需要迭代所传递的Map的元素。但应注意,putAll()在添加所有元素之前可以正确调整Map的大小,因此如果您未亲自调整Map的大小(我们将对此进行简单介绍),则putAll()可能比预期的更有效。
for (int i = 0; i < rem; i++) {
{
Map.Entry entry = (Map.Entry) keyValuePairs2[i];
Object key = entry.getKey();
Profilers in Oracle JDeveloper
Oracle JDeveloper包含一个嵌入的监测器,它测量内存和执行时间,使您能够快速识别代码中的瓶颈。我曾使用Jdeveloper的执行监测器监测HashMap的containsKey()和containsValue()方法,并很快发现containsKey()方法的速度比containsValue()方法慢很多(实际上要慢几个数量级!)。(参见图1和图2,以及随附文件中的Test2类)。
//循环遍历位于table[index]处的链接列表,以查明
//我们是否拥有此键项—如果拥有,则覆盖它
for (Entry e = table[index] ; e != nullBaidu Nhomakorabea; e = e.next) {
public Object put(Object key, Object value) {
//我们的内部数组是一个Entry对象数组
//Entry[] table;
//获取哈希码,并映射到一个索引
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % table.length;
表4:Map访问和测试方法:这些方法检索有关Map内容的信息但不更改Map内容。
get(Object key)
返回与指定键关联的值
containsKey(Object key)
如果Map包含指定键的映射,则返回true
containsValue(Object value)
如果此Map将一个或多个键映射到指定值,则返回true
我运行了一个小测试(随附文件中的Test1),该测试使用了HashMap,并使用以下两种方法对迭代Map元素的开销进行了比较:
int mapsize = aMap.size();
Iterator keyValuePairs1 = aMap.entrySet().iterator();
for (int i = 0; i < mapsize; i++)
{
Map.Entry entry = (Map.Entry) keyValuePairs1.next();
Object key = entry.getKey();
Object value = entry.getValue();
...
}
Object[] keyValuePairs2 = aMap.entrySet().toArray();
java.security.Provider
java.awt.RenderingHints
javax.swing.UIDefaults
一个用于帮助实现您自己的Map类的抽象类
AbstractMap
内部哈希:哈希映射技术
几乎所有通用Map都使用哈希映射。这是一种将元素映射到数组的非常简单的机制,您应了解哈希映射的工作原理,以便充分利用Map。
keySet()
返回Map中所包含键的Set视图。删除Set中的元素还将删除Map中相应的映射(键和值)
values()
返回map中所包含值的Collection视图。删除Collection中的元素还将删除Map中相应的映射(键和值)
访问元素
表4中列出了Map访问方法。Map通常适合按键(而非按值)进行访问。Map定义中没有规定这肯定是真的,但通常您可以期望这是真的。例如,您可以期望containsKey()方法与get()方法一样快。另一方面,containsValue()方法很可能需要扫描Map中的值,因此它的速度可能比较慢。
java.util中的集合类包含Java中某些最常用的类。最常用的集合类是List和Map。List的具体实现包括ArrayList和Vector,它们是可变大小的列表,比较适合构建、存储和操作任何类型对象的元素列表。List适用于按数值索引访问元素的情形。
Map提供了一个更通用的元素存储方法。Map集合类用于存储元素对(称作“键”和“值”),其中每个键映射到一个值。从概念上而言,您可以将List看作是具有数值键的Map。而实际上,除了List和Map都在定义java.util中外,两者并没有直接的联系。本文将着重介绍核心Java发行套件中附带的Map,同时还将介绍如何采用或实现更适用于您应用程序特定数据的专用Map。
isEmpty()
如果Map不包含键-值映射,则返回true
size()
返回Map中的键-值映射的数目
对使用containsKey()和containsValue()遍历HashMap中所有元素所需时间的测试表明,containsValue()所需的时间要长很多。实际上要长几个数量级!(参见图1和图2,以及随附文件中的Test2)。因此,如果containsValue()是应用程序中的性能问题,它将很快显现出来,并可以通过监测您的应用程序轻松地将其识别。这种情况下,我相信您能够想出一个有效的替换方法来实现containsValue()提供的等效功能。但如果想不出办法,则一个可行的解决方案是再创建一个Map,并将第一个Map的所有值作为键。这样,第一个Map上的containsValue()将成为第二个Map上更有效的containsKey()。
哈希映射结构由一个存储元素的内部数组组成。由于内部采用数组存储,因此必然存在一个用于确定任意键访问数组的索引机制。实际上,该机制需要提供一个小于数组大小的整数索引值。该机制称作哈希函数。在Java基于哈希的Map中,哈希函数将对象转换为一个适合内部数组的整数。您不必为寻找一个易于使用的哈希函数而大伤脑筋:每个对象都包含一个返回整数值的hashCode()方法。要将该值映射到数组,只需将其转换为一个正值,然后在将该值除以数组大小后取余数即可。以下是一个简单的、适用于任何对象的Java哈希函数
equals(Object o)
比较指定对象与此Map的等价性
hashCode()
返回此Map的哈希码
Map构建
Map定义了几个用于插入和删除元素的变换方法(表2)。
表2:Map更新方法:可以更改Map内容。
clear()
从Map中删除所有映射
remove(Object key)
从Map中删除键和关联的值
查看Map
迭代Map中的元素不存在直接了当的方法。如果要查询某个Map以了解其哪些元素满足特定查询,或如果要迭代其所有元素(无论原因如何),则您首先需要获取该Map的“视图”。有三种可能的视图(参见表3)
所有键值对—参见entrySet()
所有键—参见keySet()
所有值—参见values()
前两个视图均返回Set对象,第三个视图返回Collection对象。就这两种情况而言,问题到这里并没有结束,这是因为您无法直接迭代Collection对象或Set对象。要进行迭代,您必须获得一个Iterator对象。因此,要迭代Map的元素,必须进行比较烦琐的编码
表3:返回视图的Map方法:使用这些方法返回的对象,您可以遍历Map的元素,还可以删除Map中的元素。
entrySet()
返回Map中所包含映射的Set视图。Set中的每个元素都是一个Map.Entry对象,可以使用getKey()和getValue()方法(还有一个setValue()方法)访问后者的键元素和值元素
Iterator keyValuePairs = aMap.entrySet().iterator();
Iterator keys = aMap.keySet().iterator();
Iterator values = aMap.values().iterator();
值得注意的是,这些对象(Set、Collection和Iterator)实际上是基础Map的视图,而不是包含所有元素的副本。这使它们的使用效率很高。另一方面,Collection或Set对象的toArray()方法却创建包含Map所有元素的数组对象,因此除了确实需要使用数组中元素的情形外,其效率并不高。
int hashvalue = Maths.abs(key.hashCode()) % table.length;
(%二进制运算符(称作模)将左侧的值除以右侧的值,然后返回整数形式的余数。)
实际上,在1.4版发布之前,这就是各种基于哈希的Map类所使用的哈希函数。但如果您查看一下代码,您将看到
int hashvalue = (key.hashCode() & 0x7FFFFFFF) % table.length;
Properties
LinkedHashMap
IdentityHashMap
TreeMap
WeakHashMap
ConcurrentHashMap
专用Map,您通常不必亲自创建此类Map,而是通过某些其他类对其进行访问
java.util.jar.Attributes
javax.print.attribute.standard.PrinterStateReasons
Object value = entry.getValue();
...
}
此测试使用了两种测量方法:一种是测量迭代元素的时间,另一种测量使用toArray调用创建数组的其他开销。第一种方法(忽略创建数组所需的时间)表明,使用已从toArray调用中创建的数组迭代元素的速度要比使用Iterator的速度大约快30%-60%。但如果将使用toArray方法创建数组的开销包含在内,则使用Iterator实际上要快10%-20%。因此,如果由于某种原因要创建一个集合元素的数组而非迭代这些元素,则应使用该数组迭代元素。但如果您不需要此中间数组,则不要创建它,而是使用Iterator迭代元素。
了解Map接口和方法
Java核心类中有很多预定义的Map类。在介绍具体实现之前,我们先介绍一下Map接口本身,以便了解所有实现的共同点。Map接口定义了四种类型的方法,每个Map都包含这些方法。下面,我们从两个普通的方法(表1)开始对这些方法加以介绍。
表1:覆盖的方法。我们将这Object的这两个方法覆盖,以正确比较Map对象的等价性。
相关文档
最新文档