文章目录
一: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 + b
- 2)平方取中法 key 108 109 110 = 108^2 109^2 110^2
- 3)折叠法 key -》 拆分为几部分 区域号-书架号-图书编号
- 4)除留取余法 key -》 hash表的最大长度m 取不大于m的质数 key%质数
- 解决冲突
- 1)开放地址法 12 13 25 23 hash表的长度14 hash函数key%13
- 2)链地址法
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的区别
HashTable | HashMap | |
---|---|---|
继承的父类不同 | Dictionary | AbstractMap |
都实现了同时实现了map、Cloneable(可复制)、Serializable(可序列化)这三个接口 | 都实现了同时实现了map、Cloneable(可复制)、Serializable(可序列化)这三个接口 | |
默认容量 | 11 | 16 |
Table的初始化时机 | 构造函数中初始化 | 第一次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 和 Enumeration | Iterator |
是否支持fast-fail | 用Iterator 遍历,支持fast-fail用 Enumeration 不支持fast-fail | 支持fast-fail |
是否接受值为null的Key 或Value | 不接受 | 接受 |
根据hash值计算数组下标的算法 | 没有扰动处理,哈希冲突重复率高,使用(hash & 0x7FFFFFFF) % tab.length ;确保index为正数并且不会越界 | 有扰动处理,哈希冲突重复率低,使用h & (length-1) ;确保index不会越界 |
Entry数组的长度 | 11 | 16(并且始终为2的幂次方) |
LoadFactor负荷因子 | 0.75 | 0.75 |
负荷超过阈值时,内部数据的调整方式 | 将容量变为原来的2倍加1 | 将容量变为原来的2倍 |