理解Map
1.映射表的基本思想是它维护的是键-值(对)关联,因此你可以使用键来查找值。
2.下面是一个简单的关联数组的创建:
public class AssociativeArray<K, V> {
private Object[][] pairs;
private int index;
public AssociativeArray(int length) {
pairs = new Object[length][2];
}
public void put(K key, V value) {
if (index >= pairs.length)
throws new ArrayIndexOutOfBoundsException();
pairs[index++] = new Object[]{key, value};
}
@SupressWarning("unchecked")
public V get(K key) {
for (int i = 0; i < pairs.length; i++) {
if (key.equals(pairs[i][0])) {
return (V) pairs[i][1];
}
}
return null;
}
}
上面的代码只是具有一定的说明性,但是缺乏效率,并且由于具有固定尺寸而显得很不灵活。
一.性能
1.HashMap使用了特殊的值,称为散列码,来取代对键的缓慢搜索。散列码是相对唯一的、用以代表对象的int值,他是通过将该对象的某些信息进行转换而生成的。
2.hashCode()是类Object中的方法,因此所有Java对象都能产生散列码。HashMap就是使用对象的hashCode()进行快速查询的,此方法能够显著提高性能。
3.下面是几种基本的Map的实现:
Method | 实现描述 |
---|---|
HashMap | Map基于散列表带你实现(它取代了Hashtable)。插入和查询“键值对”的开销是固定的。可以通过构造器设置容量和负载因子,以调整容器的性能 |
LinkedHashMap | 类似于HashMap,但是便利它时,取得“键值对”的顺序是其插入顺序,或是最近最少使用的顺序。只比HashMap慢一点;而在迭代访问时反而更快,因为它使用链表维护内部的次序 |
TreeMap | 基于红黑树的实现。查看“键”或“键值对”时,它们会被排序(由Comparable或Comparator决定)。TreeMap的特定在于,所得到的结果是经过排序的。TreeMap是唯一的带有subMap()方法的Map。它可以返回一个子树 |
WeekHashMap | 弱键映射,允许释放映射所指向的对象;这是为解决某类特殊问题而设计的。如果映射之外没有引用指向某个“键”,则此“键”可以被垃圾回收器回收 |
ConcurrentHashMap | 一种线程安全的Map,它不涉及同步加锁 |
IdentityHashMap | 使用==代替equals()对“键”进行比较的散列映射。 |
4.对Map中使用的键的要求与对Set中的元素的要求一样。任何键都必须具有一个equals()方法;如果键被用于散列Map,那么它必须还具有恰当的hashCode()方法;如果键被用于TreeMap,那么它必须实现Comparable。
5.关联数组中的基本方法是put()和get()。keySet()方法返回一个由Map的键组成的Set。可以直接通过values()方法获取“值”,该方法会产生一个包含Map中所有“值”的Collection,需要注意的是对该Collection的任何改动都会反映到与之相关联的Map。
二.SortedMap
1.使用SortedMap(TreeMap是其现阶段的唯一实现),可以确保“键”处于排序状态。
2.SortedMap接口中的一些特殊方法如下:
Method | 功能描述 |
---|---|
Comparator comparator() | 返回当前Map使用的Comparator;或者返回null,表示以自然方式排序 |
T firstKey() | 返回Map中的第一个键 |
T lastKey() | 返回Map中的最末一个键 |
SortedMap subMap(fromKey, toKey) | 生成此Map的子集,范围由fromKey(包含)到toKey(不包含)的键确定 |
SortedMap headMap(toKey) | 生成此Map的子集,由小于toKey的所有键值对确定 |
SortedMap tailMap(fromKey) | 生成此Map的子集,由大于或等于fromMap的所有键值对组成 |
三.LinkedHashMap
1.为了提高速度,LinkedHashMap散列化所有的元素,但是在遍历键值对时,却又以元素的插入顺序返回键值对。
2.可以在构造器中设定LinkedHashMap,使之采用基于访问的最近最少使用算法。于是没有被访问过的元素酒会出现在队列的最前面。对于需要定期清理元素以节省空间的程序来说,此功能使得程序很容易就能实现。
散列与散列码
1.使用标准类库中的类来作为HashMap的键没有问题,因为它具备了键所需的全部性质。
2.当自己创建用作HashMap的键的类,有可能会忘记在其中放置必需的方法,而这是通常会犯的一个错误。
3.可能你会认为,只需编写恰当的hashCode()方法的覆盖版本即可。但是它仍然无法正常运行,除非你同时覆盖equals()方法,它也是Object的一部分。HashMap使用equals()判断当前的键是否与表中的相同。
4.正确的equals()方法必须满足的条件:
(1)自反性。对任意x,x.equals(x)一定返回true。
(2)对称性。对任意x和y,如果y.equals(x)返回true,则x.equals(y)一定返回true。
(3)传递性。对任意x,y,z,如果有x.equals(y)返回true,y.equals(z)返回true,则x.equals(z)一定返回true。
(4)一致性。对任意x和y,如果对象中用于等价比较的信息没有改变,那么无论调用x.equals(y)多少次,返回的结果应该保持一致,要么一直是true要么一直是false。
(5)对任何不是null的x,x.equals(null)一定返回false。
一.理解hashCode()
1.使用散列的目的是:想要试用一个对象来查找另外一个对象。
二.为速度而散列
1.散列的价值在于速度:散列使得查询得以快速进行。
2.散列将键保存在某处,以便能够快速找到。存储一组元素最快的数据结构是数组吗所以使用它来表示键的信息。
3.数组并不保存键本身。而是通过键对象生成一个数字,将其作为数组的下标。这个数字就是散列码,由hashCode()方法生成。
4.为解决数组容量被固定的问题,不同的键会产生相同的下标。也就是说,可能会有冲突。因此,数组多大就不重要了,任何键总能在数组中找到它的位置。
5.查询一个值的过程首先就是计算散列码,然后使用散列码查询数组。如果能够没有冲突,那就有了一个完美的散列函数,但这只是特例。通常。冲突由外部链接处理:数组并不直接保存值,而是保存值的list。然后对list中的值使用equals()方法进行线性查询。这部分的查询自然会比较慢,但是,如果散列函数好的话,数组的每个位置就只有较少的值。因此,不会查询整个list,而是快速地跳到数组的某个位置,只对很少的元素进行比较。
三.覆盖hashCode()
1.在使用HashMap的时候,你无法控制数组的下标值的产生。这个值依赖于具体的HashMap对象的容量,而容量大改变与容器的充满程度和负载因子有关。
2.设计hashCode()最重要的一个因素:无论何时,对同一个对象调用hashCode()都应该产生同样的值。
3.不应该使hashCode()依赖于具有唯一性的对象信息,尤其是使用this的值。因为这样做无法生成一个新的键,使之与put()中原始的键值对中的键相同。
4.hashCode()必须基于对象的内容生成散列码。散列码不鄙视独一无二的,但是通过hashCode()和equals(),必须能够完全确定对象的身份。
5.好的hashCode()应该产生分布均匀的散列码。如果散列码都集中在一块,那么HashMap或者HashSet在某些区域的负载会很重,这样就不如分布均匀的散列函数快。
6.产生hashCode()的基本指导:
(1)给int变量result赋予某个非零值常量,例如17。
(2)为对象内每一个有意义的域f(即每个可以做equals()操作的域)计算出一个int散列码c:
域类型 | 计算 |
---|---|
boolean | c = (f ? 0 : 1) |
byte、char、short、int | c = (int)f |
long | c = (int) (f ^ (f >>> 32)) |
float | c = Float.floatToBits(f) |
double | long l = Double.doubleToLongBits(f) c = (int) (l ^ l >>> 32) |
Object,其equals()调用这个域的equals() | c = f.hashCode() |
数组 | 对每个元素应用上面的规则 |
(3)合并计算得到的散列码:
result = 37 * result + c;
(4)返回result。
(5)检查hashCode()最后生成的结果,确保相同的对象有相同的散列码。
7.compareTo()的创建方式:排序的规则首先按照实际类型排序,然后如果有名字的话,按照name排序,最后安好创建的顺序排序。