HashMap源码分析与实现

HashMap源码分析与实现

 https://blog.csdn.net/sdksdk0/article/details/79299286

面试的时候经常会遇见诸如:“java中的HashMap是怎么工作的”,“HashMap的get和put内部的工作原理”这样的问题。本文将用一个简单的例子来解释下HashMap内部的工作原理。每当hashmap扩容的时候需要重新去add Entry对象,需要重新hash,然后放入我们新的entry table数组里面。如果在工作中,已经知道hashmap需要存多少值,几千或者几万的时候,最好新指定题目的扩容大小,防止在put的时候进行再次扩容很多次。

 

一、源码分析

 

1、初始化参数

在hashmap中我们必须要知道的参数有:初始化容量大小,默认是1 << 4,也就是16,

static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; 

加载因子0.75f,

    static final float DEFAULT_LOAD_FACTOR = 0.75f;

hash在什么时候扩容?

put的时候会去做扩容,当大于3/4的时候就会。偶数扩容  ,  2*16    2*32   2*64 

2、put方法

 
  1. public V put(K key, V value) {

  2. if (table == EMPTY_TABLE) {

  3. inflateTable(threshold);

  4. }

  5. if (key == null)

  6. return putForNullKey(value);

  7. int hash = hash(key);

  8. int i = indexFor(hash, table.length);

  9. for (Entry<K,V> e = table[i]; e != null; e = e.next) {

  10. Object k;

  11. if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {

  12. V oldValue = e.value;

  13. e.value = value;

  14. e.recordAccess(this);

  15. return oldValue;

  16. }

  17. }

  18.  
  19. modCount++;

  20. addEntry(hash, key, value, i);

  21. return null;

  22. }

 

 

对key做null检查。如果key是null,会被存储到table[0],因为null的hash值总是0。

key的hashcode()方法会被调用,然后计算hash值。hash值用来找到存储Entry对象的数组的索引。有时候hash函数可能写的很不好,所以JDK的设计者添加了另一个叫做hash()的方法,它接收刚才计算的hash值作为参数。如果你想了解更多关于hash()函数的东西,可以参考:hashmap中的hash和indexFor方法

indexFor(hash,table.length)用来计算在table数组中存储Entry对象的精确的索引。

如果两个key有相同的hash值(也叫冲突),他们会以链表的形式来存储。所以,这里我们就迭代链表。


如果在刚才计算出来的索引位置没有元素,直接把Entry对象放在那个索引上。
如果索引上有元素,然后会进行迭代,一直到Entry->next是null。当前的Entry对象变成链表的下一个节点。
如果我们再次放入同样的key会怎样呢?逻辑上,它应该替换老的value。事实上,它确实是这么做的。在迭代的过程中,会调用equals()方法来检查key的相等性(key.equals(k)),如果这个方法返回true,它就会用当前Entry的value来替换之前的value。

对于put的值,我们可以来看一个例子:

 

public class demo {

    

    public static void main(String[] args) {

         HashMap hashMap=new HashMap<String, String>();

         Object put1 = hashMap.put("aa", "30");

         Object put2 = hashMap.put("aa", "31");

         System.out.println(put1+":"+put2);

    }

}

 

当put两个相同的key的时候,这个put返回的值是oldValue,也就是说我这里打印出来的 结果就是  null:30

 

put返回的值是oldValue。key一样value会覆盖

如果hash key重复,value是不会覆盖的(经过hash算法返回的值如果重复了)。

 

对key进行hash处理的时候其实也是进行的位移处理,我们来看到hash的源码。

 
  1. final int hash(Object k) {

  2. int h = hashSeed;

  3. if (0 != h && k instanceof String) {

  4. return sun.misc.Hashing.stringHash32((String) k);

  5. }

  6.  
  7. h ^= k.hashCode();

  8.  
  9. // This function ensures that hashCodes that differ only by

  10. // constant multiples at each bit position have a bounded

  11. // number of collisions (approximately 8 at default load factor).

  12. h ^= (h >>> 20) ^ (h >>> 12);

  13. return h ^ (h >>> 7) ^ (h >>> 4);

  14. }

3、get方法

当你传递一个key从hashmap总获取value的时候:对key进行null检查。如果key是null,table[0]这个位置的元素将被返回。
key的hashcode()方法被调用,然后计算hash值。
indexFor(hash,table.length)用来计算要获取的Entry对象在table数组中的精确的位置,使用刚才计算的hash值。
在获取了table数组的索引之后,会迭代链表,调用equals()方法检查key的相等性,如果equals()方法返回true,get方法返回Entry对象的value,否则,返回null。

 
  1. public V get(Object key) {

  2. if (key == null)

  3. return getForNullKey();

  4. Entry<K,V> entry = getEntry(key);

  5. return null == entry ? null : entry.getValue();

  6. }

 

 
  1. final Entry<K,V> getEntry(Object key) {

  2. if (size == 0) {

  3. return null;

  4. }

  5.  
  6. int hash = (key == null) ? 0 : hash(key);

  7. for (Entry<K,V> e = table[indexFor(hash, table.length)];

  8. e != null;

  9. e = e.next) {

  10. Object k;

  11. if (e.hash == hash &&

  12. ((k = e.key) == key || (key != null && key.equals(k))))

  13. return e;

  14. }

  15. return null;

  16. }

4、Entry

 

hashmap table  :数组+链接   的数据结构 

 

 
  1. void resize(int newCapacity) {

  2. Entry[] oldTable = table;

  3. int oldCapacity = oldTable.length;

  4. if (oldCapacity == MAXIMUM_CAPACITY) {

  5. threshold = Integer.MAX_VALUE;

  6. return;

  7. }

  8. //创建Entry对象的数组

  9. Entry[] newTable = new Entry[newCapacity];

  10. //赋值

  11. transfer(newTable, initHashSeedAsNeeded(newCapacity));

  12. table = newTable;

  13. threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);

  14. }

 

二、手写hashmap

通过前面的内容,我们已经知道了hashmap的原理了,我们现在来自己写一个hashmap。

1、map接口

 
  1. public interface Map<K,V> {

  2.  
  3. public V put(K k,V v);

  4.  
  5. public V get(K k);

  6.  
  7. public int size();

  8.  
  9. public interface Entry<K,V>{

  10. public K getKey();

  11. public V getValue();

  12. }

  13.  
  14. }

2、实现类

 
  1. public class HashMap<K,V> implements Map<K, V> {

  2.  
  3. //初始容量

  4. private static int defaultLength=16;

  5. //加载因子

  6. private static double defaultLoader=0.75;

  7. private Entry[] table=null;

  8. private int size=0;

  9.  
  10.  
  11. public HashMap() {

  12. this(defaultLength, defaultLoader);

  13. }

  14.  
  15. public HashMap(int length,double loader){

  16. defaultLength=length;

  17. defaultLoader=loader;

  18. table=new Entry[defaultLength];

  19. }

  20.  
  21.  
  22.  
  23. public HashMap(Entry[] table, int size) {

  24. super();

  25. this.table = table;

  26. this.size = size;

  27. }

  28.  
  29. public V put(K k, V v) {

  30. size++;

  31. int index=hash(k);

  32. Entry<K, V> entry=table[index];

  33. if(entry==null){

  34. table[index]=newEntry(k,v,null);

  35. }else{

  36. table[index]=newEntry(k,v,entry);

  37. }

  38. return (V)table[index].getValue();

  39. }

  40.  
  41. private Entry<K,V> newEntry(K k, V v, Entry<K, V> next) {

  42. return new Entry(k, v, next);

  43. }

  44.  
  45. public int hash(K k){

  46. int m=defaultLength;

  47. int i=k.hashCode()%m;

  48. return i>=0?i:-i;

  49. }

  50.  
  51.  
  52.  
  53.  
  54. public V get(K k) {

  55. int index=hash(k);

  56. if(table[index]==null){

  57. return null;

  58. }

  59. //在数组中查找

  60. return find(k,table[index]) ;

  61. }

  62.  
  63. public V find(K k, Entry entry) {

  64. if(k==entry.getKey() || k.equals(entry.getKey())){

  65.  
  66. if(entry.next!=null){

  67. //System.out.println("1oldValue:"+entry.next.getValue());

  68. }

  69.  
  70.  
  71. return (V) entry.getValue();

  72. }else{

  73. if(entry.next!=null){

  74. //System.out.println("2oldValue:"+entry.next.getValue());

  75. return find(k, entry.next);

  76. }

  77. }

  78. return null;

  79. }

  80.  
  81. public int size() {

  82.  
  83. return size;

  84. }

  85.  
  86. class Entry<K,V> implements Map.Entry<K, V>{

  87.  
  88. K k;

  89. V v;

  90. Entry<K,V> next;

  91.  
  92. public Entry(K k, V v, Entry<K, V> next) {

  93. super();

  94. this.k = k;

  95. this.v = v;

  96. this.next = next;

  97. }

  98.  
  99. public K getKey() {

  100.  
  101. return k;

  102. }

  103.  
  104. public V getValue() {

  105.  
  106. return v;

  107. }

  108.  
  109. public K getK() {

  110. return k;

  111. }

  112.  
  113. public void setK(K k) {

  114. this.k = k;

  115. }

  116.  
  117. public V getV() {

  118. return v;

  119. }

  120.  
  121. public void setV(V v) {

  122. this.v = v;

  123. }

  124.  
  125. public Entry<K, V> getNext() {

  126. return next;

  127. }

  128.  
  129. public void setNext(Entry<K, V> next) {

  130. this.next = next;

  131. }

  132.  
  133. }

  134.  
  135. }

3、通过一个main函数,来测试一下,我们写的这个hashmap是否正确。

 
  1. public static void main(String[] args) {

  2. Map<String, Integer> map=new HashMap<String,Integer>();

  3. map.put("sss", 30);

  4. map.put("sss", 31);

  5. System.out.println(map.get("sss"));

  6. }

注意,引入包的时候要注意,不要引入jdk框架的map和hash包,要引入我们自己写的这个包。

 

HashMap有一个叫做Entry的内部类,它用来存储key-value对。
上面的Entry对象是存储在一个叫做table的Entry数组中。
table的索引在逻辑上叫做“桶”(bucket),它存储了链表的第一个元素。
key的hashcode()方法用来找到Entry对象所在的桶。
如果两个key有相同的hash值,他们会被放在table数组的同一个桶里面。

 

---------------------------------------------------------------------------------------------------

文章出处:http://blog.csdn.net/sdksdk0/article/details/79299286
作者:朱培 ID:sdksdk0

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值