HashMap和HashTable

it2024-03-29  50

文章目录

一:HashMap1、HashMap的简介2、resize()扩容3、哈希冲突4、HashMap的常用操作5、HashMap源码分析6、MyHashMap 二:HashTable1、HashTable的简介2、HashTable的特点(1)HashTable的继承关系(2)HashTable的存储结构(3)HashTable的遍历方式 3、HashTable源码分析4、HashTable的常用操作 三:HashMap和HashTable的区别

一:HashMap

1、HashMap的简介

HashMap 继承于AbstractMap,实现了Map、Cloneable、java.io.Serializable接口。哈希表是一个散列表(即数组和链表的结合体),其存储的内容是一个键值对(key-value)HashMap中的key-value都是存储在Entry中的,针对任意的key通过哈希算法 转换为固定的位置(数组),HashMap的底层结构是一个数组,数组中的每一项是一条链表。HashMap可以存null键和null值,不保证元素的顺序恒久不变,他的底层使用的是数组和链表,通过hashCode()方法和equals方法保证键的唯一性。HashMap 的实例有两个参数影响其性能:“初始容量” 和 “加载因子”。容量 是哈希表中桶的数量,初始容量 只是哈希表在创建时的容量。加载因子 是哈希表在其容量自动增加之前可以达到多满的一种尺度。当哈希表中的条目数超出了加载因子与当前容量的乘积时,则要对该哈希表进行 rehash 操作(即重建内部数据结构),从而哈希表将具有大约两倍的桶数。通常,默认加载因子是0.75。 节点个数大于8时,采用数组+链表+红黑树结构;节点个数小于6时,采用数组+链表

2、resize()扩容

3、哈希冲突

哈表冲突通常以下几种方式构造哈希函数1)直接定址法 key address(key) = a*key + b2)平方取中法 key 108 109 110 = 108^2 109^2 110^23)折叠法 key -》 拆分为几部分 区域号-书架号-图书编号4)除留取余法 key -》 hash表的最大长度m 取不大于m的质数 key%质数
解决冲突1)开放地址法 12 13 25 23 hash表的长度14 hash函数key%132)链地址法

4、HashMap的常用操作

public class HashMapTest { public static void main(String[] args) { HashMap<String, Integer> map = new HashMap<>(); /** * 插入方法 */ map.put("Tom", 12); map.put("Jack", 15); map.put("Jim", 20); map.put("Tom", 65); //HashMap中不允许插入重复key值,若插入重复key值,会使用新value值替换已经存在的value值 map.put(null, 100); /** * 删除方法 */ map.remove("Jack"); /** * 更改方法 */ map.replace("Jim", 200); /** * 遍历方法 */ //使用迭代器遍历:因为Map接口与List接口不属于同一集合下,因此Map接口下的集合不能使用ListIterator进行逆向遍历 System.out.println("遍历方法一:使用迭代器正向遍历key值和value值"); //因为HashMap底层数据结构采用的是数组+链表形式,key-value值存储在链式结点中, //因此需要先获取结点对象再获取迭代对象 Iterator<Map.Entry<String, Integer>> iterator = map.entrySet().iterator(); while (iterator.hasNext()) { Map.Entry<String, Integer> next = iterator.next(); System.out.println("key值:" + next.getKey() + " value值:" + next.getValue()); } System.out.println("==========================================================="); System.out.println("遍历方法二:使用foreach方法遍历key值和value值"); for (Map.Entry<String, Integer> next : map.entrySet()) { System.out.println("key值:" + next.getKey() + " value值:" + next.getValue()); } System.out.println("==========================================================="); //遍历单一值:单一遍历key值或者单一遍历value值 System.out.println("使用迭代器单一遍历key值"); Iterator<String> iterator_key = map.keySet().iterator(); while (iterator_key.hasNext()) { String key = iterator_key.next(); System.out.println("key值:" + key); } System.out.println("==========================================================="); //遍历单一值:单一遍历key值或者单一遍历value值 System.out.println("使用迭代器单一遍历value值"); Iterator<Integer> iterator_value = map.values().iterator(); while (iterator_value.hasNext()) { Integer value = iterator_value.next(); System.out.println("value值:" + value); } System.out.println("==========================================================="); //遍历单一值:单一遍历key值或者单一遍历value值 System.out.println("使用foreach方法单一遍历key值"); for (String key : map.keySet()) { System.out.println("key值:" + key); } System.out.println("==========================================================="); //遍历单一值:单一遍历key值或者单一遍历value值 System.out.println("使用foreach方法单一遍历value值"); for (Integer value : map.values()) { System.out.println("value值:" + value); } } } 运行结果

5、HashMap源码分析

主要成员变量 /**实际存储的key-value键值对的个数*/ transient int size; /**阈值,当table == {}时,该值为初始容量(初始容量默认为16);当table被填充了,也就是为table分配内存空间后, threshold一般为 capacity*loadFactory。HashMap在进行扩容时需要参考threshold,后面会详细谈到*/ int threshold; /**负载因子,代表了table的填充度有多少,默认是0.75 加载因子存在的原因,还是因为减缓哈希冲突,如果初始桶为16,等到满16个元素才扩容,某些桶里可能就有不止一个元素了。 所以加载因子默认为0.75,也就是说大小为16的HashMap,到了第13个元素,就会扩容成32。 */ final float loadFactor; /**HashMap被改变的次数,由于HashMap非线程安全,在对HashMap进行迭代时, 如果期间其他线程的参与导致HashMap的结构发生变化了(比如put,remove等操作), 需要抛出异常ConcurrentModificationException*/ transient int modCount; 构造函数 当采用无参构造时,HashMap会先将table数组赋值为空数组,待第一次添加元素时,会使用默认构造值:initialCapacity默认为16,loadFactory默认为0.75。 public HashMap(int initialCapacity, float loadFactor) { //此处对传入的初始容量进行校验,最大不能超过MAXIMUM_CAPACITY = 1<<30(230) if (initialCapacity < 0) throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity); //初始容量最大不能超过2的30次方 if (initialCapacity > MAXIMUM_CAPACITY) initialCapacity = MAXIMUM_CAPACITY; //显然加载因子不能为负数 || 判断是不是一个数字 if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException("Illegal load factor: " + loadFactor); this.loadFactor = loadFactor; threshold = initialCapacity; init(); //init方法在HashMap中没有实际实现,不过在其子类如 linkedHashMap中就会有对应实现 } 添加函数 public V put(K key, V value) { //如果table数组为空数组{},进行数组填充(为table分配实际内存空间),入参为threshold, //此时threshold为initialCapacity 默认是1<<4(24=16) if (table == EMPTY_TABLE) { inflateTable(threshold); } //如果key为null,存储位置为table[0]或table[0]的冲突链上 if (key == null) return putForNullKey(value); int hash = hash(key); //对key的hashcode进一步计算,确保散列均匀 int i = indexFor(hash, table.length); //获取在table中的实际位置 for (Entry<K,V> e = table[i]; e != null; e = e.next) { //如果该对应数据已存在,执行覆盖操作。用新value替换旧value,并返回旧value Object k; if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } modCount++; //保证并发访问时,若HashMap内部结构发生变化,快速响应失败 addEntry(hash, key, value, i); //新增一个entry return null; } inflateTable这个方法用于为主干数组table在内存中分配存储空间,通过roundUpToPowerOf2(toSize)可以确保capacity为大于或等于toSize的最接近toSize的二次幂,比如toSize=13,则capacity=16;to_size=16,capacity=16;to_size=17,capacity=32。 private void inflateTable(int toSize) { int capacity = roundUpToPowerOf2(toSize);//capacity一定是2的次幂 /**此处为threshold赋值,取capacity*loadFactor和MAXIMUM_CAPACITY+1的最小值, capaticy一定不会超过MAXIMUM_CAPACITY,除非loadFactor大于1 */ threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1); table = new Entry[capacity]; initHashSeedAsNeeded(capacity); } roundUpToPowerOf2中的这段处理使得数组长度一定为2的次幂,Integer.highestOneBit是用来获取最左边的bit(其他bit位为0)所代表的数值 private static int roundUpToPowerOf2(int number) { // assert number >= 0 : "number must be non-negative"; return number >= MAXIMUM_CAPACITY ? MAXIMUM_CAPACITY : (number > 1) ? Integer.highestOneBit((number - 1) << 1) : 1; } Hash函数 final int hash(Object k) { int h = hashSeed; if (0 != h && k instanceof String) { return sun.misc.Hashing.stringHash32((String) k); } h ^= k.hashCode(); h ^= (h >>> 20) ^ (h >>> 12); return h ^ (h >>> 7) ^ (h >>> 4); } 以上hash函数计算出的值,通过indexFor进一步处理来获取实际的存储位置。 /** * 返回数组下标 */ static int indexFor(int h, int length) { return h & (length-1); } addEntry void addEntry(int hash, K key, V value, int bucketIndex) { if ((size >= threshold) && (null != table[bucketIndex])) { resize(2 * table.length); //当size超过临界阈值threshold,并且即将发生哈希冲突时进行扩容 hash = (null != key) ? hash(key) : 0; bucketIndex = indexFor(hash, table.length); } createEntry(hash, key, value, bucketIndex); } resize void resize(int newCapacity) { Entry[] oldTable = table; int oldCapacity = oldTable.length; if (oldCapacity == MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return; } Entry[] newTable = new Entry[newCapacity]; transfer(newTable, initHashSeedAsNeeded(newCapacity)); table = newTable; threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1); }

而至于HashMap为何使用2倍扩容: 我们可以看到上面的indexFor()方法是将通过key值获取到的hash值转化为可以存储的下标值index,而其所使用的方法为return h & (length-1);其实针对转化hash值为index值的方法还有一种十分简单的:return h % length;这两种方法在处理的数值length为2的幂次方时所得到的结果是相同的,但是按位运算的效率要大于普通的算数运算,这也是为什么HashMap采用第一种方法的原因。因此我们也同样可以看出为什么HashMap的初始容量为16;以及为什么HashMap的扩容方法为2倍扩容。目的就是为了保证数组大小始终为2的幂次方,从而保证使用位运算计算index值时的正确性。当我们传入指定参数进行构造table的大小时,HashMap会调用private static int roundUpToPowerOf2(int number)方法来确保参数大小始终为2的幂次方。

transfer void transfer(Entry[] newTable, boolean rehash) { int newCapacity = newTable.length;      //for循环中的代码,逐个遍历链表,重新计算索引位置,将老数组数据复制到新数组中去(数组不存储实际数据,所以仅仅是拷贝引用而已) for (Entry<K,V> e : table) { while(null != e) { Entry<K,V> next = e.next; if (rehash) { e.hash = null == e.key ? 0 : hash(e.key); } int i = indexFor(e.hash, newCapacity); //将当前entry的next链指向新的索引位置,newTable[i]有可能为空,有可能也是个entry链,如果是entry链,直接在链表头部插入。 e.next = newTable[i]; newTable[i] = e; e = next; } } } get方法 public V get(Object key) {      //如果key为null,则直接去table[0]处去检索即可。 if (key == null) return getForNullKey(); Entry<K,V> entry = getEntry(key); return null == entry ? null : entry.getValue(); } getEntry final Entry<K,V> getEntry(Object key) { if (size == 0) { return null; } //通过key的hashcode值计算hash值 int hash = (key == null) ? 0 : hash(key); //indexFor (hash&length-1) 获取最终数组索引,然后遍历链表,通过equals方法比对找出对应记录 for (Entry<K,V> e = table[indexFor(hash, table.length)]; e != null; e = e.next) { Object k; if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) return e; } return null; } 删除 /** * Removes the mapping for the specified key from this map if present. * * @param key key whose mapping is to be removed from the map * @return the previous value associated with <tt>key</tt>, or * <tt>null</tt> if there was no mapping for <tt>key</tt>. * (A <tt>null</tt> return can also indicate that the map * previously associated <tt>null</tt> with <tt>key</tt>.) */ public V remove(Object key) { Entry<K,V> e = removeEntryForKey(key); return (e == null ? null : e.value); } /** * Removes and returns the entry associated with the specified key * in the HashMap. Returns null if the HashMap contains no mapping * for this key. */ final Entry<K,V> removeEntryForKey(Object key) { if (size == 0) { return null; } int hash = (key == null) ? 0 : hash(key); int i = indexFor(hash, table.length); Entry<K,V> prev = table[i]; Entry<K,V> e = prev; while (e != null) { Entry<K,V> next = e.next; Object k; if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) { modCount++; size--; if (prev == e) //删除的是头一个节点 table[i] = next; else prev.next = next; e.recordRemoval(this); return e; } prev = e; e = next; } return e; }

6、MyHashMap

class MyHashMap<K,V>{ private Node<K, V>[] table; private int size; class Node<K,V>{ protected K key; protected V value; protected int hash; protected Node<K, V> next; public Node(K key, V value, int hash) { this.key = key; this.value = value; this.hash = hash; } } public MyHashMap(int capacity){ table = new Node[capacity]; } public Iterator<Node<K, V>> iterator(){ return new Itr(); } class Itr implements Iterator<Node<K, V>>{ private int currentIndex;//当前桶的位置 private Node<K, V> nextNode;//返回的下一个数据节点 private Node<K, V> currentNode; //上一次next返回的数据节点 public Itr(){ if(MyHashMap.this.isEmpty()){ return; } for(int i=0; i < table.length; i++){ currentIndex = i; Node<K, V> firstNode = table[i]; if(firstNode != null){ nextNode = firstNode; return; } } } @Override public boolean hasNext() { return nextNode != null; } @Override public Node<K, V> next() { //返回下一个数据节点 currentNode = nextNode; //nextNode指向自己的next nextNode = nextNode.next; if(nextNode == null){ //说明当前桶的链表已经遍历完毕 //寻找下一个非空的桶 for(int i=currentIndex+1; i<MyHashMap.this.table.length; i++){ //设置当前桶位置 currentIndex = i; Node<K,V> firstNode = MyHashMap.this.table[currentIndex]; if(firstNode != null){ nextNode = firstNode; break; } } //nextNode保存的就是下一个非空的数据节点 } return currentNode; } @Override public void remove() { if(currentNode == null){ return; } K currentKey = currentNode.key; MyHashMap.this.remove(currentKey); currentNode = null; } } public void resize(){ //扩容桶table Node<K, V>[] newTable = new Node[table.length * 2]; for(int i=0; i<table.length; i++){ //将oldTable中每一个位置映射到newTable中 rehash(i, newTable); } this.table = newTable; } public void rehash(int index, Node<K,V>[] newTable){ Node<K, V> currentNode = table[index]; if(currentNode == null){ return; } Node<K, V> lowListHead = null; //低位的头 Node<K, V> lowListTail = null; //低位的尾 Node<K, V> highListHead = null; //高位的头 Node<K, V> highListTail = null; //高位的尾 //currentNode表示oleTable下index位置中当前节点 while(currentNode != null){ //当前节点在newTable中的位置 int newIndex = newTable.length-1 & hash(currentNode.key); if(newIndex == index){ //映射到原先下标处 if(lowListHead == null){ lowListHead = currentNode; lowListTail = currentNode; }else{ lowListTail.next = currentNode; lowListTail = lowListTail.next; } }else{ //newIndex与index不等,映射到高位下标处 if(highListHead == null){ highListHead = currentNode; highListTail = currentNode; }else{ highListTail.next = currentNode; highListTail = lowListTail.next; } } currentNode = currentNode.next; } //将lowList head->tail之前的节点链接到index位置 if(lowListTail != null){ newTable[index] = lowListHead; lowListHead.next = null; } //将highList head->tail之前的节点链接到index+table.length位置 if(highListTail != null){ newTable[index+this.table.length] = highListHead; highListHead.next = null; } } public int hash(Object key) { int h; return (h = key.hashCode()) ^ (h >>> 16); } public void put(K key, V value){ //确定所要添加元素的位置 int hash = hash(key);//散列码 int index = table.length-1 & hash;//确定的位置 //newNode -》table[index] == null || key在map不存在 //table[index]已经存在数据 //table[index]不存在数据 Node<K, V> firstNode = table[index];//得到该位置的第一个节点 if(firstNode == null){ table[index] = new Node(key, value, hash); size++; }else{ //第一种情况 key已经存在 考虑新值覆盖旧值 //第二种情况 key不存在 封装一个新的node尾插法插入链表 if(firstNode.key.equals(key)){ firstNode.value = value; //新值替换旧值 }else{ Node<K,V> tmp = firstNode; while(tmp.next != null && !tmp.key.equals(key)){ tmp = tmp.next; } if(tmp.next == null){ if(tmp.key.equals(key)){ tmp.value = value; //新值替换旧值 }else{ tmp.next = new Node(key, value, hash); size++; } }else{ tmp.value = value; } } } } public boolean remove(K key){ int hash = hash(key); int index = table.length-1 & hash; Node<K, V> firstNode = table[index]; if(firstNode == null){ return false; }else{ if(firstNode.next == null){ if(firstNode.key.equals(key) && firstNode.hash == hash){ //为什么判断key是否相等的同时还需要判断散列码 table[index] = null; return true; } } Node<K,V> tmpPrev = firstNode; Node<K, V> tmp = firstNode.next; while(tmp.next != null){ if(tmp.key.equals(key) && tmp.hash == hash){ //tmp之前节点的next指向tmp.next tmpPrev.next = tmp.next; size--; }else{ tmpPrev = tmp; tmp = tmp.next; } } } return false; } public Node<K,V> get(K key){ //获取对应key所在数组的index int hash = hash(key);//散列码 int index = table.length - 1 & hash; //定位key记录所在的位置 Node<K, V> firstNode = table[index]; if (firstNode == null) { return null; } if (hash == firstNode.hash && key == firstNode.key) { return firstNode; } else { //遍历链表 Node<K, V> currentNode = firstNode.next; while (currentNode != null) { if (currentNode.hash == hash && currentNode.key == key) { return currentNode; } currentNode = currentNode.next; } } return null; } public boolean isEmpty(){ return size == 0; } }

二:HashTable

1、HashTable的简介

HashTable同样是基于哈希表实现的,其中的每个元素是一个key-value对,内部也是通过单链表解决冲突问题,容量不足(超过了阀值)时,同样会自动增长。它在很大程度上和HashMap的实现差不多,不过相对的功能较少,目前已经很少使用了,下面来看看HashTable的具体内容。

2、HashTable的特点

(1)HashTable的继承关系

public class Hashtable<K,V> extends Dictionary<K,V> implements Map<K,V>, Cloneable, java.io.Serializable { }

从中可以看出HashTable继承Dictionary类,实现Map接口。其中Dictionary类是任何可将键映射到相应值的类(如 Hashtable)的抽象父类。每个键和每个值都是一个对象。在任何一个 Dictionary 对象中,每个键至多与一个值相关联。

实现了Map接口,是"key-value键值对"接口。即接口中的每个元素都是一对, 以key-value的形式保存,并且Key是不重复的,元素的存储位置由key决定。也就是可以通过key去寻找key-value的位置,从而得到value的值。适合做查找工作。实现了Cloneable接口,能被克隆。Cloneable是标记型的接口,它们内部都没有方法和属性,实现 Cloneable来表示该对象能被克隆,能使用Object.clone()方法。如果没有实现 Cloneable的类对象调用clone()就会抛出CloneNotSupportedException。实现了Serializable接口,它支持序列化。public interface Serializable类通过实现 java.io.Serializable 接口以启用其序列化功能。

(2)HashTable的存储结构

/** * The hash table data. */ private transient Entry<?,?>[] table; /** * Hashtable bucket collision list entry */ 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; } }

从上面的结构可以看出。HashTable的存储结构与HashMap的存储结构相同

(3)HashTable的遍历方式

private class Enumerator<T> implements Enumeration<T>, Iterator<T> { }

HashTable提供了两种遍历方式:

Enumeration<T>:利用枚举的方式从前向后遍历集合,不支持快速失败机制。Enumeration迭代器只能遍历Vector、Hashtable这种古老的集合,因此通常不要使用它,除非在某些极端情况下,不得不使用Enumeration,否则都应该选择Iterator迭代器。Iterator<T>:迭代器遍历方式。支持快速失败机制,目前使用较多的遍历方式。

3、HashTable源码分析

成员变量

{ private transient Entry<?,?>[] table; private transient int count; private int threshold; private float loadFactor; private transient int modCount = 0; } table:为一个Entry[]数组类型,Entry代表了“拉链”的节点,每一个Entry代表了一个键值对,哈希表的"key-value键值对"都是存储在Entry数组中的。count:HashTable的大小,注意这个大小并不是HashTable的容器大小,而是他所包含Entry键值对的数量。threshold:Hashtable的阈值,用于判断是否需要调整Hashtable的容量。threshold的值=“容量”。loadFactor:加载因子。modCount:用来实现“fail-fast”机制的(也就是快速失败)。所谓快速失败就是在并发集合中,其进行迭代操作时,若有其他线程对其进行结构性的修改,这时迭代器会立马感知到,并且立即抛出ConcurrentModificationException异常,而不是等到迭代完成之后才告诉你。  

构造方法

默认构造函数,容量为11,加载因子为0.75。 /** * Constructs a new, empty hashtable with a default initial capacity (11) * and load factor (0.75). */ public Hashtable() { this(11, 0.75f); } 用指定初始容量和默认的加载因子 (0.75) 构造一个新的空哈希表。 /** * Constructs a new, empty hashtable with the specified initial capacity * and default load factor (0.75). * * @param initialCapacity the initial capacity of the hashtable. * @exception IllegalArgumentException if the initial capacity is less * than zero. */ public Hashtable(int initialCapacity) { this(initialCapacity, 0.75f); } 用指定初始容量和指定加载因子构造一个新的空哈希表。其中initHashSeedAsNeeded方法用于初始化hashSeed参数,其中hashSeed用于计算key的hash值,它与key的hashCode进行按位异或运算。这个hashSeed是一个与实例相关的随机值,主要用于解决hash冲突。 /** * Constructs a new, empty hashtable with the specified initial * capacity and the specified load factor. * * @param initialCapacity the initial capacity of the hashtable. * @param loadFactor the load factor of the hashtable. * @exception IllegalArgumentException if the initial capacity is less * than zero, or if the load factor is nonpositive. */ public Hashtable(int initialCapacity, float loadFactor) { if (initialCapacity < 0) throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException("Illegal Load: "+loadFactor); if (initialCapacity==0) initialCapacity = 1; this.loadFactor = loadFactor; table = new Entry<?,?>[initialCapacity]; threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1); } 构造一个与给定的 Map 具有相同映射关系的新哈希表。 /** * Constructs a new hashtable with the same mappings as the given * Map. The hashtable is created with an initial capacity sufficient to * hold the mappings in the given Map and a default load factor (0.75). * * @param t the map whose mappings are to be placed in this map. * @throws NullPointerException if the specified map is null. * @since 1.2 */ public Hashtable(Map<? extends K, ? extends V> t) { this(Math.max(2*t.size(), 11), 0.75f); putAll(t); }

4、HashTable的常用操作

put方法 (注意这里键key和值value都不可为空) /** * Maps the specified <code>key</code> to the specified * <code>value</code> in this hashtable. Neither the key nor the * value can be <code>null</code>. <p> * * The value can be retrieved by calling the <code>get</code> method * with a key that is equal to the original key. * * @param key the hashtable key * @param value the value * @return the previous value of the specified key in this hashtable, * or <code>null</code> if it did not have one * @exception NullPointerException if the key or value is * <code>null</code> * @see Object#equals(Object) * @see #get(Object) */ public synchronized V put(K key, V value) { // Make sure the value is not null if (value == null) { throw new NullPointerException(); } // Makes sure the key is not already in the hashtable. Entry<?,?> tab[] = table; int hash = key.hashCode(); int index = (hash & 0x7FFFFFFF) % tab.length; @SuppressWarnings("unchecked") Entry<K,V> entry = (Entry<K,V>)tab[index]; for(; entry != null ; entry = entry.next) { if ((entry.hash == hash) && entry.key.equals(key)) { V old = entry.value; entry.value = value; return old; } } addEntry(hash, key, value, index); return null; } get方法 /** * Returns the value to which the specified key is mapped, * or {@code null} if this map contains no mapping for the key. * * <p>More formally, if this map contains a mapping from a key * {@code k} to a value {@code v} such that {@code (key.equals(k))}, * then this method returns {@code v}; otherwise it returns * {@code null}. (There can be at most one such mapping.) * * @param key the key whose associated value is to be returned * @return the value to which the specified key is mapped, or * {@code null} if this map contains no mapping for the key * @throws NullPointerException if the specified key is null * @see #put(Object, Object) */ @SuppressWarnings("unchecked") public synchronized V get(Object key) { Entry<?,?> tab[] = table; int hash = key.hashCode(); int index = (hash & 0x7FFFFFFF) % tab.length; for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) { if ((e.hash == hash) && e.key.equals(key)) { return (V)e.value; } } return null; } remove方法 /** * Removes the key (and its corresponding value) from this * hashtable. This method does nothing if the key is not in the hashtable. * * @param key the key that needs to be removed * @return the value to which the key had been mapped in this hashtable, * or <code>null</code> if the key did not have a mapping * @throws NullPointerException if the key is <code>null</code> */ public synchronized V remove(Object key) { 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(Entry<K,V> prev = null ; e != null ; prev = e, e = e.next) { if ((e.hash == hash) && e.key.equals(key)) { modCount++; if (prev != null) { prev.next = e.next; } else { tab[index] = e.next; } count--; V oldValue = e.value; e.value = null; return oldValue; } } return null; }

三:HashMap和HashTable的区别

HashTableHashMap继承的父类不同DictionaryAbstractMap都实现了同时实现了map、Cloneable(可复制)、Serializable(可序列化)这三个接口都实现了同时实现了map、Cloneable(可复制)、Serializable(可序列化)这三个接口默认容量1116Table的初始化时机构造函数中初始化第一次put并发操作使用同步机制,实际应用程序中,仅仅是Hashtable本身的同步并不能保证程序在并发操作下的正确性,需要高层次的并发保护。下面的代码试图在key所对应的value值等于x的情况下修改value为x+1 { value = hashTable.get(key); if(value.intValue()== x){ hashTable.put(key, new Integer(value.intValue()+1)); } } 如2个线程同时执行以上代码,可能放入不是x+1,而是x+2.没有同步机制,需要使用者自己进行并发访问控制数据遍历的方式Iterator 和 EnumerationIterator是否支持fast-fail用Iterator遍历,支持fast-fail 用Enumeration不支持fast-fail支持fast-fail是否接受值为null的Key 或Value不接受接受根据hash值计算数组下标的算法没有扰动处理,哈希冲突重复率高,使用(hash & 0x7FFFFFFF) % tab.length;确保index为正数并且不会越界有扰动处理,哈希冲突重复率低,使用h & (length-1);确保index不会越界Entry数组的长度1116(并且始终为2的幂次方)LoadFactor负荷因子0.750.75负荷超过阈值时,内部数据的调整方式将容量变为原来的2倍加1将容量变为原来的2倍
最新回复(0)