对比Hashtable、HashMap、TreeMap有什么不同?
Hashtable、HashMap、TreeMap都是最常见的一些Map实现,是以键值对的形式存储和操作数据的容器类型。
Hashtable是早期Java类库提供的一个哈希表实现。
是同步的,不支持null键和null值。
由于同步导致的性能开销,所以不推荐使用。
HashMap是应用更为广泛的哈希表实现,行为大致与Hashtable一致。
主要区别在于HashMap不是同步的,支持null键和null值。
通常情况下HashMap进行put或get操作,可以达到常数时间的性能,所以他是绝大部分利用键值对存取场景的首选。
TreeMap则是基于红黑树的一种提供顺序访问Map。
和HashMap不同,他的put、get、remove之类的操作都是O(log(n))的时间复杂度。
具体顺序可以由指定的Comparator来决定,或者根据键的自然顺序来排序。
Map虽然通常被包括在Java集合框架里,但其本身不是狭义上的集合框架(Collection)。
Hashtable 比较特别,作为类似Vector、Stack的早期集合相关类型,它是扩展了Dictionary 类的,类结构上与 HashMap 之类明显不同。
HashMap 等其他 Map实现则是都扩展了AbstractMap,里面包含了通用方法抽象。不同 Map的用途,从类图结构就能体现出来,设计目的已经体现在不同接口上。(为什么继承了抽象类还要实现接口?)
HashMap 的性能表现非常依赖于哈希码的有效性,请务必掌握 hashCode 和 equals 的一些基本约定:
虽然LinkedHashMap和TreeMap都可以保证某种顺序,但二者还是非常不同的。
LinkedHashMap 通常提供的是遍历顺序符合插入顺序,
它的实现是通过为条目(键值对)维护一个双向链表。
通过特定构造函数,我们可以创建反映访问顺序的实例,
所谓的 put、get、compute 等,都算作“访问”。
这种行为适用于一些特定应用场景,例如,我们构建一个空间占用敏感的资源池,希望可以自动将最不常被访问的对象释放掉,这就可以利用 LinkedHashMap 提供的机制来实现。
对于TreeMap,它的整体顺序是由键的顺序关系决定的,
通过Comparator或Comparable(自然顺序)来决定。
主要围绕:
HashMap的内部结构可以看作是数组(Node
数组被分为一个个桶(bucket),通过哈希值决定了键值对在这个数组的寻址。哈希值相同的键值对,则以链表形式存储。
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
final V putVal(int hash, K key, V value, boolean onlyIfAbent,
boolean evit) {
Node[] tab; Node p; int , i;
if ((tab = table) == null || (n = tab.length) = 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == ull)
tab[i] = newNode(hash, key, value, nll);
else {
// ...
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for first
treeifyBin(tab, hash);
// ...
}
}
resize方法兼顾两个职责,创建初始存储表格,或者在容量不满足需求的时候,进行扩容(resize)。(扩容后,需要将老的数组中的元素重新放置到新的数组,这是扩容的一个主要开销来源。)
具体键值对在哈希表中的位置(数组 index)通过下面的位运算:
i = (n - 1) & hash。
仔细观察哈希值(hash)的源头,我们会发现,它并不是 key 本身的 hashCode,而是来自于HashMap内部的另外一个hash方法。
static final int hash(Object kye) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>>16;
}
为什么这里需要将高位数据移位到低位进行异或运算呢?这是因为有些数据计算出的哈希值差异主要在高位,而HashMap里的哈希寻址是忽略容量以上的高位的,那么这种处理就可以有效避免类似情况下的哈希碰撞。
容量和负载因子决定了可用的桶的数量,空桶太多会浪费空间,太满则会严重影响操作的性能。
关于负载因子的建议:
题:解决哈希冲突有哪些典型方法?
答:开发地址法、拉链法等。