HashMap 继承自AbstractMap类,底层数组+链表实现,可以存入null键及null值,线程不安全,而效率也比较高,初始容量默认为16,每次扩容会变为原来的2倍;
当map中的元素超过entry数组的75%,触发扩容操作,为了减少链表的长度,元素分配更均匀;
扩容针对整个map,每次扩容时,原来数组中的元素依次重新计算存放位置,并重新插入;
Hashtable 继承自Dictionary类,底层数组+链表实现, 不允许存入null键和null值,线程安全,实现线程安全的方式是在修改数据时,锁住整个表,所以其效率低,默认初始容量为11,每次扩容变为原来的2n+1;
那么为什么hashtable是线程安全的呢?我们可以翻看一下:
例如:put 方法,hashtable中几乎所有的方法都是被synchronized 修饰的;以保证其安全性;
synchronized 可以修饰静态方法(锁对象为本类的字节码文件对象)、修饰普通方法(锁对象为this,即谁调用了它)、修饰某段代码(锁对象为任意对象);
再来说hashmap,它的get put 方法没有加同步,那么意味着,如果2个线程同时put了两个相同的key时,这2个key会被放到数组中的同一位置,(hashmap 中的key是用数组来存储的),就会导致其中一个线程put的数据被覆盖;另外:hashmap并发执行put 操作时会引起死循环(其实是发生在数组扩容时,hashmap 中的node链表形成环形数据结构)。
hashmap基于hash算法,实现对数据的读写,当调用put方法传入键值对时,会调用key的hashcode方法计算出哈希值,并依据此找到对应的bucket位置来存储值对象,如果出现了两个不同的key的hashcode值相同,它们会存储在同一个bucket位置的链表中,然后通过key对象的equals方法来比对 找到对应的键值对,如果链表大小超过阈值,链表就会形成树形结构;
所以在api中给出了这样的方法保证hashmap的安全:
Map m = Collections.synchronizedMap(new HashMap(...));
当然还有另外一种方法就是:使用ConcurrentHashmap: 底层采用分段的数组+链表实现,线程安全,这个类遵守与
除此之外还支持多线程对map 的读操作,在执行写操作时,CHM只锁住部分的Map,同时支持16个进程进行写操作,读操作不受限制; 原因在于ConcurrentHashmap引用了锁分割,将map分割为16部分(默认初始容量为16),由分段锁控制不同的segment; Map m = Collections.synchronizedMap(new HashMap(...)); 方法 和hashtable 效率相对较高(应用场景:读取多于写入); 锁分段技术如何保证了线程安全:首先将数据分成一段一段存储,然后给每段数据配一把锁,当一个线程占用锁并访问其中一段的数据时,其他段的数据是可以同时被其他线程访问而不受影响的;chm默认将hash表分为16个bucket,如进行get put remove操作时,只需要锁住当前用到的锁,这样,原来只能一个线程进入,现在却可以同时有16个线程同时访问执行,并发性能显著提升;Hashtable
相同的功能规范,即支持Hashtable 的所有功能(注意:它不 允许将 null 用作键或值);