HashMap与Hashtable(二)
来源:互联网 发布:centos系统还原 编辑:程序博客网 时间:2024/05/17 09:05
Hashtable与HashMap相同的地方很多,底层数据结构相同,解决散列冲突的方式相同,主要的不同在于Hashtable是线程安全的,当然现在的线程安全定义很泛滥,vector、Hashtable都可以说是线程安全的,不过就是在方法上加上synchronized修饰词,以同步的方式使用。在实际使用时仍然需要额外的代码保证,否则依然会抛出错误
vector示例:
public class t{public static void main(String[] args){ final Vector<String> arr=new Vector<String>(1000);int i=0;while(i++<1000){arr.add("ssh");}new Thread(){public void run(){for(int i=arr.size();i>0;i--){//get操作是原子操作,但是获取i值,再进行get操作,arr.get(i);//不是原子的,}}}.start();new Thread(){public void run(){for(int i=arr.size();i>0;i--){arr.remove(i);}}}.start();} }可能会抛出ArrayIndexOutOfBoundsException,以vector举例说明Hashtable操作方法虽然是同步修饰的,但是使用过程中仍然需要注意使用场景。
put操作:
public synchronized V put(K key, V value) { // Make sure the value is not null if (value == null) {//不需要null值 throw new NullPointerException(); } // Makes sure the key is not already in the hashtable. Entry tab[] = table; int hash = hash(key);//根据key对象的hashCode,重新计算hash值,若为null,则hashCode方法会抛出异常 int index = (hash & 0x7FFFFFFF) % tab.length;//计算table数组下标,原理在上篇HashMap中讲过 for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) { if ((e.hash == hash) && e.key.equals(key)) {//key已存在则覆盖value,返回旧value V old = e.value; e.value = value; return old; } } modCount++; //否则,增加修改次数,判断是否需要扩展table if (count >= threshold) { // Rehash the table if the threshold is exceeded rehash(); tab = table; hash = hash(key); index = (hash & 0x7FFFFFFF) % tab.length; } // Creates the new entry. Entry<K,V> e = tab[index];//头插方式添加新Entry tab[index] = new Entry<>(hash, key, value, e); count++; //Entry个数增加 return null; }
从中可见,Hashtable的key、value不能为null,自然也不会有HashMap中的putForNullKey、getForNullKey。
Entry数组table和Entry节点个数count是以transient修饰,Hashtable中自定义writeObject和readObject,实现底层数组自定义序列化和反序列化,参见 ArrayList的remove、序列化
关于迭代操作:
HashMap和Hashtable 都存在一个entrySet
//Hashtableprivate transient volatile Set<Map.Entry<K,V>> entrySet = null;//利用volatile编译后指令保持可见性//HashMapprivate transient Set<Map.Entry<K,V>> entrySet = null;
HashMap的entrySet()方法返回:
public Set<Map.Entry<K,V>> entrySet() {//entrySet调用一个私有的方法 return entrySet0(); } private Set<Map.Entry<K,V>> entrySet0() { Set<Map.Entry<K,V>> es = entrySet;//如果为空则返回创建的EntrySet,避免iterator()时 return es != null ? es : (entrySet = new EntrySet());//抛出NullPointException } private final class EntrySet extends AbstractSet<Map.Entry<K,V>> { public Iterator<Map.Entry<K,V>> iterator() {//返回迭代器 return newEntryIterator(); } .........}
Iterator<Map.Entry<K,V>> newEntryIterator() { return new EntryIterator();//实际返回为EntryIterator类型对象 }
由方法返回结果看出,如果entrySet为null,返回一个EntrySet类型对象,对象的iterator()方法返回为Iterator<Map.Entry<K,V>>类型对象;
如果entrySet不为null,返回entrySet属性本身,即Set<Map.Entry<K,V>>类型对象,调用iterator()返回同样为Iterator<Map.Entry<K,V>>类型对象。
所以调用entrySet().iterator()方法,实际返回的是一个Iterator<Map.Entry<K,V>>接口的子类EntryIterator类型对象。由于EntryIterator继承自HashIterator<Map.Entry<K,V>>:
private final class EntryIterator extends HashIterator<Map.Entry<K,V>> { public Map.Entry<K,V> next() {//Iterator接口中存在三个方法,next()、hasNext()、remove() return nextEntry(); //抽象类HashIterator实现了其中的后两个,在EntryIterator子类 }//中实现了next方法,这也是为什么调用一个iterator()方法,返回一个 }//迭代器而已,却要放到最下面,因为要返回底层类EntryIterator的迭代器实例。private abstract class HashIterator<E> implements Iterator<E> { int expectedModCount; // For fast-fail 英文已经说白了,为了快速失败 HashIterator() { expectedModCount = modCount;//修改次数与预期修改次数,在ArrayList的remove中已经谈过 if (size > 0) { // advance to first entry Entry[] t = table; while (index < t.length && (next = t[index++]) == null) ; } } final Entry<K,V> nextEntry() {//EntryIterator对象调用next()方法时调用的方法 if (modCount != expectedModCount) //因为除了EntryIterator外,还有ValueIterator和 throw new ConcurrentModificationException();//KeyIterator,检测修改次数,发出快速失败 ........... } public void remove() {//除了hasNext外,迭代器的其他两个操作都会在开头检测快速失败 ............ } }由以上可以看出,HashMap的迭代器有三种,ValueIterator、KeyIterator和EntryIterator,三种迭代器都继承于相同的抽象类HashIterator,并给出各自不同的next()方法。
Hashtable的entrySet()返回:
public Set<Map.Entry<K,V>> entrySet() { if (entrySet==null) entrySet = Collections.synchronizedSet(new EntrySet(), this); return entrySet;//返回同步的set容器 } private class EntrySet extends AbstractSet<Map.Entry<K,V>> { public Iterator<Map.Entry<K,V>> iterator() { return getIterator(ENTRIES);//返回指定类型的迭代器 } ................}为了向前兼容,使用原始的迭代器类型:
private <T> Iterator<T> getIterator(int type) {//为了包容Enumeration类型 if (count == 0) { return Collections.emptyIterator(); } else { return new Enumerator<>(type, true); } }private class Enumerator<T> implements Enumeration<T>, Iterator<T> {//同时实现了Enumeration和Iterator protected int expectedModCount = modCount;//为了检测快速失败 public boolean hasMoreElements() {//hasNext实际调用的方法 .... } public T nextElement() {//next方法实际调用的方法 ......... } public boolean hasNext() {//Iterator中方法 return hasMoreElements(); } ........... public T next() {//在调用nextElement之前,检查快速失败 if (modCount != expectedModCount) throw new ConcurrentModificationException(); return nextElement(); }}
使用迭代器可以参考迭代器模式,将聚合对象的存储和遍历分开,满足单一功能职责,在不破坏封装的前提下,可以提供一个迭代器类来完成对聚合对象的遍历,不过在JDK中一般选择的方式都是作为内部类实现。
总结:
HashMap和Hashtable最大区别仍然是方法是否同步,底层数据结构相同,都是基于数组-链表形式,HashMap中key-value可以为null,Hashtable中不可以,HashMap的迭代方式为Iterator,Hashtable为了向前兼容,实际使用的是Enumeration,HashMap的底层数组table长度为2的整数次幂,Hashtable数组长度则一直为一个奇数,两者关于定位table数组下标的算法不同,都自定义了数组元素的序列化和反序列化。
- HashMap与Hashtable(二)
- HashMap与HashTable解读(二)
- HashMap与HashTable(二)-HashMap原理与实现
- HashMap与HashTable(二)-HashMap原理与实现
- hashtable与hashmap
- HashMap 与 Hashtable
- HashMap 与HashTable 区别
- Hashtable与HashMap
- HashTable与HashMap
- Hashtable与hashmap 比较
- HashMap与HashTable区别
- HashMap与HashTable
- Hashtable与HashMap区别
- HashMap与HashTable区别
- Hashtable与hashmap 比较
- hashTable与HashMap区别
- Hashtable与HashMap
- HashMap与HashTable区别
- 结构化随机森林源码分析
- 莫比乌斯反演入门 HDOJ 1695:GCD 、BZOJ 2301: [HAOI2011]Problem b
- 12.Which is the correct description of a pinned buffer in the database buffer cache?
- [javascript权威指南][阅读笔记]五
- Unity3d自带寻路Navmesh三大组件
- HashMap与Hashtable(二)
- 对类成员访问权限的控制,是通过设置成员的访问控制属性实现的,下列不是访问控制属性的是( )
- 天梯赛 L2-011. 玩转二叉树(数据结构)
- jzoj 1569. 【普及模拟】公共子串 解题报告
- Windows 无法自动将 IP 协议堆栈绑定到网络适配器
- 【前端开发】HTML标签总结-行标签和块标签
- iOS开发-使用AVAudioPlayer实现音乐播放器
- sublimeText 快捷键
- AN 外置字幕CTS crash(memcpy) && backtrac文件分析