当前位置: 移动技术网 > IT编程>开发语言>Java > java中HashMap的原理分析

java中HashMap的原理分析

2019年07月22日  | 移动技术网IT编程  | 我要评论

我们先来看这样的一道面试题:

在 hashmap 中存放的一系列键值对,其中键为某个我们自定义的类型。放入 hashmap 后,我们在外部把某一个 key 的属性进行更改,然后我们再用这个 key 从 hashmap 里取出元素,这时候 hashmap 会返回什么?

文中已给出示例代码与答案,但关于hashmap的原理没有做出解释。

1. 特性

我们可以用任何类作为hashmap的key,但是对于这些类应该有什么限制条件呢?且看下面的代码:

public class person {
  private string name;

  public person(string name) {
    this.name = name;
  }
}

map<person, string> testmap = new hashmap<>();
testmap.put(new person("hello"), "world");
testmap.get(new person("hello")); // ---> null

本是想取出具有相等字段值person类的value,结果却是null。对hashmap稍有了解的人看出来——person类并没有override hashcode方法,导致其继承的是object的hashcode(返回是其内存地址)。这也是为什么常用不变类如string(或integer等)做为hashmap的key的原因。那么,hashmap是如何利用hashcode给key做快速索引的呢?

2. 原理

首先,我们来看《thinking in java》中一个简单hashmap的实现方案:

//: containers/simplehashmap.java
// a demonstration hashed map.
import java.util.*;
import net.mindview.util.*;

public class simplehashmap<k,v> extends abstractmap<k,v> {
 // choose a prime number for the hash table size, to achieve a uniform distribution:
 static final int size = 997;
 // you can't have a physical array of generics, but you can upcast to one:
 @suppresswarnings("unchecked")
 linkedlist<mapentry<k,v>>[] buckets =
  new linkedlist[size];
 public v put(k key, v value) {
  v oldvalue = null;
  int index = math.abs(key.hashcode()) % size;
  if(buckets[index] == null)
   buckets[index] = new linkedlist<mapentry<k,v>>();
  linkedlist<mapentry<k,v>> bucket = buckets[index];
  mapentry<k,v> pair = new mapentry<k,v>(key, value);
  boolean found = false;
  listiterator<mapentry<k,v>> it = bucket.listiterator();
  while(it.hasnext()) {
   mapentry<k,v> ipair = it.next();
   if(ipair.getkey().equals(key)) {
    oldvalue = ipair.getvalue();
    it.set(pair); // replace old with new
    found = true;
    break;
   }
  }
  if(!found)
   buckets[index].add(pair);
  return oldvalue;
 }
 public v get(object key) {
  int index = math.abs(key.hashcode()) % size;
  if(buckets[index] == null) return null;
  for(mapentry<k,v> ipair : buckets[index])
   if(ipair.getkey().equals(key))
    return ipair.getvalue();
  return null;
 }
 public set<map.entry<k,v>> entryset() {
  set<map.entry<k,v>> set= new hashset<map.entry<k,v>>();
  for(linkedlist<mapentry<k,v>> bucket : buckets) {
   if(bucket == null) continue;
   for(mapentry<k,v> mpair : bucket)
    set.add(mpair);
  }
  return set;
 }
 public static void main(string[] args) {
  simplehashmap<string,string> m =
   new simplehashmap<string,string>();
  m.putall(countries.capitals(25));
  system.out.println(m);
  system.out.println(m.get("eritrea"));
  system.out.println(m.entryset());
 }
}

simplehashmap构造一个hash表来存储key,hash函数是取模运算math.abs(key.hashcode()) % size,采用链表法解决hash冲突;buckets的每一个槽位对应存放具有相同(hash后)index值的map.entry,如下图所示:

jdk的hashmap的实现原理与之相类似,其采用链地址的hash表table存储map.entry:

/**
 * the table, resized as necessary. length must always be a power of two.
 */
transient entry<k,v>[] table = (entry<k,v>[]) empty_table;

static class entry<k,v> implements map.entry<k,v> {
  final k key;
  v value;
  entry<k,v> next;
  int hash;
  …
}

map.entry的index是对key的hashcode进行hash后所得。当要get key对应的value时,则对key计算其index,然后在table中取出map.entry即可得到,具体参看代码:

public v get(object key) {
  if (key == null)
    return getfornullkey();
  entry<k,v> entry = getentry(key);

  return null == entry ? null : entry.getvalue();
}

final entry<k,v> getentry(object key) {
  if (size == 0) {
    return null;
  }

  int hash = (key == null) ? 0 : hash(key);
  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;
}

可见,hashcode直接影响hashmap的hash函数的效率——好的hashcode会极大减少hash冲突,提高查询性能。同时,这也解释开篇提出的两个问题:如果自定义的类做hashmap的key,则hashcode的计算应涵盖构造函数的所有字段,否则有可能得到null。

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网