1、HashMap、HashTable、ConcurrentHashMap的区别
HashMap和HashTable都实现了Map接口,里面存放的元素不保证有序,并且不存在相同元素;
区别(线程安全和保存值是否为null方面):
(1) HashMap和HashTable在功能上基本相同,但HashMap是线程不安全的,HashTable是线程安全的;
HashMap的put源码如下:
public V put(K key, V value) { if (table == EMPTY_TABLE) { inflateTable(threshold); } if (key == null) return putForNullKey(value); //说明key和value值都是可以为null int hash = hash(key); int i = indexFor(hash, table.length); for (Entry e = table[i]; e != null; e = e.next) { Object k; if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } modCount++; addEntry(hash, key, value, i); return null;}
(2)可以看到,HashMap的key和value都是可以为null的,当get()方法返回null值时,HashMap中可能存在某个key,只不过该key值对应的value为null,也有可能是HashM中不存在该key,所以不能使用get()==null来判断是否存在某个key值,对于HashMap和HashTable,提供了containsKey()方法来判断是否存在某个key。
HashTable的put源码如下:
public synchronized V put(K key, V value) { // Make sure the value is not null if (value == null) { //当value==null的时候,会抛出异常 throw new NullPointerException(); } // Makes sure the key is not already in the hashtable. Entry tab[] = table; int hash = hash(key); int index = (hash & 0x7FFFFFFF) % tab.length; for (Entry e = tab[index] ; e != null ; e = e.next) { if ((e.hash == hash) && e.key.equals(key)) { V old = e.value; e.value = value; return old; } } modCount++; 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 e = tab[index]; tab[index] = new Entry<>(hash, key, value, e); count++; return null;}
(3)HashTable是不允许key和value为null的。HashTable中的方法大部分是同步的,因此HashTable是线程安全的。
拓展:
(1) 影响HashMap(或HashTable)性能的两个因素:初始容量和load factor;
HashMap中有如下描述: When the number of entries in the hash table exceeds the product of the load factor and the current capacity,
the hash table is rehashed (that is, internal data structures are rebuilt)
当我们Hash表中数据记录的大小超过当前容量,Hash表会进行rehash操作,其实就是自动扩容,这种操作一般会比较耗时。所以当我们能够预估Hash表大小时,在初始化的时候就尽量指定初始容量,避免中途Hash表重新扩容操作,如:
HashMap map = new HashMap(20);
(类似可以指定容量的还有ArrayList、Vector)
(2)使用选择上,当我们需要保证线程安全,HashTable优先选择。当我们程序本身就是线程安全的,HashMap是优先选择。
其实HashTable也只是保证在数据结构层面上的同步,对于整个程序还是需要进行多线程并发控制;在JDK后期版本中,对于HashMap,可以通过Collections获得同步的HashMap;如下:
Map m = Collections.synchronizedMap(new HashMap(...));
这种方式获得了具有同步能力的HashMap。
(3)在JDK1.5以后,出现了ConcurrentHashMap,它可以很好地解决在并发程序中使用HashMap的问题,ConcurrentHashMap和HashTable功能很像,不允许为null的key或value,但它不是通过给方法加synchronized方法进行并发控制的。
在ConcurrentHashMap中使用分段锁技术Segment,将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问,能够实现真正的并发访问。效率也比HashTable好的多。
2、TreeMap、HashMap、LinkedHashMap的区别
关于Map集合,前面几篇都有讲过,可以去回顾一下。而TreeMap、HashMap、LinkedHashMap都是Map的一些具体实现类,其关系图如下:
其中,HashMap和HashTable主要区别在线程安全方面和存储null值方面。HashMap前面讨论的已经比较多了,下面说说LinkedHashMap和TreeMap。
(1)LinkedHashMap保存了数据的插入顺序,底层是通过一个双链表的数据结构来维持这个插入顺序的。key和value都可以为null;
(2)TreeMap实现了SortMap接口,它保存的记录是根据键值key排序,默认是按key升序排列。也可以指定排序的Comparator。
HashMap、LinkedHashMap和TreeMap都是线程不安全的,HashTable是线程安全的。提供两种遍历Map的方法如下:
(1)推荐方式:
Map map = new HashMap(20);for (Map.Entry entry : map.entrySet()){ //直接遍历出Entry System.out.println("key-->"+entry.getKey()+