HashMap原理

HashMap是java中非常常用的一个类。理解其实现原理还是非常有必要,之前只是零星看了一些博客的解析,这次把我看源码后的一些理解记录在这里。
 
主要分成两部分,一是hashMap的数据结构  二是 hashMap的存取算法  
 
一、数据结构
 
顾名思义,HashMap是基于哈希表的。具体来讲,是由java数组实现的哈希表。在HashMap源码中,这个数组定义为
 
transient Entry[] table
 
数组元素Entry(即键值对)定义如下:
 
我们看到Entry 定义了这几个成员变量 key, value 和 next  。next的存在意味着数组中的每个元素entry是一个单链表的结构。据此分析当hash有冲突时,会把冲突的Entry放到这个链表后面。
static class Entry<K,V> implements Map.Entry<K,V> {
  final K key;
  V value;
  Entry<K,V> next;
  ...
}

 

既然是个数组,那么数组是要声明容量的,这个工作在初始化HashMap的时候完成 ,我们看默认的初始化方法

public HashMap() {
  this.loadFactor = DEFAULT_LOAD_FACTOR;
  threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);
  table = new Entry[DEFAULT_INITIAL_CAPACITY];
  init();
}

 

 

这里的容量定义为DEFAULT_INITAL_CAPACITY  ,是个常量,默认值是16。也就是说你声明一个HashMap,默认给你的是16个桶来放Key(注意是key ,不是value,所以并不意味着初始的HashMap只能容纳16组Entry),当key超过16时就得扩容了。具体扩容方法,是超过threshhold后就会触发resize()重新hash。因此初始化HashMap时根据数据情况合理的设置容量是必要得。

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);
  table = newTable;
  threshold = (int)(newCapacity * loadFactor);
}
 

 

二、存取算法

 

具体来说就是常用的put(),get()方法的源码

public V put(K key, V value) {
  if (key == null)
  return putForNullKey(value);
  int hash = hash(key.hashCode());
  int i = indexFor(hash, table.length);
  for (Entry<K,V> e = table[i]; e != null; e = e.next) {
  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++;
  addEntry(hash, key, value, i);
  return null;
}

 

 

put一个值的时候首先会根据key的hashCode()方法计算hash值,然后会根据这个hash值计算entry数组(桶)的下标(i)在具体判断是否key 命中时,是根据 e.hash==hash&& ((k = e.key) == key || key.equals(k)))   这个表达式。从而我们知道,HashMap判断当前是否已经存储了一个值是根据key的hashCode() 和equal() 来判断的。所以如果要用自定义对象来作key,需要重写这两个方法,保证相同对象的一致性。

那么如果上面执行下来没有发现相同的key,这里有两种情况,一是没有桶,也就是entry数组没有位置。二是有桶(Hash命中)但是链表中找不到,这两种情况都可以通过addEntry(hash, key, value, i)这个方法把新值put进去。

void addEntry(int hash, K key, V value, int bucketIndex) {
  Entry<K,V> e = table[bucketIndex];
  table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
  if (size++ >= threshold)
  resize(2 * table.length);
}

 

这个方法很短,但是完成了很多工作,一是new了一个entry, 二是监视当前的桶大小是否超过阈值,如果超过就进行扩容。这个地方其实容易迷糊,看起来似乎是new了一个Entry对象把老对象替换掉了。此时请记得,entry是一个链表,new的过程可能是新创建一个链表,也可能是在已有链表上添加一个节点。

put搞明白了,get就很容易理解了。

public V get(Object key) {
  if (key == null)
  return getForNullKey();
  int hash = hash(key.hashCode());
  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.equals(k)))
  return e.value;
  }
  return null;
}

 

其实完成的就是put前半段查找的过程。如果找不到,会返回null。

 

通过上面分析,HashMap是怎么存储与运作的应该知道个大概了。总体来说,我想有两个地方比较关键,一是对与容量(Capacity )的规划,二是hashCode()的实现。应用的得当应该会让HashMap效率提高不少档次。另外有一点,看源码就发现HashMap的方法都没有做synchronize,相比而言Hashtable实现原理基本类似,但是其存取方法都是synchronized, 因此hashMap非线程安全,但是相应的效率会比较高。

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值