zoukankan      html  css  js  c++  java
  • [转]Java手把手系列:使用HashMap的基本世界观(存储/获取/遍历/集合观)

    原创 DrunkCoder 写bug咯 2019-09-21

    3. HashMap基本世界观

    第二小节介绍了什么是哈希表以及Java里面对应的实现类HashMap,本小节就来看看Java里面的HashMap如何使用。

     

    3.1 put/存储

     

    3.1.1 基本用法

    往HashMap里面存储一个数据,需要调用其方法:

     

    V put(K key, V value)

    put方法接收两个参数:key和value, key会用来计算hash得到其存储位置,value就是要存储的值。

    当使用key将一个value添加到HashMap里面,这个key对象本身的hashCode函数会被调用来计算hash值,我们来做一个实验:

    • 首先我们生成一个KeyObject的类,用来作为HashMap的key

    • 然后在KeyObject的hashCode添加日志,看看调用put的时候会不会输出

     

    public class KeyObject {
        private int id;
    
        @Override
        public int hashCode() {
            System.out.println("Calling hashCode() function");
            return id;
        }
    
        public KeyObject(int id){
          this.id = id;
        }
    }
    

      

    我们会使用KeyObject来映射存储一个String的value值,如下:

     

    public static void test_hashmap_put() {
        KeyObject key = new KeyObject(1);
        Map<KeyObject, String> map = new HashMap<>();
        map.put(key, "val");
    }
    

    上面代码会把数据存入HashMap,本身没有任何其他逻辑,但是执行上述代码的时候,我们会得到输出:

     

    Calling hashCode() function

    这个是为什么呢?

     

    3.1.2 put流程(hashCode与hash的计算)

    上一小节说到:key对象本身的hashCode函数会被调用来计算hash值,我们一步一步来看put的实现:

     

    public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
    }

    可以看到:

    • put会调用putVal

    • putVal里面会调用函数:hash(key)

    来看看hash函数做了什么:

     

    static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

    原来如此:

    • hash函数会调用key的hashCode api用来计算hash值

    • 这里还会与h本身右移之后做异或(后面来分析index的如何生成)

    这个也是上面KeyObject的hashCode函数会被调用的原因。

    细心的人会好奇,在putVal函数里面,key已经用来计算hash值了,为什么后面第二个参数还是要传入key呢?

    good question!!!

    那是因为:

    • Java HashMap计算得到index之后,key和value都一起被存了

    • 还记得我们再将HashMap存储结构到时候说到:Java HashMap存储到元素叫Entry,key和value一起被放到Entry里面存起来了

     

    3.1.3 要点总结(面试常问)
    • put的存储过程,key的hashCode如何使用的

    • HashMap里面如何存储key的

     

    3.2 get/检索

    3.2.1 基本用法

    在HashMap里面获取一个元素,需要调用其方法:

     

    public V get(Object key) {
    Node<K,V> e;
    return (e = getNode(hash(key), key)) == null ? null : e.value;
    }

    get 一个元素需要知道它被存入HashMap的时候的key,我们来看一个简单的demo:

     

    public void test_hashmap_get() {
        Map<String, String> map = new HashMap<>();
        map.put("xiaoming", "清华大学");
    
        String val = map.get("xiaoming");
        System.out.println(val)
    }
    
    • 我们先构造一个String->String的map

    • 然后才使用key:xiaoming存入value:清华大学

    • 然后使用get,传入key:xiaoming检索

    • 最后输出的是“清华大学”

    3.2.2 get的流程(与put类似)

    我们从源码看到,get的时候,依旧会使用key来计算hash值,这个就很容易理解了:
    前后的hash值肯定要一致,才能找到要获取的元素。

     

    3.3 从集合角度看HashMap

    我们前面说,HashMap不是继承自Collection接口,但是HashMap提供了几种接口,可以把HashMap当作collection来对待。

     

    3.3.1 Key集合

    我们可以通过方法:keySet 得到map里面所有的keys

     

    public void test_hashmap_keyset() {
        Map<String, String> map = new HashMap<>();
        map.put("name", "xm");
        map.put("type", "student");
    
        Set<String> keys = map.keySet();
        System.out.println(keys.size())
        System.out.println(keys.contains("name"));
        System.out.println(keys.contains("type"));
    }
    

    通过keySet去修改key,同样会影响到HashMap本身,我们来实验以下:

     

    public void test_hashmap_keyset_modify() {
        Map<String, String> map = new HashMap<>();
        map.put("name", "xm");
        map.put("type", "student");
        //map size = 2
        System.out.println(map.size());
    
        Set<String> keys = map.keySet();
        keys.remove("name");
        //map size = 1
        System.out.println(map.size());
    }
    
    3.3.2 Values集合

    同样地,可以通过values()方法得到map里面所有的values

     
    
    public void test_hashmap_values() {
        Map<String, String> map = new HashMap<>();
        map.put("name", "xm");
        map.put("type", "student");
    
        Collection<String> values = map.values();
    
        //size = 2
        System.out.println(values.size());
        //true
        System.out.println(values.contains("xm"));
        //true
        System.out.println(values.contains("student"));
    }
    

    同样地,通过values去修改value,同样会影响到HashMap本身。

     

    3.3.3 Key-Value集合

    一般,我们可以通过entrySet获取key和values的pairs集合。

     

    public void test_hashmap_entryset() {
        Map<String, String> map = new HashMap<>();
        map.put("name", "xm");
        map.put("type", "student");
    
        Set<Entry<String, String>> entries = map.entrySet();
    
        System.out.println(entries.size());
        for (Entry<String, String> e : entries) {
            String key = e.getKey();
            String val = e.getValue();
            System.out.println(key);
            System.out.println(val);
        }
    }
    
    3.3.4 关于Map集合的角度的叨叨说明
    • HashMap存储的元素,本身无序的,所以我们上面测试代码,都是无序的

    • 上面得到的key/value/key-value集合的迭代器,都是fail-fast的,即:得到迭代器之后,再修改会报异常

    • fail-fast的情况下,只能通过迭代器的remove函数来修改集合

    • hashmap的迭代效率要比tree map和linked hash map低,后面的文章我们来对比。

     

    3.4 HashMap的遍历

    HashMap至少可以说出四种遍历方式

     

    3.4.1 迭代器遍历少林篇

    直接使用EntrySet的迭代器来遍历,直接获取key & value,代码如下:

    public static void traverseByEntrySet{
        Map<String,String> map = new HashMap();
        Iterator iter = map.entrySet().iterator();
        while (iter.hasNext()) {
          Map.Entry entry = (Map.Entry) iter.next();
          Object key = entry.getKey();
          Object val = entry.getValue();
      }
    }
    

     

    推荐使用这种方式,简单高效!

     

    3.4.2 迭代器遍历峨眉派

    使用keyset的迭代器来遍历,然后通过key获取value

     

    public static void traverseByKeySet{
        Map<String,String> map = new HashMap();
        Iterator iter = map.keySet().iterator();
        while (iter.hasNext()) {
          Object key = iter.next();
          Object val = map.get(key);
      }
    }
    

      

     

    不推荐这种方式,遍历了key,还要通过get去获取value,多此一举!

     

    3.4.3 foreach遍历武当篇

    可以使用EntrySet,然后使用foreach遍历这个colleciton

     

    Map<String, String> map = new HashMap<String, String>();
    for (Entry<String, String> entry : map.entrySet()) {
      entry.getKey();
      entry.getValue();
    }
    

      

     

     

    3.4.4 foreach遍历青城派

    3.4.2 迭代器遍历峨眉派 一样的道理,获取keyset,foreach遍历keyset,然后使用get获取value

     

    Map<String, String> map = new HashMap<String, String>();
    for (String key : map.keySet()) {
    map.get(key);
    }

     

    3.4.5 要点总结(面试常问)

    面试常见问题:
    四种方式有两种不推荐,效率也有差异,小伙伴你知道吗?
    可以下去试试耗时

    4. 小节

    今天带来了HashMap的基本用法,但是里面的hash值和index如何转换的,它的容量、冲突等面试常见的还没有涉及,我们会在后面的文章一一道来。

     
     

    微信扫一扫
    关注该公众号

  • 相关阅读:
    字典树+二叉树
    ##22
    简单代码优雅写
    全排列
    【持续更新】哟!又在写BUG啊!
    大整数加法和大整数乘法
    【框架编程思想】线数筛的高级应用(欧拉12题和欧拉21题)
    【持续更新】 用算法流程实现的代码块们
    记忆化
    资源收集
  • 原文地址:https://www.cnblogs.com/alamZ/p/13612273.html
Copyright © 2011-2022 走看看