Java Map 集合类简介请参考
Map接口
Map是一个将键映射为值的对象。一个映射不能包含重复键:每个键最多能映射一个值。Map接口如下所示:
public interface Map {
// Basic Operations
Object put(Object key, Object value);
Object get(Object key);
Object remove(Object key);
boolean containsKey(Object key);
boolean containsValue(Object value);
int size();
boolean isEmpty();
// Bulk Operations
void putAll(Map t);
void clear();
// Collection Views
public Set keySet();
public Collection values();
public Set entrySet();
// Interface for entrySet element
public interface Entry {
Object getKey();
Object getValue();
Object setValue(Object value);
}
}
JDK包含两个新的通用Map实现,一个是HashMap, 它将它的项存储在一个哈希表中,是一种最好的实现;另一个是TreeMap, 它将它的项存储在一个红-黑树上,它可保证迭代的顺序。 另外, Hashtable已被改进以实现Map。
与哈希表的比较
如果你使用过Hashtable, 你应该已经熟悉了Map的一般风格(当然Map是一个接口,而Hashtable是一个具体的实现)。 以下是它们的主要区别:
Map提供Collection视图,作为Enumeration对象的替代直接支持迭代过程。Collection视图 极大地提高了接口的可表达性,正如后续课程将讲到的。
Map允许你在键、值或键-值对上进行迭代;Hashtable则不提供第三个选项。
Map提供了在迭代过程中删除项的安全途径;Hashtable则不能。
进一步讲,Map修补了Hashtable接口上的某些小缺陷。 Hashtable具有一个称作contains的方法,如果Hashtable包含一个给定值,它将返回true。 从它的名字上理解, 你可能期望如果Hashtable包含一个给定的key, 这个方法也会返回一个true ,因为键是一个Hashtable的主要存取机制。 Map接口通过将这个方法重新命名为containsValue,从而消除了引起混乱的来源;同时也改善了接口的一致性: containsValue与containsKey可很好地对应并行。
基本操作
基本操作 (put, get, remove, containsKey, containsValue, s , a和isEmpty) 的功能与它们在Hashtable中的对等物非常相似。下面的简单程序针对参数列表中的词汇生成一个频率表。频率表将每个词和它在参数列表中所出现的次数相映射。
import java.util.*;
public class Freq { private static final Integer ONE = new Integer(1);
public static void main(String args[]) {
Map m = new HashMap();
// Initialize frequency table from command line
for (int i=0; i$#@60; args.length; i++) {
Integer freq = (Integer) m.get(args[i]);
m.put(args[i], (freq==null ? ONE :
new Integer(freq.intValue() + 1)));
}
System.out.println(m.size()+" distinct words detected:");
System.out.println(m);
}
}
有关这个程序的一个小技巧是put语句的第二个参数。这是一个条件表达式,它的效果是如果这个词汇以前从未被看到过,则将频率设置为one,如果这个词汇已经被看到,则设置为当前值加1 。让我们来运行这个程序:
% java Freq if it is to be it is up to me to delegate
8 distinct words detected:
{to=3, me=1, delegate=1, it=2, is=2, if=1, be=1, up=1}
假设你更喜欢以字母顺序排列的频率表,那么所有你要做的工作就是将Map的实现类型从HashMap改变为TreeMap. 这四个字母的改变,使该程序从相同的命令行生成了如下输出:
8 distinct words detected:
{be=1, delegate=1, if=1, is=2, it=2, me=1, to=3, up=1}
这个接口"酷"吗?怎么样?
正如Set和List接口一样, Map加强了对equals和hashCode方法的要求, 于是, 两个Map对象可做逻辑等同性比较而不必考虑它们的实现类型。 如果它们显示了相等的键-值映射, 则两个Map对象是相等的。
按惯例, 所有的Map实现可提供构造函数; 该构造函数提取一个Map对象并将这个新的Map初始化, 使之包含特定Map中的所有键-值映射。这个标准Map构造函数是为Collection实现而设计的标准对象集 构造函数的完全对等物。它允许调用者创建一个期望的实现类型的Map ; 该实现类型初始包含另一个Map的所有映射。 而不考虑其它Map的实现类型。例如,假设你有一个命名为m的Map, 则下列一行代码创建了一个新的HashMap, 它初始包含所有与m相同的键-值映射:
Map copy = new HashMap(m);
批量操作(Bulk Operations)
clear操作所完成的工作正象其词义上所表达的那样: 它从Map 中删除所有映射。putAll操作是Collection接口中的addAll操作的Map对等物; 它可将一个Map转储至另一个, 除此之外, 它还有一个更微妙的用处。假设一个Map被用来表示属性-值对(attribute-value pairs ) ; putAll操作将与标准Map构造函数一起提供一种用默认值创建属性表的简捷方法。以下是一个可演示此种技术的静态方法:
static Map newAttributeMap(Map defaults, Map overrides) {
Map result = new HashMap(defaults);
result.putAll(overrides);
return result;
}
Collection视图
Collection视图 方法允许以三种方式将一个Map作为一个Collection来视图:
keySet: 包含在Map中的键的Set。
values: 包含在Map中的值的Collection。 该Collection不是一个Set, 因为多个键可映射相同的值。
entrySet: 包含在Map中的键-值对的Set 。Map接口提供了一个小的被称作Map.Entry的嵌套接口,它是在这个Set中的元素的类型。
Collection视图提供了在Map上进行迭代的唯一方法。下面的例子给出了在一个Map上迭代键的标准惯用程序:
for (Iterator i=m.keySet().iterator(); i.hasNext(); )
System.out.println(i.next());
对值进行迭代的惯用程序是类似的。这是迭代键-值对的惯用程序:
for (Iterator i=m.entrySet().iterator(); i.hasNext(); ) {
Map.Entry e = (Map.Entry) i.next();
System.out.println(e.getKey() + ": " + e.getValue());
}
第一次提交这些惯用程序时,许多人考虑到每次调用一个Collection视图时,Map都必须创建一个新的Collection对象,因而担心其速度慢。请放心:这是不可能的。如果每次一个Map被要求给出一个特定的Collection视图时,没有道理Map不能总是返回相同的对象。这恰恰是所有JDK的Map实现所要作的事。
用所有三个Collection视图, 调用一个Iterator的remove的操作可从后备Map中删除相关项(假设该Map支持删除)。用entrySet视图, 通过在迭代过程中调用一个Map.Entry的setValue方法 (再一次假设该Map支持值的更改),也可能改变与一个键相关的值。 请注意这是在迭代过程中更改一个Map的唯一安全途径。
Collection视图支持它的所有形式的元素删除:remove, removeAll, retainAll, 和clear操作, 以及Iterator.remove操作 (然而,这是建立在假设后备Map支持元素删除的基础之上)。
Collection视图在任何情况下都不支持元素增加。对keySet和values视图这是无意义的,而对entrySet视
Collection视图的奇特用法: Map代数
在应用Collection视图时,批量操作 (containsAll, removeAll和retainAll) 是一个惊人的有力工具。假设你要了解一个Map是否是另一个的子映射(submap),也就是说,第一个Map是否包含第二个的全部键-值映射,请看下面惯用程序的小技巧:
if (m1.entrySet().containsAll(m2.entrySet())) {
..
}
对照类似行,假设你要了解两个Map对象是否包含所有所有相同键的映射
:
if (m1.keySet().equals(m2.keySet())) {
...
}
图来说,这是没必要的,因为后备Map的put和putAll提供了相同的功能。
假设你具有一个映射,代表一个属性-值对集合;以及两个sets, 表示要求的属性和允许的属性(允许的属性包括要求的属性)。下列代码可判定该属性映射是否符合那些限定条件,如果不符合,则打印详细的出错消息:
boolean valid = true;
Set attributes = attributeMap.keySet();
if(!attributes.containsAll(requiredAttributes)) {
Set missing = new HashSet(requiredAttributes);
missing.removeAll(attributes);
System.out.println("Missing required attributes: "+missing);
valid = false;
}
if (!permissibleAttributes.containsAll(attributes)) {
Set illegal = new HashSet(attributes);
illegal.removeAll(permissibleAttributes);
System.out.println("Contains illegal attributes: "+illegal);
valid = false;
}
if (valid)
System.out.println("OK");
假设你想了解由两个Map对象公用的所有键:
Set commonKeys = new HashSet(a.keySet());
commonKeys.retainAll(b.keySet());
类似的惯用程序使你可以获得公共值以及公共键-值对。要获得公共键-值对,则需格外小心; 因为结果Set的元素(即Map.Entry对象)在Map被更改后,可能是无效的。 到目前为止,所有惯用程序都是"非破坏性的":它们不更改后备Map。 下面是一些更改后备Map的例子。假设你要删除一个Map与另一个Map所共有的所有键-值对:
m1.entrySet().removeAll(m2.entrySet());
假设你要从一个Map中删除所有在另一个Map中具有映射的键:
m1.keySet().removeAll(m2.keySet());
当你在同样的批量操作中开始混合键和值时,发生了什么事情呢?假设你有一个称作managers的Map, 它将公司中的每个雇员与该雇员的经理相映射。我们对键和值对象的类型是不清楚的。这不要紧, 只要它们是相同的类型就可以了。现在,假设你要知道全部的"个体贡献者"是谁? (这是为不是经理的雇员所用的公司语言)。下面的一行程序准确地告诉你所要了解的东西:
Set individualContributors = new HashSet(managers.keySet());
individualContributors.removeAll(managers.values());
假设你要辞退直接向某些经理报告的雇员(我们称他为herbert):
Employee herbert = ... ;
managers.values().removeAll(Collections.singleton(herbert));
请注意,这个惯用程序利用了Collections.singleton, 它是一个静态方法,可返回一个永恒的带有单一特定元素的Set。
一旦完成了这些工作,你就可能有了一帮雇员,他们的经理不再为公司工作(如果任何herbert的直接报告是他们自己的经理)。下列代码告诉你所有的他们的经理不再为公司工作的雇员:
Map m = new HashMap(managers);
m.values().removeAll(managers.keySet());
Set slackers = m.keySet();
这个例子是一个小的技巧。首先,它作了一个Map的临时拷贝,然后又从这个临时拷贝中删除所有的经理值是初始Map中的键的项。记住这个初始Map包含一个为每一个雇员准备的项。于是,在临时Map中保留的项包含了初始Map中的经理值不再是雇员。在临时拷贝中的键则恰恰表示了我们正在寻找的雇员的所有项。如果你把这个例子多看看,你就应该全清楚了。如果还不清楚,该去拿一杯热气腾腾刚酿好的Java饮料了。
还有许多与本章中的惯用程序类似的例子,但要把它们全列出来则过于烦琐,也是不实际的,一旦你掌握了它的用法,你就很容易在你需要它的时候拿出正确的解决方案。多重映射(Multimaps)一个multimap与一个map类似, 只是它可以将每个键映射为多个值。Collections Framework不包括多重映射接口,因为它们不是很普遍地被使用。将一个其值为List对象的Map当作多重映射来使用则是相当简单的事情。这个技术在下一个代码举例中将被演示,这个例子是阅读每行(全部小写)一个单词的一部词典并打印所有满足尺寸标准的permutation groups(排列组)。 一个排列组是一组单词,它们包含完全相同的字母,但字母顺序不同。这个程序在命令行中使用了两个参数:词典文件名和要打印的排列组的尺寸;排列组包含的单词如果少于指定的最小值,则该排列组不被打印。
有一个查找排列组的标准技巧:将词典中的每个词的字母按字母顺序进行排列(即将一个词的字母按字母顺序记录下来)并将一个项放入一个多重映射,将经过字母排序的单词映射到原来的单词。例如,单词"bad" 导致一个项映射 "abd" 至 "bad" 被放入多重映射。一个瞬间的反射将显示任何给定的键映射到所有单词形成一个排列组。在一个多重映射中迭代键以及打印满足尺寸条件的每一个排列组是一个简单的事情。
下列程序是这个技术的一个直接的实现。仅有的技巧部分是alphabetize方法,它返回一个串,这个串包含与它的参数相同的字符,并按字母顺序排列。这个例程(它与Collections Framework无关) 实现一个巧妙的桶式分类。 它假定按字母顺序排列的单词完全由小写字母组成。
import java.util.*;
import java.io.*;
public class Perm {
public static void main(String[] args) {
int minGroupSize = Integer.parseInt(args[1]);
// Read words from file and put into simulated multimap
Map m = new HashMap();
try {
BufferedReader in =
new BufferedReader(new FileReader(args[0]));
String word;
while((word = in.readLine()) != null) {
String alpha = alphabetize(word);
List l = (List) m.get(alpha);
if (l==null)
m.put(alpha, l=new ArrayList());
l.add(word);
}
} catch(IOException e) {
System.err.println(e);
System.exit(1);
}
// Print all permutation groups above size threshold
for (Iterator i = m.values().iterator(); i.hasNext(); ) {
List l = (List) i.next();
if (l.size() $#@62; = minGroupSize)
System.out.println(l.size() + ": " + l);
}
}
private static String alphabetize(String s) {
int count[] = new int[256];
int len = s.length();
for (int i=0; i$#@60; len; i++)
count[s.charAt(i)]++;
StringBuffer result = new StringBuffer(len);
for (char c="a"; c$#@60; ="z"; c++)
for (int i=0; i$#@60; count[c]; i++)
result.append(c);
return result.toString();
}
}
在一台老式UltraSparc 1上运行一个有80,000个单词的词典用了大约16秒;最小排列组尺寸为8 。它生成了下列输出
% java Perm dictionary.txt 8
9: [estrin, inerts, insert, inters, niters, nitres, sinter,
triens, trines]
8: [carets, cartes, caster, caters, crates, reacts, recast, traces]
9: [capers, crapes, escarp, pacers, parsec, recaps, scrape, secpar,
spacer]
8: [ates, east, eats, etas, sate, seat, seta, teas]
12: [apers, apres, asper, pares, parse, pears, prase, presa, rapes,
reaps, spare, spear]
9: [anestri, antsier, nastier, ratines, retains, retinas, retsina,
stainer, stearin]
10: [least, setal, slate, stale, steal, stela, taels, tales, teals,
tesla]
8: [arles, earls, lares, laser, lears, rales, reals, seral]
8: [lapse, leaps, pales, peals, pleas, salep, sepal, spale]
8: [aspers, parses, passer, prases, repass, spares, sparse, spears]
8: [earings, erasing, gainers, reagins, regains, reginas, searing,
seringa]
11: [alerts, alters, artels, estral, laster, ratels, salter, slater,
staler, stelar, talers]
9: [palest, palets, pastel, petals, plates, pleats, septal, staple,
tepals]
8: [enters, nester, renest, rentes, resent, tenser, ternes, treens]
8: [peris, piers, pries, prise, ripes, speir, spier, spire]
对象排序
一个 List l 可能被做如下排序:
Collections.sort(l);
如果这个 list 由 String 元素所组成, 那么它将按词典排序法(按字母顺序)进行排序; 如果它是由 Date 元素所组成, 那么它将按年代顺序来排序。 Java 怎么会知道该怎么做呢? 这一定是个魔术! 其实不然。实际上, String 和 Date 均实现了Comparable接口。 Comparable 接口为一个类提供一个 自然排序( natural ordering), 它允许那个类的对象被自动排序。下表列出了实现了Comparable 的JDK类:
类 自然排序
Byte 带符号的数字排序
Character 不带符号的数字排序
Long 带符号的数字排序
Integer 带符号的数字排序
Short 带符号的数字排序
Double 带符号的数字排序
Float 带符号的数字排序
BigInteger 带符号的数字排序
BigDecimal 带符号的数字排序
File 依赖系统的按路径名字母顺序排序
String 按字母顺序排序
Date 按年代顺序排序
CollationKey 特定字符集按字母顺序排序
如果你要为一个其元素没有实现 Comparable的列表排序,Collections.sort(list) 将扔出一个 ClassCastException。类似的,如果你要为一个其元素没有作相互比较的列表进行排序, Collections.sort 将扔出一个 ClassCastException. 能够被相互比较的元素被称作 mutually comparable(可相互比较的)。 虽然不同类型的元素有可能被相互比较,但以上列出的任何JDK类型都不允许在类之间的比较 (inter-class comparison)。
如果你只是要为可比较的元素的列表进行排序,或为它们创建排序的对象集, 则这就是你实际需要了解的全部有关 Comparable 接口的内容。如果你要实现你自己的 Comparable 类型,则下一节将会引起你的兴趣。
分享到:
相关推荐
Map接口提供了多种实现,如HashMap、TreeMap、LinkedHashMap等,每种实现都有其特定的特性和用途。 HashMap是最常见的Map实现之一,它通过散列函数快速定位键值对,插入和访问的速度快,但元素的顺序不是固定的。在...
在Java编程语言中,Map接口是集合框架的重要组成部分,它提供了键值对的存储功能。Map集合不按照特定顺序存储元素,而是通过键(Key)来查找对应的值(Value...此外,Map还可以用于实现缓存、存储配置信息等多种用途。
- 集合框架是Java API中的重要部分,包括List、Set、Map等接口,以及ArrayList、HashSet、HashMap等实现类,为数据存储和操作提供了强大支持。 这些只是Java API的一部分知识点,实际的开发过程中,开发者会根据...
2. 流的概念:字节流(InputStream/OutputStream)和字符流(Reader/Writer),了解它们的层次结构和用途。 3. 文件输入/输出流:FileInputStream、FileOutputStream、FileReader和FileWriter,以及缓冲流...
总的来说,Java中的Set、List和Map接口及其实现类为不同的数据存储需求提供了灵活的选择。Set适合存储不重复元素,List适合存储有序并可能重复的元素,而Map则用于存储键值对应的数据。理解它们的特性和使用场景,有...
在Java中,Map有多种实现类,每种实现类有不同的特性和用途。本篇文章将详细探讨`HashMap`, `LinkedHashMap`, `TreeMap`, 和 `Hashtable`这四个常见的Map实现类,特别是它们如何处理排序的问题。 首先,`HashMap`是...
1. 关键字与保留字:了解Java中定义的特殊用途的单词,例如public, class, static等。 2. 数据类型:包括基本数据类型(int, double, char等)和引用数据类型(类、接口、数组等)。 3. 控制流程:掌握条件判断语句if,...
2. 寻找并添加所需的库文件,如GenericFileFilter.java,同时确保将MeteoInfoLib文件中的MapView、MapLayout和LayersLegend控件添加到项目中。 构建用户界面涉及以下步骤: 1. 创建一个JFrame窗体,命名为FrmMain,...
- Map接口及其实现类:HashMap、TreeMap、LinkedHashMap等,键值对的概念。 - 集合操作:添加、删除、查找、迭代等方法。 8. **泛型**: - 泛型的基本概念:类型参数化,限制通配符。 - 泛型类、泛型方法:如何...
Java 集合框架中有多种类型的容器,每种容器都有其特点和用途。常见的容器有 Collection、List、Set、Map 等。 1.1.1 容器的分类 Java 集合框架中的容器可以分为两大类:Collection 和 Map。Collection 是一个...
Java面试是每位Java开发者职业生涯中的重要关卡,它考察了候选人的基础知识、编程能力、问题解决技巧以及实际项目经验。"java面试32问.rar"这个压缩包很可能包含了32个精心挑选的Java面试问题,旨在帮助求职者做好...
集合框架在`java.util`包中,包括`List`、`Set`、`Map`接口和它们的实现类,如`ArrayList`、`HashSet`、`HashMap`等。这些数据结构和算法的实现对于处理数据存储和操作至关重要。 3. **IO流**: `java.io`包提供...
- **List、Set和Map接口**:ArrayList、LinkedList、HashSet、HashMap等具体实现类的特性与用途。 - **泛型**:提高代码的类型安全,减少类型转换的麻烦。 - **迭代器**:遍历集合的主要方式。 6. **多线程** -...
15. **Java集合框架**:详细分析集合框架的结构,包括List、Set、Map接口及其实现类的特性与应用场景。 16. **IO与NIO**:对比传统IO与非阻塞IO(New IO)的区别,介绍NIO的使用。 17. **网络编程**:讲解Socket...
《JAVA.5.0.TIGER程序高手秘笈》是一本深入探讨Java 5.0(也称为Java Tiger)编程技术的专业书籍,属于"notebook系列",旨在帮助程序员提升在Java平台上的开发技能。该书可能涵盖了Java 5.0的新特性、核心概念以及...
【标签】"java 求职面试 教育/考试 真题"明确了这个资料的用途。"java"表示内容与Java编程语言相关,"求职面试"说明这是面向面试准备的材料,"教育/考试"表明该资料可作为学习和自我测试之用,而"真题"则意味着其中...
6-10:这部分可能涵盖更高级的主题,如数组、字符串处理、异常处理、集合框架(List, Set, Map)的使用,以及IO流的基本操作。 接下来,"16-18"可能涉及更深入的内容,如: 16. 多线程:理解线程的概念,线程的...
4. **集合框架**:Java集合框架包括List、Set、Map等接口以及它们的实现类,如ArrayList、LinkedList、HashSet、HashMap等。熟练使用这些数据结构能够提高代码效率和可维护性。 5. **IO流**:输入输出流是Java处理...
类、方法和变量前应有描述其功能和用途的注释。Javadoc风格的注释 (`/** ... */`) 应用于公共API,便于生成文档。 3. **空格与缩进**:代码中逻辑相关的元素之间应使用空格来提高可读性,例如操作符周围、大括号...