说起Java的HashMap相信大家都不是很陌生,但是对于HashMap内部结构有些同学可能不太了解,咱们下一步就将其展开。

HashMap是基于Hash算法的,同理的还有HashSet和HashTable。我的一篇博文讲述了如果将Object作为Key的话,那么就需要注意重写其hashCode()和equals()方法,当然,这个equals()不是必要重写的,但是在Effective Java中这条是一条准则。那么针对这块有个问题,如果只写了hashCode方法不写equals或者反之呢? 咱们一会儿慢慢展开,相信同学们读完了会有所领悟。

HashMap有几个重要的概念 hashcode, entry, bucket,说道hashCode,其实它是一个Object对象的一个方法,也就说每个方法都有一个,它的默认值是对象地址然后转换为int并返回。Entry是一个Map的静态内部类,用于存储Key, value,以及指向next的一个链表(linked list)。

  1. static class Entry<K,V> implements Map.Entry<K,V> {
  2. final K key;
  3. V value;
  4. Entry<K,V> next;
  5. int hash;

  Bucket是什么呢,其实就是根据Hash值分布的一个篮子,请参考如下图表。

下一步,咱们直接进入HashMap源码(java 7), 从它的最原始的空构造函数入手,初始化时长度为16,0.75是负载因子。HashMap在初始化的时候没有初始化这个Entry, 它是在put的时候进行的,看inflateTable(threadhold);填充表,在这个时候这个负载因子就用到了16*0.75=12,也就是bucket默认的长度为12个,也就是hash的值只能存放12个,如果超过了12个的话,那就需要进行扩展,扩大两倍比如32,然后再根据这个负载因子去计算。

那么为什么用一个负载因子呢? 因为考虑到时间和空间之间的权衡才使用这个功能,如果这个负载因子小了,也就意味着分配的bucket比较少,这样查询和添加的时候速度比较快,但是增加bucket的速度变慢了。如果这个负载因子变大了,那么每次分配bucket的数量也随之增大了,这样会占据很大的内存空间。这个load factor参数决定了HashMap的性能。那是否还有其他参数决定HashMap的性能呢,那就是初始化的的capacity(容量),这个也可以初始化的时候设置,设置太大了在遍历的时候也会消耗很多资源。

  1. /**
  2. * Constructs an empty <tt>HashMap</tt> with the default initial capacity
  3. * (16) and the default load factor (0.75).
  4. */
  5. public HashMap() {
  6. this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
  7. }

    然后继续看这个put,首先判断key是否为null,如果为null的话,就会分配一个hashcode为0的Entry用于存储。也就是HashMap支持null值并且只允许一个

  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. }
  23.  
  24. /**
  25. * Inflates the table.
  26. */
  27. private void inflateTable(int toSize) {
  28. // Find a power of 2 >= toSize
  29. int capacity = roundUpToPowerOf2(toSize);
  30.  
  31. threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
  32. table = new Entry[capacity];
  33. initHashSeedAsNeeded(capacity);
  34. }
  1. * Retrieve object hash code and applies a supplemental hash function to the
  2. * result hash, which defends against poor quality hash functions. This is
  3. * critical because HashMap uses power-of-two length hash tables, that
  4. * otherwise encounter collisions for hashCodes that do not differ
  5. * in lower bits. Note: Null keys always map to hash 0, thus index 0.
  6. */
  7. final int hash(Object k) {
  8. int h = hashSeed;
  9. if (0 != h && k instanceof String) {
  10. return sun.misc.Hashing.stringHash32((String) k);
  11. }
  12.  
  13. h ^= k.hashCode();
  14.  
  15. // This function ensures that hashCodes that differ only by
  16. // constant multiples at each bit position have a bounded
  17. // number of collisions (approximately 8 at default load factor).
  18. h ^= (h >>> 20) ^ (h >>> 12);
  19. return h ^ (h >>> 7) ^ (h >>> 4);
  20. }

  然后我们在继续看它的hash方法,它把key的hashCode取得了,然后又加工了一次,这是为什么呢?

  因为为了防止差的hashcode功能,也就是更好的提高了hashcode的计算。然后根据hashcode去找到bucket所在的index,如果index相同的话怎么办呢,然后再去取出它的key,将key和Entry里边的Key再次进行比较,如果相等equals()的话,那么就可以取出结果了,如果不相等的话,继续Entry.next()进行判断,最后如果没有找到的话,就会返回null

  这里大家一定要注意Entry里边存放了key, value, next, 这时这个key取出来就用于判断了。这下同学们明白了如果hashcode相同了怎么处理了吧。如果如果一个key在设计时只重写了equals而没有写hashcode(),那么会出现什么现象呢? 答案是两个对象会被当做两个不同的对象放到了不同的bucket里边,这其实违背了一个很重要的原则:The part of the contract here which is important is: objects which are .equals() MUST have the same .hashCode(). 对象相等即它的hashCode相等。如果是在HashSet的里面,会被当做两个相同的对象,但这时错误的比如:Person a = new Person("zhangsang",13); Person b = new Person("zhangsan", 13); 如果只重写equals,那么他们是equal的,但是放到set里边会被认为两个对象。

还有一点就是put和get的时间复杂度为O(1),这就是它的hash的原理了。下面是我自己写的代码,供大家参考。

  1. package com.hqs.core;
  2.  
  3. import java.util.HashMap;
  4. import java.util.Iterator;
  5. import java.util.Map;
  6.  
  7. public class Person {
  8. private final String name;
  9. private int age;
  10.  
  11. public Person(String name, int age) {
  12. this.name = name;
  13. this.age = age;
  14. }
  15.  
  16. @Override
  17. public String toString() {
  18. return "name: " + this.name + " age: " + this.age + " ";
  19. }
  20.  
  21. @Override
  22. public int hashCode() {
  23. return this.age;
  24. }
  25.  
  26. @Override
  27. public boolean equals(Object obj) {
  28. boolean flag = false;
  29. if(obj == null)
  30. flag = false;
  31. if(obj == this)
  32. flag = true;
  33. if(getClass() != obj.getClass())
  34. flag = true;
  35. Person p = (Person) obj;
  36. if(p.name != null && this.name !=null) {
  37. if(p.name.equals(this.name) && p.age == this.age)
  38. flag = true;
  39. } else {
  40. flag = false;
  41. }
  42. return flag;
  43. }
  44.  
  45. public static void main(String[] args) {
  46. Person zhangsan = new Person("zhangsan", 13);
  47. Person lisi = new Person("lisi", 13);
  48. Person xiaoming = new Person("xiaoming", 12);
  49.  
  50. System.out.println("zhangsan hashCode: " + zhangsan.hashCode());
  51. System.out.println("lisi hashCode: " + lisi.hashCode());
  52. System.out.println("xiaoming hashcode: " + xiaoming.hashCode());
  53.  
  54. Map map = new HashMap();
  55. map.put(zhangsan, "first");
  56. map.put(lisi, "second");
  57. map.put(xiaoming, "third");
  58.  
  59. Iterator<Person> it = map.entrySet().iterator();
  60. while(it.hasNext()) {
  61. Map.Entry entry = (Map.Entry<Person, String>)it.next();
  62. Person person= (Person)entry.getKey();
  63. String value = (String)entry.getValue();
  64. System.out.println(person + value);
  65. }
  66. }
  67.  
  68. }
  69.  
  70. zhangsan hashCode: 13
  71. lisi hashCode: 13
  72. xiaoming hashcode: 12
  73. name: xiaoming age: 12 third
  74. name: lisi age: 13 second
  75. name: zhangsan age: 13 first

  如果有写的不对的地方,还希望同学们给一些意见:68344150@qq.com

Core Java 谈谈HashMap的更多相关文章

  1. Core Java 谈谈 ThreadPoolExecutor

    说起Java 7的Executors框架的线程池,同学们能想到有几种线程池,它们分别是什么? 一共有四个,它们分别是Executors的 newSingleThreadPool(), newCache ...

  2. Core Java Interview Question Answer

    This is a new series of sharing core Java interview question and answer on Finance domain and mostly ...

  3. org.apache.ibatis.builder.IncompleteElementException: Could not find result map java.util.HashMap

    这样的配置有问题吗? <select id="getFreightCollectManagementList" resultMap="java.util.HashM ...

  4. spring boot 之 错误:SpelEvaluationException: EL1008E: Property or field 'timestamp' cannot be found on object of type 'java.util.HashMap'

    这个错误我也见过很多次了,今天终于理解了其出现的原因. 错误是这样的: 2017-11-23 18:05:39.504 ERROR 4092 --- [nio-8080-exec-3] o.a.c.c ...

  5. Top 25 Most Frequently Asked Interview Core Java Interview Questions And Answers

    We are sharing 25 java interview questions , these questions are frequently asked by the recruiters. ...

  6. Difference Between Arraylist And Vector : Core Java Interview Collection Question

    Difference between Vector and Arraylist is the most common  Core Java Interview question you will co ...

  7. java.lang.ClassCastException: java.util.HashMap cannot be cast to java.lang.String

    问题背景:从前端传来的json字符串中取某些值,拼接成json格式入参调外部接口. 报如下错: java.lang.ClassCastException: java.util.HashMap cann ...

  8. EL1008E: Property or field 'timestamp' cannot be found on object of type 'java.util.HashMap

    2018-06-22 09:50:19.488  INFO 20096 --- [nio-8081-exec-2] o.a.c.c.C.[Tomcat].[localhost].[/]       : ...

  9. Java数据结构HashMap

    java数据结构HashMap /** * <html> * <body> * <P> Copyright JasonInternational</p> ...

随机推荐

  1. Oracle数据库常用关键字以及函数

    常用关键字 insert into---插入数据 delete---删除数据 update---更新一条数据 select---实际工作中尽量不要写* set---设置某些属性 where---给执行 ...

  2. getComputedStyle方法获取元素CSS值

    javascript的style属性只能获取内联样式,对于外部样式和嵌入式样式需要用currentStyle属性.但是,currentStyle在FIrefox和Chrome下不支持,需要用getCo ...

  3. Mybatis源码分析-SqlSessionTemplate

    承接Mybatis源码解析-MapperRegistry注册mapper接口,本文将在前文基础上讲解持久层的生成 SqlSessionFactory生成 在spring中,SqlSessionFact ...

  4. 填涂颜色 洛谷 p1162

    题目描述 由数字0 组成的方阵中,有一任意形状闭合圈,闭合圈由数字1构成,围圈时只走上下左右4个方向.现要求把闭合圈内的所有空间都填写成2.例如:6X6的方阵(n=6),涂色前和涂色后的方阵如下: 0 ...

  5. sqlite 的基本使用1

    mac 下自带的sqlite3 直接在终端键入 sqlite3 即进入 sqlite的交互界面 1,创建数据库 sqlite3 命令 被用来创建新的数据库 比如sqlite3 mydb,即创建了一个m ...

  6. Mac OS X下安装和配置Maven

    1.下载Maven 打开Maven官网下载页面:http://maven.apache.org/download.cgi 下载:apache-maven-3.5.0-bin.tar.gz 解压下载的安 ...

  7. Linux工具参考篇(网摘)

    Linux工具参考篇 原文出处:[Linux Tools Quick Tutorial] 1. gdb 调试利器 2. ldd 查看程序依赖库 3. lsof 一切皆文件 4. ps 进程查看器 5. ...

  8. thymeleaf文本处理

    文本处理 显示文本是网页开发的最基本需求,另外,国际化的程序当今也是相当必要的.这些问题,thymeleaf都可以轻松解决. th:text标签属性 这个属性的基本作用就是显示文本,它的值可以既可以从 ...

  9. webService接口交互

    1.需要在XXXXX.wsdl中配置相应的类与service方法. 2.启动你的项目,打开浏览器,输入地址:http://localhost:8080/lis/services,就能看到你XXXXX. ...

  10. canvas图形处理和进阶用法

    前面的话 上一篇博客介绍了canvas基础用法,本文将更进一步,介绍canvas的图形处理和进阶用法 图形变换 图形变换是指用数学方法调整所绘形状的物理属性,其实质是坐标变形.所有的变换都依赖于后台的 ...