用法主要原因是线程安全问题,一眼看来,线程安全用hashtable,线程不安全用hashmap。
但是实际上我们线程不安全用hashmap,而线程安全不用hashtable,因为有比他更好的,ConcurrentHashMap(锁粒度更低、效率更高)。
HashMap可以通过下面的语句进行同步:
Map m = Collections.synchronizeMap(hashMap);
hashset是简化版的hashmap,只存在键(唯一),因此底层是用hashmap实现
set底层的map的第二个参数共用同一个value(Object)。下面是源码
private transient HashMap<E,Object> map;
//set的底层map中的第二个参数是一个共用的常量Object了,
//所以每个方法只要传一个参数即可。
private static final Object PRESENT = new Object();
public HashSet() {
map = new HashMap<E,Object>();
}
public boolean contains(Object o) {
return map.containsKey(o);
}
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
public boolean remove(Object o) {
return map.remove(o)==PRESENT;
}
public void clear() {
map.clear();
}
在多线程环境下若使用HashMap需要使用Collections.synchronizedMap()方法来获取一个线程安全的集合。
HashMap可以通过下面的语句进行同步:
Map m = Collections.synchronizeMap(hashMap);
相同点:
- 底层数组+链表实现
- 扩容时机相同,当count大于临界值时,重新哈希
- 实现map接口
不同点:
- hashmap:初始size为16,扩容:newsize = oldsize2
hashtable:初始size为11,扩容:newsize = oldsize2+1 - hashmap:可以使用null作为key、value(不推荐) hashtable:不能使用null(抛出异常)
- 一般采取hashmap,因为速度快,原因是非线程安全,所以hashtable是线程安全的,使用synchronized关键字进行线程同步
- HashMap继承于AbstractMap,
而Hashtable继承于Dictionary。Dictionary是一个抽象类,它直接继承于Object类,没有实现任何接口。 - 都可以通过迭代器Iterator遍历,而hashtable也可以通过Enumeration遍历
Dictionary一般是通过Enumeration(枚举类)去遍历,Map则是通过Iterator(迭代器)去遍历。
- 计算hash的方法不同
Hashtable计算hash是直接使用key的hashcode对table数组的长度直接进行取模。
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
HashMap计算hash对key的hashcode进行了二次hash,以获得更好的散列值,然后对table数组长度取摸。
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);
}
使用场景
非并发场景使用HashMap,并发场景可以使用Hashtable,但是推荐使用ConcurrentHashMap(锁粒度更低、效率更高)。
另外使用在使用HashMap时要注意null值的判断,
Hashtable也要注意防止put null key和 null value。
当然,HashSet在存储对象等数据时,为了去重,一般都会重写hashCode()和equals().关于这两个方法不想多说,只说两点:
1…equals()相等的两个对象他们的hashCode()肯定相等,也就是用equals()对比是绝对可靠的。
2… hashCode()相等的两个对象他们的equals()不一定相等,也就是hashCode()不是绝对可靠的。
未懂:
-
重写hashCode()和equal()
前面说到了,HashMap的很多函数要基于equal()函数和hashCode()函数。hashCode()用来定位要存放的位置,equal()用来判断是否相等。
那么,相等的概念是什么?
Object版本的equal只是简单地判断是不是同一个实例。但是有的时候,我们想要的的是逻辑上的相等。比如有一个学生类student,有一个属性studentID,只要studentID相等,不是同一个实例我们也认为是同一学生。当我们认为判定equals的相等应该是逻辑上的相等而不是只是判断是不是内存中的同一个东西的时候,就需要重写equal()。而涉及到HashMap的时候,重写了equals(),就需要重写hashCode()我们总结一下几条基本原则
同一个对象(没有发生过修改)无论何时调用hashCode()得到的返回值必须一样。 如果一个key对象在put的时候调用hashCode()决定了存放的位置,而在get的时候调用hashCode()得到了不一样的返回值,这个值映射到了一个和原来不一样的地方,那么肯定就找不到原来那个键值对了。
hashCode()的返回值相等的对象不一定相等,通过hashCode()和equals()必须能唯一确定一个对象 不相等的对象的hashCode()的结果可以相等。hashCode()在注意关注碰撞问题的时候,也要关注生成速度问题,完美hash不现实
一旦重写了equals()函数(重写equals的时候还要注意要满足自反性、对称性、传递性、一致性),就必须重写hashCode()函数。而且hashCode()的生成哈希值的依据应该是equals()中用来比较是否相等的字段
如果两个由equals()规定相等的对象生成的hashCode不等,对于hashMap来说,他们很可能分别映射到不同位置,没有调用equals()比较是否相等的机会,两个实际上相等的对象可能被插入不同位置,出现错误。其他一些基于哈希方法的集合类可能也会有这个问题
Effective Java Programming Language Guide中建议的hashCode写法 -
计算hash的方法不同 Hashtable计算hash是直接使用key的hashcode对table数组的长度直接进行取模。
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
HashMap计算hash对key的hashcode进行了二次hash,以获得更好的散列值,然后对table数组长度取摸。
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);
}