java进阶|HashTable源码分析和理解
键值对集合Map,HashTable都是我们常用的,但是随着多线程环境以及代码的普及,ConcurrentHashMap这样的并发集合也常用了起来,今天我们先来分析一下HashTable的源码。
HashTable的结构图如下,以及我们可以去看下类继承关系图。便于自己理解,这里指的都是自己分析的。
public class Hashtable<K,V>extends Dictionary<K,V>implements Map<K,V>, Cloneable, java.io.Serializable {}
首先我们先看下HashTable的put()方法,毕竟使用一个集合就是为了存取数据的。
public synchronized V put(K key, V value) {//判断传入的value值是否为null,若为null直接抛出空NPE异常if (value == null) {throw new NullPointerException();}// Makes sure the key is not already in the hashtable.Entry<?,?> tab[] = table;int hash = key.hashCode();//获取键key的hash值int index = (hash & 0x7FFFFFFF) % tab.length;//获取hash槽的位置,之所以&上是为了使hash分布更均匀@SuppressWarnings("unchecked")Entry<K,V> entry = (Entry<K,V>)tab[index];//获取指定索引位置的entry,然后进行循环判断for(; entry != null ; entry = entry.next) {//若hash值相等且查询出的key与put进去的值相等,则进行值替换if ((entry.hash == hash) && entry.key.equals(key)) {V old = entry.value;entry.value = value;return old;}}//否则,则是新加入一个entry节点数据addEntry(hash, key, value, index);return null;}步骤一:private void addEntry(int hash, K key, V value, int index) {modCount++;Entry<?,?> tab[] = table;//首先判断当前hashTable的元素个数是否大于阈值threashold,若大于则需要rehash()//接下来再看如何rehash()的if (count >= threshold) {rehash();tab = table;hash = key.hashCode();index = (hash & 0x7FFFFFFF) % tab.length;}// Creates the new entry.@SuppressWarnings("unchecked")Entry<K,V> e = (Entry<K,V>) tab[index];tab[index] = new Entry<>(hash, key, value, e);//步骤一count++;}步骤一:这里面主要是定义一个内部类用于将元素进行数据的保存private static class Entry<K,V> implements Map.Entry<K,V> {final int hash;final K key;V value;Entry<K,V> next;protected Entry(int hash, K key, V value, Entry<K,V> next) {this.hash = hash;this.key = key;this.value = value;this.next = next;}@SuppressWarnings("unchecked")protected Object clone() {return new Entry<>(hash, key, value,(next==null ? null : (Entry<K,V>) next.clone()));}// Map.Entry Opspublic K getKey() {return key;}public V getValue() {return value;}public V setValue(V value) {if (value == null)throw new NullPointerException();V oldValue = this.value;this.value = value;return oldValue;}public boolean equals(Object o) {if (!(o instanceof Map.Entry))return false;Map.Entry<?,?> e = (Map.Entry<?,?>)o;return (key==null ? e.getKey()==null : key.equals(e.getKey())) &&(value==null ? e.getValue()==null : value.equals(e.getValue()));}public int hashCode() {return hash ^ Objects.hashCode(value);}public String toString() {return key.toString()+"="+value.toString();}}private static class Entry<K,V> implements Map.Entry<K,V> {final int hash;final K key;V value;Entry<K,V> next;protected Entry(int hash, K key, V value, Entry<K,V> next) {this.hash = hash;this.key = key;this.value = value;this.next = next;}@SuppressWarnings("unchecked")protected Object clone() {return new Entry<>(hash, key, value,(next==null ? null : (Entry<K,V>) next.clone()));}// Map.Entry Opspublic K getKey() {return key;}public V getValue() {return value;}public V setValue(V value) {if (value == null)throw new NullPointerException();V oldValue = this.value;this.value = value;return oldValue;}public boolean equals(Object o) {if (!(o instanceof Map.Entry))return false;Map.Entry<?,?> e = (Map.Entry<?,?>)o;return (key==null ? e.getKey()==null : key.equals(e.getKey())) &&(value==null ? e.getValue()==null : value.equals(e.getValue()));}public int hashCode() {return hash ^ Objects.hashCode(value);}public String toString() {return key.toString()+"="+value.toString();}}
接下来我们分析一下rehash()方法,看下是如何扩容的。
protected void rehash() {//首先获取原来hashTable容量的大小int oldCapacity = table.length;Entry<?,?>[] oldMap = table;//然后计算新容量大小,2倍+1的容量大小int newCapacity = (oldCapacity << 1) + 1;//判断新扩容容量的大小是否大于最大值,若大于直接将最大容量赋予新容量if (newCapacity - MAX_ARRAY_SIZE > 0) {if (oldCapacity == MAX_ARRAY_SIZE)// Keep running with MAX_ARRAY_SIZE bucketsreturn;newCapacity = MAX_ARRAY_SIZE;}Entry<?,?>[] newMap = new Entry<?,?>[newCapacity];modCount++;//计算阈值threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);table = newMap;//将新的entry数据引用指向原来的table//循环遍历旧的数据,然后将旧的entry数据复制到新的entry里面,额,又是数据的拷贝for (int i = oldCapacity ; i-- > 0 ;) {for (Entry<K,V> old = (Entry<K,V>)oldMap[i] ; old != null ; ) {Entry<K,V> e = old;old = old.next;int index = (e.hash & 0x7FFFFFFF) % newCapacity;e.next = (Entry<K,V>)newMap[index];newMap[index] = e;}}}步骤一:/*** The maximum size of array to allocate.* Some VMs reserve some header words in an array.* Attempts to allocate larger arrays may result in* OutOfMemoryError: Requested array size exceeds VM limit*/private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
以上就是rehash()的过程,是不是也很简单呢。
接下来我们看下HashTable如何获取键的集合keySet()。
public Set<K> keySet() {if (keySet == null)//若keySet为null,则直接new一个空的集合进行返回keySet = Collections.synchronizedSet(new KeySet(), this);return keySet;}
继续看下HashTable如何获取值的集合valueSet()方法。
public Collection<V> values() {if (values==null)values = Collections.synchronizedCollection(new ValueCollection(),this);return values;}
判断HashTable集合数据是否为空isEmpty()方法。
public synchronized boolean isEmpty() {return count == 0;}
获取HashTable集合数据元素个数的size()方法。
public synchronized int size() {return count;}
判断HashTable集合数据是否包含指定的value值contains()方法。
public synchronized boolean contains(Object value) {//若传入的值为null直接抛出NPE,因为put进去的时候value值是不能为空的,所以查询的时候自然不能包含为null的数据if (value == null) {throw new NullPointerException();}//将table的引用赋值新的tab[]数组,然后循环遍历entry,获取entry的value进行一一比较,若相等,直接返回true//若不相等,则直接返回falseEntry<?,?> tab[] = table;for (int i = tab.length ; i-- > 0 ;) {for (Entry<?,?> e = tab[i] ; e != null ; e = e.next) {if (e.value.equals(value)) {return true;}}}return false;}
判断HashTable集合数据是否包含指定的key,containsKey()。
public synchronized boolean containsKey(Object key) {Entry<?,?> tab[] = table;//将table引用直接新的tab[]int hash = key.hashCode();//获取key的hashCode,然后获取hashTable的索引位置做准备int index = (hash & 0x7FFFFFFF) % tab.length;for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {//若循环遍历判断hash值以及key相等,则直接返回true,否则直接返回falseif ((e.hash == hash) && e.key.equals(key)) {return true;}}return false;}
获取HashTable集合数据的值,根据key获取value的get()方法。
public synchronized V get(Object key) {Entry<?,?> tab[] = table;//将table引用赋值新的tab[]引用int hash = key.hashCode();//计算key的hash值,然后为确定hashTable的索引值做准备int index = (hash & 0x7FFFFFFF) % tab.length;//循环遍历tab的每一个entry元素,判断hash值是否相等以及key是否相等//相等则直接返回true,否则返回falsefor (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {if ((e.hash == hash) && e.key.equals(key)) {return (V)e.value;}}return null;}
判断HashTable集合数据是否包含value值,containsValue()方法,这个方法是复用了contains()方法。
public boolean containsValue(Object value) {return contains(value);}
如何清空HashTable集合数据的元素信息:clear()方法。
public synchronized void clear() {Entry<?,?> tab[] = table;modCount++;for (int index = tab.length; --index >= 0; )tab[index] = null;//let's gc to collectcount = 0;//元素个数赋值为0}
根据key获取HashTable集合数据的值,若没有直接获取默认值getOrDefault()。
public synchronized V getOrDefault(Object key, V defaultValue) {V result = get(key);//根据key获取value值return (null == result) ? defaultValue : result;//若获取的值为null,则直接返回默认值defaultValue}
进行HashTable集合数据的遍历操作forEach()操作。
public synchronized void forEach(BiConsumer<? super K, ? super V> action) {Objects.requireNonNull(action); //首先判断要传入的action是否为nullfinal int expectedModCount = modCount;Entry<?, ?>[] tab = table;//将table直接赋值新的tab,然后进行循环遍历for (Entry<?, ?> entry : tab) {//while循环判断entry是否为nullwhile (entry != null) {//accept操作,关于Consumer的操作,自己可以看下怎么操作的action.accept((K)entry.key, (V)entry.value);entry = entry.next;if (expectedModCount != modCount) {throw new ConcurrentModificationException();}}}}
HashTable集合数据的putIfAbsent()方法,若key存在,则直接更新,否则新增一条数据。
public synchronized V putIfAbsent(K key, V value) {Objects.requireNonNull(value);//判断value值是否为null,若value为null则直接抛出NPEEntry<?,?> tab[] = table;int hash = key.hashCode();//计算key的hashCode,计算hash槽的位置做准备int index = (hash & 0x7FFFFFFF) % tab.length;@SuppressWarnings("unchecked")Entry<K,V> entry = (Entry<K,V>)tab[index];//循环遍历entry集合,判断hash值是否相等以及key是否相等,若相等,则直接更新,否则新增for (; entry != null; entry = entry.next) {if ((entry.hash == hash) && entry.key.equals(key)) {V old = entry.value;if (old == null) {entry.value = value;}return old;}}addEntry(hash, key, value, index);return null;}
进行HashTable集合数据的替换replace()方法。
public synchronized V replace(K key, V value) {Objects.requireNonNull(value);Entry<?,?> tab[] = table;int hash = key.hashCode();int index = (hash & 0x7FFFFFFF) % tab.length;@SuppressWarnings("unchecked")Entry<K,V> e = (Entry<K,V>)tab[index];for (; e != null; e = e.next) {if ((e.hash == hash) && e.key.equals(key)) {V oldValue = e.value;e.value = value;return oldValue;}}return null;}
HashTable集合数据的替换操作replace()方法。
public synchronized V replace(K key, V value) {Objects.requireNonNull(value);//首先判断value值是否为null,若为null直接抛出NPEEntry<?,?> tab[] = table;//将table赋值到新的tab[]然后进行后面的操作int hash = key.hashCode();//定位key的hashCode,为下面的定位hash槽进行位置确定做准备int index = (hash & 0x7FFFFFFF) % tab.length;@SuppressWarnings("unchecked")//获取指定index位置的entry值Entry<K,V> e = (Entry<K,V>)tab[index];for (; e != null; e = e.next) {//找到对应的hash值相等以及key相等的entry,然后获取旧值,将value进行赋值,然后返回旧值if ((e.hash == hash) && e.key.equals(key)) {V oldValue = e.value;e.value = value;return oldValue;}}return null;}
到这里,想对HashTable这个键值对集合源码分析的方法就结束了,其它的部分方法就不分析了,用到的很少,接下来继续按照以往的风格贴上一张HashTable的结构图,以及自己源码走读所用到的部分示例程序。
package com.wpw.springbootjuc.java8.map;import lombok.extern.slf4j.Slf4j;import java.util.Collection;import java.util.Hashtable;import java.util.Set;/*** 源码走读** @author wpw*/@Slf4jpublic class HashTableTest {public static void main(String[] args) {Hashtable<String, String> hashtable = new Hashtable<>();hashtable.put("姓名", "张三");hashtable.put("年龄", "10");hashtable.entrySet().forEach(entry -> {System.out.println(String.format("键:%s,值:%s", entry.getKey(), entry.getValue()));});System.out.println();Set<String> keySet = hashtable.keySet();keySet.stream().forEach(x -> System.out.print(String.format("键:%s", x)));System.out.println();log.info("获取HashTable的值集合数据");Collection<String> values = hashtable.values();values.forEach(x -> System.out.print(x + "\t"));System.out.println();boolean empty = hashtable.isEmpty();System.out.println("empty = " + empty);int size = hashtable.size();System.out.println("size = " + size);boolean flag = hashtable.contains("10");System.out.println("flag = " + flag);boolean containsValue = hashtable.containsValue("张三");System.out.println("containsValue = " + containsValue);log.info("清空HashTable的数据信息");hashtable.clear();log.info("获取HashTable数据集合的元素个数");int count = hashtable.size();System.out.println("count = " + count);String defaultValue = hashtable.getOrDefault("李四", "浙江");System.out.println("defaultValue = " + defaultValue);log.info("HashTable的putIfAbsent()方法的使用");//如果存在,则更新,不存在则直接新增hashtable.putIfAbsent("年龄", "张三2");hashtable.put("区域", "浙江");hashtable.entrySet().forEach(entry -> {System.out.println(String.format("键:%s,值:%s", entry.getKey(), entry.getValue()));});System.out.println();hashtable.replace("区域", "北京");hashtable.entrySet().forEach(entry -> {System.out.println(String.format("键:%s,值%s", entry.getKey(), entry.getValue()));});}}
到这里就结束了,后面慢慢去分析自己喜欢文章了。喜欢的可以分享和在看呗。
