Java入门记(五):容器关系的梳理(下)——Map
注意:阅读本文及相关源码时,需要数据结构相关知识,包括:哈希表、链表、红黑树。
Map是将键(key)映射到值(value)的对象。不同的映射不能包含相同的键;每个键最多只能映射到一个值。下图是常见Map的接口和实现。与Collection相比,继承关系简单不少。
一、Map接口和AbstractMap抽象类
Map接口除了增加映射、根据key获取value、判断映射中的key或value是否存在、删除映射的基本方法外,还包含了返回包含所有key的Set、包含所有value的collection的方法。由于key不能重复,返回的Collection自然具有Set的属性,很适合用Set返回。而value则不行。
与其他Collection接口不同,Map接口中有一个子接口:Entry。Entry代表了一个映射,包含了key和value两部分,同时,一个Enry的key没有提供修改方法,而value允许修改。需要说明的是,如果用一个可变对象作为Map的key,若变化后equals()与之前的行为不同,那么映射的行为是不确定的(JDK1.6文档)。
对于抽象类AbstractMap,大部分实现的方法借助了将所有entry组成的set返回的抽象方法entrySet():size()、isEmpty()(使用size())、containsValue()、containsKey()、get()、clear()、keySet()、values()等。而remove()、removeAll()、retainAll()、clear()、toString()则借助了抽象方法iterator()。
values()返回值value的是一个匿名内部类实现的AbstractCollection。value在第一次访问时创建,在后续所有访问中返回。虽然不进行元素的同步,其引用几乎总是不变的,但返回值的行为会随着Map中的元素变化:
public Collection<V> values() {
if (values == null) {
values = new AbstractCollection<V>() {
public Iterator<V> iterator() {
return new Iterator<V>() {
private Iterator<Entry<K,V>> i = entrySet().iterator(); public boolean hasNext() {
return i.hasNext();
} public V next() {
return i.next().getValue();
} public void remove() {
i.remove();
}
};
} public int size() {
return AbstractMap.this.size();
} public boolean contains(Object v) {
return AbstractMap.this.containsValue(v);
}
};
}
return values;
}
对于Map.Entry,AbstractMap中实现了两个键值对类型:SimpleEntry和SimpleImmutableEntry。后者与前者的区别是,不允许setValue(),调用该方法抛出UnsupportedOperationException异常。
二、HashMap/LinkedHashMap/WeakHashMap
在Java入门记(四):容器关系的梳理(上)——Collection一文中提到,HashSet/LinkedHashSet的底层实际是HashMap/LinkedHashMap。HashMap和一般的散列表实现方式相同,用数组存放相同哈希值的元素所组成队列的首元素,队列的元素是Entry,包括了key、value、hash值、next等属性。寻找指定key时,先做哈希,根据哈希值找到数组中对应的队列头,遍历队列找出key及对应的value。
由于HashMap允许null作为key,这个key没办法做哈希值的计算,只能遍历哈希值数组,找到首元素的key为null的队列。这个实现可以参考私有方法getForNullKey()。
HashMap的key的哈希值数组有一个容量限制:必须为2的幂次。即使在新建HashMap时或调用resize()时指定一个非2的幂次的容量,实际调用时新的HashMap容量也会扩大到不小于这个指定容量的2的幂次的值。与之相关联地,哈希值的计算hash()和某个hash值在数组的索引的计算indexFor()方式为:
static int hash(int h) {
// This function ensures that hashCodes that differ only by
// constant multiples at each bit position have a bounded
// number of collisions (approximately 8 at default load factor).
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
static int indexFor(int h, int length) {
return h & (length-1);
}
2的幂次可以保证计算索引时适当的截断(舍弃高位)。
LinkedHashMap与HashMap的关系并不像LinkedList和ArrayList那样。从结构上来看,LinkedHashMap仅仅是把所有的Entry组成了一个双向链表。这样,在迭代遍历时,可以使用插入顺序或LRU顺序访问所有元素(通过设置accessOrder标记位)。
WeakHashMap和HashMap很类似,其内部包含了一个ReferenceQueue,并且它的Entry是继承自WeakReference的。通过这种方式,在clear()、resize()、size()、getTable()时,都会调用expungeStaleEntries()方法,垃圾回收掉不再使用的映射关系。这里不介绍Reference的相关内容了。
思考下上一篇文章所提出的问题:是不是可以先实现HashSet,再用HashSet实现HashMap?个人认为,这样实现的HashSet中的元素(对应Map的Entry),只有键没有值,是无法直接实现HashMap的。
二、Hashtable/Properties
Hashtable虽然实现了Map接口,但没有用AbstractMap来做。它的行为与HashMap很相似,保留下来是为了兼容原来的代码,不推荐继续使用。
继承了Hashtable<Object,Object>的Properties稍有点不同,它与流的关系密切些,可保存在流中或从流中加载。另外,它是线程安全的。
三、SortedMap和TreeMap
SortedMap中的所有元素都是排过序的。这个“排序”不同于LinkedHashMap中将所有元素组织成一个链表,而是指任意任意两个元素都可以比较大小关系,并根据这个比较规则Comparator进行排序。更准确的说,是键的大小关系。建立在有序的基础上,SortedMap接口中包含了返回部分Map的方法subMap(K fromKey, K toKey)、headMap(K toKey)、tailMap(K fromKey)以及首尾key的方法firstKey()、lastKey()。
TreeMap是SortedMap的一个实现,其Compartor可以为null,这种情况下比较元素大小时调用元素自身的compareTo()方法。
TreeMap实际上使用了红黑树,保存了树的根。关于红黑树的算法,讲起来可以单独开一篇文章,这里不展开了,想了解的读者可以读下《算法导论》的相关章节。TreeMap的元素插入、删除其实是红黑树节点的插入和删除。在元素有序的前提下,找到特定的key(以及对应的value)同样是使用了红黑树的查找方法。
Java入门记(五):容器关系的梳理(下)——Map的更多相关文章
- Java入门记(四):容器关系的梳理(上)——Collection
目录 一.Collection及子类/接口容器继承关系 二.List 2.1 ArrayList 2.1.1 序列化的探讨 2.1.2 删除元素 2.1.3 调整大小 2.2 Vector和Stack ...
- java单例五种实现模式梳理
java单例五种实现模式 饿汉式(线程安全,调用效率高,但是不能延时加载) 一上来就把单例对象创建出来了,要用的时候直接返回即可,这种可以说是单例模式中最简单的一种实现方式.但是问题也比较明显.单例在 ...
- leaflet入门(五)API翻译(下)
L.PointConstructor(函数构造器)Properties(属性)Methods(方法) L.BoundsConstructor(函数构造器)Properties(属性)Methods(方 ...
- Java入门记(二):向上转型与向下转型
在对Java学习的过程中,对于转型这种操作比较迷茫,特总结出了此文.例子参考了<Java编程思想>. 目录 几个同义词 向上转型与向下转型 例一:向上转型,调用指定的父类方法 例二:向上转 ...
- Java入门记(三):初始化顺序
初始化顺序的规则 1.在一个类的对象实例化时,成员变量首先初始化,然后才调用构造器,无论书写顺序.如果调用构造器前,没有显式初始化,那么会赋默认值. 这样做法的原因可以理解为:构造器执行时可能会用到一 ...
- Java入门记(一):折腾HelloWorld
HelloWorld,学习每门语言的第一步.有人戏称,这些年的编程生涯就是学习各种语言的HelloWorld,不知是自谦还是自嘲.目前所在的公司使用Java作为主要开发语言,我进行语言转换也大半年了, ...
- JAVA入门 第五周 1多项式
1 多项式加法(5分) 题目内容: 一个多项式可以表达为x的各次幂与系数乘积的和,比如: 现在,你的程序要读入两个多项式,然后输出这两个多项式的和,也就是把对应的幂上的系数相加然后输出. 程序要处理的 ...
- Java入门(五):控制流程
在Java中,使用条件语句和循环结构确定控制流程,在本文中,主要包括块作用域.条件语句.循环结构.中断循环这四部分. 一.块作用域 块,也叫复合语句,是指由一对大括号括起来的若干条Java语句.块决定 ...
- Java入门教程五(数字和日期处理)
Java 提供了处理相关问题的类,包括 Math 类.Random 类.BigInteger 类.Date 类等. Math类 Math 类封装了常用的数学运算,提供了基本的数学操作,如指数.对数.平 ...
随机推荐
- 使用.net 创建windows service
最近公司项目需要,写了个windows 服务,windows 服务的内容可以在VS 中新建一个"windows服务项目", (1)服务中的主要代码: public partial ...
- 【转】STM32中的抢占优先级、响应优先级概念
STM32(Cortex-M3)中有两个优先级的概念--抢占式优先级和响应优先级,有人把响应优先级称作'亚优先级'或'副优先级',每个中断源都需要被指定这两种优先级. 具有高抢占式优先级的中断可以在具 ...
- 如何使用Chrome Timeline 工具(译)
[原文地址]https://developers.google.com/web/tools/chrome-devtools/profile/evaluate-performance/timeline- ...
- delphi怎么创建一个文本文件
//创建一个文本文件 procedure TFrmAutomatismBalance.FlatButton3Click(Sender: TObject); var TF:TextFile; Path ...
- 移动应用开发测试工具Bugtags集成和使用教程
前段时间,有很多APP突然走红,最终却都是樱花一现.作为一个创业团队,突然爆红是非常难得的机会.然并卵,由于没有经过充分的测试,再加上用户的激增,APP闪退.服务器数据异常等问题就被暴露出来,用户的流 ...
- jquery的全选插件
全选看起来挺简单的,要做得完美就不那么容易了. 目前,我的全选插件能做到以下6点: 1.点击全选checkbox,能将要选择的checkbox都选中.去掉全选按钮,能将所有的checkbox都不选.这 ...
- CentOS 7 用户怎样安装 LNMP(Nginx+PHP+MySQL)
关于 Nginx (发音 “engine x”)这是一款免费.开源.高效的 HTTP 服务器,Nginx是以稳定著称,丰富的功能,结构简单,低资源消耗.本教程演示如何在CentOS 6.5服务器(适用 ...
- Twisted
Twisted是一个事件驱动的网络框架,其中包含了诸多功能,例如网络协议,线程,数据库管理,网络操作,电子邮件等 事件驱动 一,注册事件 二,触发事件 自定义事件框架 event_fram.py # ...
- 剑指 Offer 题目汇总索引
剑指 Offer 总目录:(共50道大题) 1. 赋值运算符函数(或应说复制拷贝函数问题) 2. 实现 Singleton 模式 (C#) 3.二维数组中的查找 4.替换空格 ...
- 两种流行Spring定时器配置:Java的Timer类和OpenSymphony的Quartz
1.Java Timer定时 首先继承java.util.TimerTask类实现run方法 import java.util.TimerTask; public class EmailReportT ...