`

HashMap、Hashtable、ConcurrentHashMap等深入分析

    博客分类:
  • java
阅读更多

Map用于存储“key-value”元素对,它将一个key映射到一个而且只能是唯一的一个value。Map可以使用多种实现方式,HashMap的实现采用的是Hash表;而TreeMap采用的是红黑树。

原文地址:HashMap、Hashtable、ConcurrentHashMap等深入分析。转载请注明出处,谢谢

1、HashMap的实现原理:

HashMap使用hash的方式实现。而hash表最常见的实现方式就是数组+链表的形式。Hash表结合了数组和链表两者的优点,数组寻址容易但插入删除困难,而链表寻址困难但插入删除容易。HashMap其实就是一个“链表的数组”。

上图是HashMap的结构示意图。HashMap底层就是一个数组,而数组中的每一项又是一个链表。当HashMap新建时,就会初始化数组,Entry就是数组中的元素。而一个元素到底该存在什么位置是怎么确定的呢?一般情况是通过hash(key)&(len-1)获得的结果表示数组的下标,也就是元素的key的哈希值对数组长度取模得到。如果通过此方法获取到相同的下标,则会根据Entry来形成链表。Entry是HashMap中的一个静态内部类,它包括key、value、next等属性,其中next的作用是指向下一个Entry。打个比方, 第一个键值对A进来,通过计算其key的hash得到的index=0,记做:Entry[0] = A。一会后又进来一个键值对B,通过计算其index也等于0,现在怎么办?HashMap会这样做:B.next = A,Entry[0] = B,如果又进来C,index也等于0,那么C.next = B,Entry[0] = C;这样我们发现index=0的地方其实存取了A,B,C三个键值对,他们通过next这个属性链接在一起。所以疑问不用担心。也就是说数组中存储的是最后插入的元素。

 

HashMap里面也包含一些优化方面的实现。比如:Entry[]的长度一定后,随着map里面数据的越来越长,这样同一个index的链就会很长,会不会影响性能?HashMap里面设置一个负载因子(也称为容客率),随着map的size越来越大,Entry[]会以一定的规则加长长度。

2、HashMap的存取实现:

   存储操作:

 

public V put(K key, V value) {
        return putImpl(key, value);
    }
    V putImpl(K key, V value) {
        Entry<K,V> entry;
		// HashMap允许存放null键和null值。
        if(key == null) {
// 当key为null时,调用putForNullKey方法,将value放置在数组第一个位置。
            entry = findNullKeyEntry();
            if (entry == null) {
                modCount++;
                entry = createHashedEntry(null, 0, 0);
                if (++elementCount > threshold) {
                    rehash();
                }
            }
        } else {
		    // 根据key的hashCode重新计算hash值。
            int hash = key.hashCode();
		    // 搜索指定hash值所对应table中的索引。
            int index = hash & (elementData.length - 1);
            entry = findNonNullKeyEntry(key, index, hash);
            if (entry == null) {
                modCount++;
                entry = createHashedEntry(key, index, hash);
                if (++elementCount > threshold) {
                    rehash();
                }
            }
            if ((cache != null) && (hash >> CACHE_BIT_SIZE == 0)
                    && (key instanceof Integer)) {
                cache[hash] = value;
            }
        }
        V result = entry.value;
        entry.value = value;
        return result;
    }
 

 

从源代码中可以看出:当我们往HashMap中put元素的时候,先根据key的hashCode重新计算hash值,根据hash值得到这个元素在数组中的位置(即下标),如果数组该位置上已经存放有其他元素了,那么在这个位置上的元素将以链表的形式存放,新加入的放在链头,最先加入的放在链尾。如果数组该位置上没有元素,就直接将该元素放到此数组中的该位置上。

 

读取操作:

 

    public V get(Object key) {
        if (key != null && cache != null && key instanceof Integer) {
            int index = ((Integer) key).intValue();
            if (index >> CACHE_BIT_SIZE == 0) {
                return (V) cache[index];
            }
        }
        Entry<K, V> m = getEntry(key);
        if (m != null) {
            return m.value;
        }
        return null;
    }
 

 

有了上面存储时的hash算法作为基础,理解起来这段代码就很容易了。从上面的源代码中可以看出:从HashMap中get元素时,首先计算key的hashCode,找到数组中对应位置的某一元素,然后通过key的equals方法在对应位置的链表中找到需要的元素。

3、HashMap和Hashtable主要区别:

(1)HashMap和Hashtable都实现了Map接口,但是Hashtable的实现是基于Dictionary抽象类。

(2)HashMap允许null值(key和value都可以),Hashtable不允许null值(key和value都不可以)。因此,HashMap使用get()方法获取到null,可能是某个键对于value为null,也可能是没有该键,需要使用containsKey()来判断是否包含某个键值。Hashtable没有该问题,两种方式都可以。

(3)HashMap中hash数组的默认大小是16,客座率是0.75,增加方式是直接翻倍,也就是2的指数, 当HashMap容量达到了16*0.75 = 12,它的容量便会拓展到16*2=32。Hashtable中hash数组默认大小是11,增加的方式是old*2+1。

(4)Hashtable使用Enumeration,HashMap使用Iterator。以上只是表面的不同,它们的实现也有很大的不同。

 

(5)两者最大的区别在于HashMap不是线程安全的,而Hashtble的方法是线程安全的,它的方法是同步过了的,所以在多线程场合要手动同步HashMap。但java中提供了两种生成同步的HashMap的方法。见下文。

4、潜在的线程安全问题:

Map m =Collections.synchronizedMap(new HashMap(...));

 

该方法返回的是一个SynchronizedMap的实例。SynchronizedMap类是定义在Collections中的一个静态内部类。它实现了Map接口,并对其中的每一个方法实现,通过synchronized关键字进行了同步控制。这种方式获取的map中每个方法都是同步的,但并不意味着就一定是线程安全的。如下面代码:

 

Map<String,String>map=Collections.synchronizedMap(new TreeMap<String,String>());
map.put("key1","value1");
map.put("key2","value2");
if(map.containsKey("key1")){
	map.remove("key1");
}
Set<Entry<String,String>> entries = map.entrySet();
Iterator<Entry<String,String>> iter = entries.iterator();
while(iter.hasNext()){
	System.out.println(iter.next()); //Willthrow ConcurrentModificationException
	map.remove("key2");
}

   

 

上边这段代码是在map删除一个元素之前,先判断这个元素是否存在。如果两个有线程,A线程首先执行containsKey方法,返回true,准备执行remove操作,这个时候,如果线程B也执行了containsKey方法,同样会返回true,然后准备执行remove方法,线程A,B有一个先执行remove可以删除该元素,当另一个执行时,会发现该元素已经没有了。

5、线程安全的HsahMap:

    Hashtable虽然是线程安全的,它采用的锁机制是一次锁住整个hash表,同一时间点只能有一个线程操作,所有访问Hashtable的线程都要竞争同一把锁。在JDK5中,新增了ConcurrentMap接口和它的一个实现类ConcurrentHashMap。这个map可以实现与Hashtable相同的线程安全,但效率要比其高很多。ConcurrentHashMap的锁机制是一次锁住一个桶,它将Hash表默认分成16个桶,对于常见操作如get、set、remove等,只需要操作当前桶即可。这样原来只能有1个线程操作,变成了最多可以16个线程同时操作,性能提高是显而易见的。

6、遍历方式:

    HashMap的遍历有两种常用的方法,那就是使用keyset或者entryset来进行遍历,如下;

Map<String, String> map = new HashMap<String, String>();
//方式一:
Iterator<Map.Entry<String, String>> iterator = map.entrySet().iterator();
while (iterator.hasNext()) {
	Map.Entry<String, String> entry = iterator.next();
	entry.getKey();
	entry.getValue();
}
//方式二:
for (Entry<String, String> entry : map.entrySet()) {
	entry.getKey();
	entry.getValue();
}
//方式三:
Iterator iterator = map.keySet().iterator();
while (iterator.hasNext()) {
	Object key = iterator.next();
	Object val = map.get(key);
}
//方式四:
for (String key : map.keySet()) {
	Object value = map.get(key);
}

  

使用entrySet时,将map中key和value都放在了entry对象中,所以取的时候直接取即可。使用keySet时,其实是遍历了2次,第一次形成iterator,第二次在HashMap中取出key对应的value。 

 

  • 大小: 17.1 KB
分享到:
评论

相关推荐

    HashMap,HashTable,ConcurrentHashMap之关联.docx

    HashMap,HashTable,ConcurrentHashMap 之关联 HashMap、HashTable、ConcurrentHashMap 是 Java 集合类中的重点,以下是对它们的详细解释: HashMap HashMap 是非线程安全的,它的键和值都允许有 null 值存在。...

    hashMap和hashTable的区别

    ### hashMap和hashTable的区别 #### 一、简介与基本概念 `HashMap` 和 `HashTable` 都是 Java 集合框架中非常重要的数据结构,它们都实现了 `Map` 接口,用于存储键值对。尽管它们在功能上有很多相似之处,但在...

    HashMap与HashTable区别

    如果需要线程安全,可以通过其他手段如`ConcurrentHashMap`等实现。 ### 总结 `HashMap`和`HashTable`各有优势,在选择使用哪种数据结构时,需要根据具体的应用场景来决定。如果程序运行在单线程环境中或者能够...

    HashMap与HashTable的区别(含源码分析)

    - `HashTable`中的`containsKey()`, `containsValue()`, `put()`, `remove()`等方法返回值为`boolean`,而`HashMap`这些方法返回的是`Object`,如`put()`返回的是旧值或null。 - `HashTable`的迭代器是失败快速的...

    hashtable和hashmap的区别

    接下来,我们将详细探讨`Hashtable`和`HashMap`之间的区别,并分析它们各自的优缺点。 #### 1. 线程安全性 - **Hashtable**: 是线程安全的。`Hashtable`的所有关键操作(如`put()`、`get()`)都是同步的,这意味着...

    hashmap和hashtable的区别.docx

    - Hashtable 使用了 Java 的遗留命名约定,如 `put()`、`get()` 等,而 HashMap 遵循了 Java 集合框架的一般命名约定,如 `put(K key, V value)`、`get(Object key)`。 综上所述,选择 HashMap 还是 Hashtable ...

    HashMap底层实现原理HashMap与HashTable区别HashMap与HashSet区别.docx

    HashMap与HashTable的主要区别在于线程安全性和对null值的支持。HashMap是非同步的,意味着在多线程环境中,如果不进行适当的同步控制,可能会导致数据不一致。而HashTable是同步的,因此它在多线程环境下的安全性更...

    经典讲解List和ArrayList和Vector和HashTable和HashMap区别

    在Java编程语言中,集合框架是处理对象数组的重要工具,其中`List`、`ArrayList`、`Vector`、`HashTable`和`HashMap`是五个关键的接口和类,它们各有不同的特性和用途。以下是这些概念的详细解释: 1. **List接口**...

    HashMap 和 Hashtable的区别

    HashMap 和 Hashtable 是 Java 集合框架中两个重要的 Map 实现,它们虽然都是用来存储键值对的数据结构,但在很多方面存在显著的区别。以下将详细分析它们的主要差异、工作原理和适用场景。 1. **线程安全性** - `...

    HashMap和HashTable区别共2页.pdf.zi

    在Java编程语言中,`HashMap`和`HashTable`是两种常用的集合类,它们都是用于存储键值对的数据结构。这两个类都实现了`Map`接口,但它们之间存在一些显著的区别,这些差异主要体现在线程安全性、null值处理、迭代...

    java面试题——详解HashMap和Hashtable 的区别

    - 由于 `Hashtable` 的线程安全特性可以通过其他方式实现,比如使用 `Collections.synchronizedMap()` 工具方法,因此 `Hashtable` 在现代 Java 开发中逐渐被弃用,推荐使用 `ConcurrentHashMap` 来代替,它提供了...

    HashMap和Hashtable的区别

    在Java编程语言中,`HashMap`和`Hashtable`都是实现`Map`接口的容器,用于存储键值对数据。它们的主要区别在于线程安全性、继承结构、提供的接口、对`null`值的支持、容量初始化与扩容策略以及哈希计算与冲突解决...

    java中HashMap,LinkedHashMap,TreeMap,HashTable的区别

    本文将详细分析四种常用的`Map`实现类:`HashMap`, `LinkedHashMap`, `TreeMap`以及`HashTable`之间的区别。 #### 1. HashMap `HashMap`是一种基于哈希表实现的`Map`接口,提供了一个非同步的、允许使用`null`键和...

    浅析java中ArrayList与Vector的区别以及HashMap与Hashtable的区别

    总结起来,ArrayList和Vector的选择主要取决于是否需要线程安全,而HashMap和Hashtable的选择则需要权衡线程安全、null值的使用以及性能等因素。在多线程且对性能要求较高的环境中,通常推荐使用ArrayList配合手动...

    hashmap面试题_hashmap_

    面试中,可能会被问及HashMap的性能优化、内存占用分析、以及在特定场景下的选择,如并发环境、内存敏感的应用等。 总结,HashMap是Java编程中的基础工具,掌握其工作原理和常见面试题,不仅能帮助我们应对面试,更...

    并发编程atomic&collections-课上笔记1

    本文主要讲述了 Java 中的并发编程,包括 atomic 包的介绍、CAS 算法的原理、ABA 问题的解决方案,以及 collections 中的 HashMap、HashTable 和 ConcurrentHashMap 的源码分析。 Atomic 包的介绍 ----------------...

    HashMap底层原理.pdf

    对于HashMap的线程安全问题,在JDK 1.7版本中,HashMap的多线程操作可能导致数据丢失等问题。而在JDK 1.8中,虽然在某些操作(如put操作)中增加了同步控制,但仍未从根本上解决多线程问题。如果需要线程安全的...

    关于如何解决HashMap线程安全问题的介绍

    3. 使用HashTable:虽然HashTable是线程安全的,但由于其所有操作都是同步的,所以在多线程并发访问时,性能较低。因此,在现代Java开发中,通常更倾向于使用ConcurrentHashMap。 4. 避免在多线程环境中直接使用...

    Java容器HashMap与HashTable详解

    此外,HashTable的put()和get()等方法会抛出NullPointerException和IllegalStateException,而不是像HashMap那样处理null值。 HashMap更适合单线程环境或在性能要求较高的多线程环境中使用,而HashTable则适合对...

Global site tag (gtag.js) - Google Analytics