[Analyse du code source] La différence entre HashMap et HashTable (analyse et interprétation du code source)


[Analyse du code source] La différence entre HashMap et HashTable (analyse et interprétation du code source)

Avant-propos: 
C'est un autre excellent week-end, mais malheureusement je me suis levé un peu tard aujourd'hui. Jetons un coup d'œil à HashMap et HashTable pour voir quelle est la différence entre eux.

Voyons d'abord une définition plus confuse:

Copier le code

L'instance Hashtable a deux paramètres qui affectent ses performances: la capacité initiale  et  le facteur de charge . La capacité est le nombre de compartiments dans la table de hachage et la capacité initiale est la capacité lorsque la table de hachage est créée. Notez que l'état de la table de hachage est ouvert: en cas de "collision de hachage", un seul compartiment stockera plusieurs entrées, et ces entrées doivent être recherchées dans l'ordre. Le facteur de charge mesure le niveau de remplissage de la table de hachage avant que sa capacité ne soit automatiquement augmentée. Les deux paramètres, capacité initiale et facteur de charge, ne sont que des indices de mise en œuvre. Les détails spécifiques sur le moment et l'opportunité d'appeler la méthode rehash dépendent de l'implémentation.

  HashTable est une implémentation de l'interface Map basée sur une table de hachage. Cette implémentation fournit toutes les opérations de mappage facultatives et permet l'utilisation de valeurs nulles et de clés nulles. (Sauf pour être asynchrone et autoriser l'utilisation de null, la classe HashMap est à peu près la même que Hashtable.) Cette classe ne garantit pas l'ordre du mappage, en particulier elle ne garantit pas que la commande durera éternellement.  Cette implémentation suppose que la fonction de hachage distribue les éléments de manière appropriée entre les compartiments, ce qui peut fournir des performances stables pour les opérations de base (get et put). Le temps requis pour parcourir la vue de collection est proportionnel à la «capacité» (le nombre de compartiments) de l'instance HashMap et à sa taille (le nombre de relations de mappage clé-valeur). Par conséquent, si les performances itératives sont importantes, ne définissez pas la capacité initiale trop élevée (ou ne définissez pas le facteur de charge trop bas).
  

Copier le code

 

Un, des exemples de preuve

Copier le code

 1 2 public static void main (String [] args) {3 Map <String, String> map = new HashMap <String, String> (); 4 map.put ("a", "aaa"); 5 map.put ("b", "bbb"); 6 map.put ("c", "ccc"); 7 map.put ("d", "ddd"); 
 8 Iterator <String> iterator = map.keySet (). Iterator (); 9 while (iterator.hasNext ()) {10 Object key = iterator.next (); 11 System.out.println ("map.get (key) is:" + map.get (key)); 12} 13 14 Hashtable <String, String> tab = new Hashtable <String, String> (); 15 tab.put ("a", "aaa"); 16 tab.put ("b", "bbb"); 17 tab.put ("c", "ccc"); 18 tab.  
19 Iterator <String> iterator_1 = tab.keySet (). Iterator (); 20 while (iterator_1.hasNext ()) {21 Object key = iterator_1.next (); 22 System.out.println ("tab.get (clé ) est: "+ tab.get (clé)); 23} 24} 25}

Copier le code

Tout d'abord, il y a un tel morceau de code ci-dessus, alors quelle est sa sortie? 

Comme vous pouvez le voir, HashMap est sorti dans l'ordre normal, tandis que l'ordre de sortie de HashTable est un peu bizarre.2

, L'analyse du code source
voit les résultats ci-dessus, puis jetons un coup d'œil au code source de HashMap et HashTable séparément.

Tout d'abord, je veux inculquer quelques idées , Et puis selon ces règles définies (résumées par les prédécesseurs), puis accédez au code source pour le

savoir.1) HashTable est synchrone, HashMap est les méthodes put and get de HashTable asynchrone
:

Copier le code

 1 public synchronized V put(K key, V value) { 2         // Make sure the value is not null 3         if (value == null) { 4             throw new NullPointerException(); 5         } 6  7         // Makes sure the key is not already in the hashtable. 8         Entry<?,?> tab[] = table; 9         int hash = key.hashCode();10         int index = (hash & 0x7FFFFFFF) % tab.length;11         @SuppressWarnings("unchecked")12         Entry<K,V> entry = (Entry<K,V>)tab[index];13         for(; entry != null ; entry = entry.next) {14             if ((entry.hash == hash) && entry.key.equals(key)) {15                 V old = entry.value;16                 entry.value = value;17                 return old;18             }19         }20 21         addEntry(hash, key, value, index);22         return null;23     }

Copier le code

 

Copier le code

 1 public synchronized V get(Object key) { 2         Entry<?,?> tab[] = table; 3         int hash = key.hashCode(); 4         int index = (hash & 0x7FFFFFFF) % tab.length; 5         for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) { 6             if ((e.hash == hash) && e.key.equals(key)) { 7                 return (V)e.value; 8             } 9         }10         return null;11     }

Copier le code

HashMap中put和get方法:

1 public V put(K key, V value) {2       return putVal(hash(key), key, value, false, true);3 }
1 public V get(Object key) {2         Node<K,V> e;3         return (e = getNode(hash(key), key)) == null ? null : e.value;4 }

从以上代码中就能显而易见的看到HashTable中的put和get方法是被synchronized修饰的, 这种做的区别呢? 
由于非线程安全,效率上可能高于Hashtable. 如果当多个线程访问时, 我们可以使用HashTable或者通过Collections.synchronizedMap来同步HashMap。


2)HashTable与HashMap实现的接口一致,但HashTable继承自Dictionary,而HashMap继承自AbstractMap;
HashTable:

 

 HashMap:
 

 

3)HashTable不允许null值(key和value都不可以) ,HashMap允许null值(key和value都可以)。

 在1中我们可以看到HashTable如果value为null就会直接抛出: throw new NullPointerException();
 那么再看看HashMap put value 具体做了什么?

Copier le code

public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
}

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; K k;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
}

Copier le code

由此可见, 并没有value值进行强制的nullCheck.

4)HashTable有一个contains(Object value)功能和containsValue(Object value)功能一样。
这里我们可以直接对比HashMap和HashTable有关Contains的方法:

HashTable中的contains方法在HashMap中就被取消了, 那么我们来具体看下HashTable中的contains方法的作用: 

Copier le code

 1 public synchronized boolean contains(Object value) { 2         if (value == null) { 3             throw new NullPointerException(); 4         } 5  6         Entry<?,?> tab[] = table; 7         for (int i = tab.length ; i-- > 0 ;) { 8             for (Entry<?,?> e = tab[i] ; e != null ; e = e.next) { 9                 if (e.value.equals(value)) {10                     return true;11                 }12             }13         }14         return false;15 }

Copier le code

然后再看下HashTable中的containsValue方法:

1 public boolean containsValue(Object value) {2         return contains(value);3 }

这里就很明显了, contains方法其实做的事情就是containsValue, 里面将value值使用equals进行对比, 所以在HashTable中直接取消了contains方法而是使用containsValue代替.

5)HashTable使用Enumeration进行遍历,HashMap使用Iterator进行遍历。


首先是HashTable中:

 View Code

然后是HashMap中:

 View Code

废弃的接口:Enumeration
Enumeration接口是JDK1.0时推出的,是最好的迭代输出接口,最早使用Vector(现在推荐使用ArrayList)时就是使用Enumeration接口进行输出。虽然Enumeration是一个旧的类,但是在JDK1.5之后为Enumeration类进行了扩充,增加了泛型的操作应用。

Enumeration接口常用的方法有hasMoreElements()(判断是否有下一个值)和 nextElement()(取出当前元素),这些方法的功能跟Iterator类似,只是Iterator中存在删除数据的方法,而此接口不存在删除操作。

为什么还要继续使用Enumeration接口
Enumeration和Iterator接口功能相似,而且Iterator的功能还比Enumeration多,那么为什么还要使用Enumeration?这是因为java的发展经历了很长时间,一些比较古老的系统或者类库中的方法还在使用Enumeration接口,因此为了兼容,还是需要使用Enumeration。

下面给出HashTable和HashMap的几种遍历方式:

 Person.java

 Test.java

6)HashTable中hash数组默认大小是11,增加的方式是 old*2+1。HashMap中hash数组的默认大小是16,而且一定是2的指数。

HashMap:

1 /**2      * The default initial capacity - MUST be a power of two.3      */4     static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

HashTable:通常,默认加载因子是 0.75, 这是在时间和空间成本上寻求一种折衷。加载因子过高虽然减少了空间开销,但同时也增加了查找某个条目的时间(在大多数 Hashtable 操作中,包括 get 和 put 操作,都反映了这一点)。

1  // 默认构造函数。2 public Hashtable() {3     // 默认构造函数,指定的容量大小是11;加载因子是0.754     this(11, 0.75f);5 }

 

7)哈希值的使用不同
HashTable:,HashTable直接使用对象的hashCode

1 int hash = key.hashCode();2 int index = (hash & 0x7FFFFFFF) % tab.length;

HashMap:HashMap重新计算hash值,而且用与代替求模:

Copier le code

1 int hash = hash(k);2 int i = indexFor(hash, table.length);3 static int hash(Object x) {4 h ^= (h >>> 20) ^ (h >>> 12);5      return h ^ (h >>> 7) ^ (h >>> 4);6 }7 static int indexFor(int h, int length) {8 return h & (length-1);9 }

Copier le code

 

3,其他关联
3.1HashMap与HashSet的关系

a、HashSet底层是采用HashMap实现的:

1 public HashSet() {2     map = new HashMap<E,Object>();3 }

b. Lorsque la méthode add de HashSet est appelée, une ligne (paire clé-valeur) est en fait ajoutée au HashMap. La clé de la ligne est l'objet ajouté au HashSet et la valeur de la ligne est une constante de type Object.

1 objet final statique privé PRESENT = nouvel objet (); public boolean add (E e) {  
2 return map.put (e, PRESENT) == null; 
3}  
4 public boolean remove (Object o) {  
5 return map.remove (o) == PRESENT; 
6}

3.2 La relation entre HashMap et ConcurrentHashMap

En ce qui concerne cette partie du contenu, je vous suggère de parcourir le code source. Il ConcurrentHashMap s'agit également d'une classe de collection thread-safe. Elle HashTableest également différente de l'autre. La principale différence est la granularité du verrouillage et la façon de verrouiller. ConcurrentHashMap La granularité du verrouillage HashTableest un peu plus fine. Divisez les données en segments et attribuez un verrou à chaque segment de données. Lorsqu'un thread occupe le verrou pour accéder à un segment de données, les données des autres segments sont également accessibles par d'autres threads.

Pour plus d'informations, veuillez consulter: http://www.hollischuang.com/archives/82


4. Le code source de HashTable est disponible

 

 Afficher le code

 

 

 

Catégorie:  Lecture du code source

Un bon article devrait  faire attention à mon  article préféré  

Est une fleur considérée comme romantique

Propulsé par .NET 5.0.0-rc.2.20475.5 sur Kubernetes


Je suppose que tu aimes

Origine blog.51cto.com/7592962/2543739
conseillé
Classement