[Análisis del código fuente] La diferencia entre HashMap y HashTable (análisis e interpretación del código fuente)


[Análisis del código fuente] La diferencia entre HashMap y HashTable (análisis e interpretación del código fuente)

Prólogo: 
Es otro gran fin de semana, pero desafortunadamente me levanté un poco tarde hoy. Echemos un vistazo a HashMap y HashTable para ver cuál es la diferencia entre ellos.

Comencemos con una definición más confusa:

Copiar codigo

La instancia Hashtable tiene dos parámetros que afectan su rendimiento: la capacidad inicial  y  el factor de carga . La capacidad es la cantidad de depósitos en la tabla hash y la capacidad inicial es la capacidad cuando se crea la tabla hash. Tenga en cuenta que el estado de la tabla hash es abierto: en el caso de una "colisión hash", un solo depósito almacenará varias entradas, y estas entradas deben buscarse en orden. El factor de carga es una medida de cuán llena puede estar la tabla hash antes de que su capacidad aumente automáticamente. Los dos parámetros, capacidad inicial y factor de carga, son solo pistas para la implementación. Los detalles específicos sobre cuándo y si llamar al método de refrito dependen de la implementación.

  HashTable es una implementación de la interfaz Map basada en una tabla hash. Esta implementación proporciona todas las operaciones de mapeo opcionales y permite el uso de valores nulos y claves nulas. (Excepto por ser asíncrona y permitir el uso de null, la clase HashMap es aproximadamente la misma que Hashtable). Esta clase no garantiza el orden de asignación, especialmente no garantiza que el orden dure para siempre.  Esta implementación asume que la función hash distribuye los elementos de manera apropiada entre los depósitos, lo que puede proporcionar un rendimiento estable para operaciones básicas (obtener y poner). El tiempo que lleva iterar la vista de colección es proporcional a la "capacidad" (el número de depósitos) de la instancia de HashMap y su tamaño (el número de relaciones de mapeo clave-valor). Por lo tanto, si el rendimiento iterativo es importante, no establezca la capacidad inicial demasiado alta (o establezca el factor de carga demasiado bajo).
  

Copiar codigo

 

Uno, ejemplos de prueba

Copiar codigo

 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 Iterador <String> iterador = map.keySet (). Iterator (); 9 while (iterator.hasNext ()) {10 Object key = iterator.next (); 11 System.out.println ("map.get (key) es:" + 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 Iterador <String> iterator_1 = tab.keySet (). Iterator (); 20 while (iterator_1.hasNext ()) {21 Clave de objeto = iterator_1.next (); 22 System.out.println ("tab.get (clave ) es: "+ tab.get (clave)); 23} 24} 25}

Copiar codigo

En primer lugar, hay un código de este tipo arriba, entonces, ¿cuál es su salida? 

Como puede ver, HashMap se genera en el orden normal, mientras que el orden de salida de HashTable es un poco extraño.2

, El análisis del código fuente
ve los resultados anteriores, luego echemos un vistazo al código fuente de HashMap y HashTable por separado. En

primer lugar, quiero inculcar algunas ideas , Y luego de acuerdo con estas reglas definidas (resumidas por los predecesores) y luego ir al código fuente para averiguarlo.

1) HashTable es síncrono, HashMap es los métodos put y get de HashTable asincrónico
:

Copiar codigo

 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     }

Copiar codigo

 

Copiar codigo

 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     }

Copiar codigo

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 具体做了什么?

Copiar codigo

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;
}

Copiar codigo

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

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

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

Copiar codigo

 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 }

Copiar codigo

然后再看下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值,而且用与代替求模:

Copiar codigo

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 }

Copiar codigo

 

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

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

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

b. Cuando se llama al método add de HashSet, se agrega una fila (par clave-valor) al HashMap. La clave de la fila es el objeto agregado al HashSet, y el valor de la fila es una constante de tipo Object.

1 Objeto final estático privado PRESENTE = nuevo Objeto (); public boolean add (E e) {  
2 return map.put (e, PRESENT) == null; 
3}  
4 public boolean remove (Object o) {  
5 return map.remove (o) == PRESENTE; 
6}

3.2 La relación entre HashMap y ConcurrentHashMap

Con respecto a esta parte del contenido, te sugiero que revises el código fuente. También ConcurrentHashMap es una clase de recopilación segura para subprocesos. También HashTablees diferente de las demás. La principal diferencia es la granularidad del bloqueo y la forma de bloquear. ConcurrentHashMap La granularidad del bloqueo HashTablees un poco más fina. Divida los datos en segmentos y asigne un bloqueo a cada segmento de datos. Cuando un subproceso ocupa el bloqueo para acceder a un segmento de datos, otros subprocesos también pueden acceder a los datos de otros segmentos.

Para obtener más información, consulte: http://www.hollischuang.com/archives/82


4. El código fuente de HashTable está disponible

 

 Ver código

 

 

 

Categoría:  lectura de código fuente

Buen artículo debe  prestar atención a mi  artículo favorito  

¿Es una flor considerada romántica?

Con tecnología de .NET 5.0.0-rc.2.20475.5 en Kubernetes


Supongo que te gusta

Origin blog.51cto.com/7592962/2543739
Recomendado
Clasificación