[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:
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).
Uno, ejemplos de prueba
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}
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
:
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 }
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 }
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 具体做了什么?
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; }
由此可见, 并没有value值进行强制的nullCheck.
4)HashTable有一个contains(Object value)功能和containsValue(Object value)功能一样。
这里我们可以直接对比HashMap和HashTable有关Contains的方法:
HashTable中的contains方法在HashMap中就被取消了, 那么我们来具体看下HashTable中的contains方法的作用:
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 }
然后再看下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值,而且用与代替求模:
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 }
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 HashTable
es diferente de las demás. La principal diferencia es la granularidad del bloqueo y la forma de bloquear. ConcurrentHashMap
La granularidad del bloqueo HashTable
es 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