简述hashmap集合遍历的两种方法_遍历集合

简述hashmap集合遍历的两种方法_遍历集合HashMap遍历方法;HashMap实现原理分析

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE使用 1年只要46元 售后保障 童叟无欺

1.HashMap

1.1.HashMap遍历方法

public class CircleMap {

public static void main(String[] args) {

 

//创建HashMap对象

   HashMap<String, Integer> tempMap = new HashMap<String, Integer>();

//<key,value>的形式向HashMap对象中添加数据。

//数据的存储数据与添加的顺序无关,与key值对应的hash值有关

   tempMap.put(“a”, 1);

   tempMap.put(“b”, 2);

   tempMap.put(“c”, 3);

 

   // 遍历方法一 hashmap entrySet() 遍历

   System.out.println(“方法一“);

   Iterator it = tempMap.entrySet().iterator();

   while (it.hasNext()) {

//HashMap里面静态内部类Entry对象

    Map.Entry entry = (Map.Entry) it.next();  

    Object key = entry.getKey();       //取得key

    Object value = entry.getValue();   //取得value

    System.out.println(“key=” + key + ” value=” + value);

   }

 

   // JDK1.5,应用新特性For-Each循环

   // 遍历方法二

   System.out.println(“方法二“);

//for each方法遍历

   for (Map.Entry<String, Integer> entry : tempMap.entrySet()) {

    String key = entry.getKey().toString();  

    String value = entry.getValue().toString();

    System.out.println(“key=” + key + ” value=” + value);

   }

 

// 遍历方法三 hashmap keySet() 遍历

//map.keySet():表示包含HashMap中所有keyCollection对象

   System.out.println(“方法三“);

   for (Iterator i = tempMap.keySet().iterator(); i.hasNext();) {

    Object obj = i.next();   //获取下一个key

    System.out.println(obj);// 循环输出key

    System.out.println(“key=” + obj + ” value=” + tempMap.get(obj));

   }

 

//map.values():表示包含HashMap中所有valueCollection对象

   for (Iterator i = tempMap.values().iterator(); i.hasNext();) {

    Object obj = i.next();  //获取下一个value

    System.out.println(obj);// 循环输出value

   }

 

   // 遍历方法四 treemap keySet()遍历

   System.out.println(“方法四“);

//for each方法遍历

   for (Object o : tempMap.keySet()) {

//o对象的值属于key值。

//map.get(key)方法:通过key值获取value

    System.out.println(“key=” + o + ” value=” + tempMap.get(o));

   }

 

   // java如何遍历HashMap <String, ArrayList> map = new HashMap <String,

   // ArrayList>();

   System.out

     .println(“java 遍历Map <String, ArrayList> map = new HashMap <String, ArrayList>();”);

   HashMap<String, ArrayList> map = new HashMap<String, ArrayList>();

   Set<String> keys = map.keySet();  //创建包含 所有key值得集合

   Iterator<String> iterator = keys.iterator();   //创建遍历所有key值得对象

   while (iterator.hasNext()) {

    String key = iterator.next();

   //将通过key参数获得对应的value值保存在ArrayList集合中

    ArrayList arrayList = map.get(key); 

    for (Object o : arrayList) {  //使用for each方法遍历ArrayList集合

    System.out.print(o + ” “);

   }

//使用List集合遍历HashMap

   HashMap<String, List> mapList = new HashMap<String, List>();

   for (Map.Entry entry : mapList.entrySet()) {

    String key = entry.getKey().toString();

    List<String> values = (List) entry.getValue();

    for (String value : values) {

    System.out.println(key + ” –> ” + value);

   }

 }

}

1.2.HashMap(哈希表)实现原理分析

哈希表的实现有几种方法:

1.2.1HashMap的数据结构

 简述hashmap集合遍历的两种方法_遍历集合

 简述hashmap集合遍历的两种方法_遍历集合

简述hashmap集合遍历的两种方法_遍历集合

HashMap是以键值对<key,value>的形式存储数据的。

从上图我们可以发现哈希表是由数组+链表组成的,一个长度为16的数组中,每个元素存储的是一个链表的头结点。那么这些元素是按照什么样的规则存储到数组中呢。一般情况是通过hash(key)%len获得,也就是元素的key的哈希值对数组长度取模得到。比如上述哈希表中,12%16=12,28%16=12,108%16=12,140%16=12。所以1228108以及140都存储在数组下标为12的位置。

HashMap怎么实现按键值对来存取数据呢?这里HashMap有做一些处理:

首先HashMap里面实现一个静态内部类Entry,其重要的属性有 key , value, next,从属性key,value我们就能很明显的看出来Entry就是HashMap键值对实现的一个基础bean,我们上面说到HashMap的基础就是一个线性数组,这个数组就是Entry[]Map里面的内容都保存在Entry[]里面。

1.2.2.HashMap的存取实现

 

// 存储时:

// 这个hashCode方法这里不详述,只要理解每个keyhash是一个固定的int
int hash = key.hashCode(); 

int index = hash % Entry[].length;
Entry[index] = value;

// 取值时:
int hash = key.hashCode();
int index = hash % Entry[].length;
return Entry[index];

 1putmap.put(key,value)

疑问:如果两个key通过hash%Entry[].length得到的index相同,会不会有覆盖的危险?

这里HashMap里面用到链式数据结构的一个概念。上面我们提到过Entry类里面有一个next属性,作用是指向下一个Entry例如,第一个键值对A进来,通过计算其keyhash得到的index=0,记做:Entry[0] = A。一会后又进来一个键值对B,通过计算其index也等于0,现在怎么办?HashMap会这样做:B.next = A,Entry[0] = B,如果又进来C,index也等于0,那么C.next = B,Entry[0] = C;这样我们发现index=0的地方其实存取了A,B,C三个键值对,他们通过next这个属性链接在一起。也就是说数组中存储的是最后插入的元素。

HashMap<String , Double> map = new HashMap<String , Double>(); 
 map.put("语文" , 80.0); 
 map.put("数学" , 89.0); 
 map.put("英语" , 78.2); 

put(K key, V value)源代码:

 Entry[] table;

 public V put(K key, V value) {

        if (key == null)

            return putForNullKey(value); //null总是放在数组的第一个链表中

// 根据 key 的 keyCode 计算 Hash 值

        int hash = hash(key.hashCode());

// 搜索指定 hash 值在对应 table 中的索引

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

        // 遍历链表:如果 i 索引处的 Entry 不为 null,通过循环不断遍历 e 元素的下一个元素

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

            Object k;

 //如果key在链表中已存在,则替换为新value

//<key,value>是一一映射的:相同的key对应相同的value值。

            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); // 将 key、value 添加到 i 索引处

        return null;

} 

//该方法仅用于添加一个key-value队

void addEntry(int hash, K key, V value, int bucketIndex) {

// 获取指定 bucketIndex 索引处的 Entry 

  Entry<K,V> e = table[bucketIndex];

  // 将新创建的 Entry 放入 bucketIndex 索引处,并让新的 Entry 指向原来的 Entry

    table[bucketIndex] = new Entry<K,V>(hash, key, value, e); //参数e, 是Entry.next

    //如果size超过threshold,则扩充table大小为原来的2倍。再散列

    if (size++ >= threshold)

            resize(2 * table.length);

}

当然HashMap里面也包含一些优化方面的实现,这里也说一下。比如:Entry[]的长度一定后,随着map里面数据的越来越长,这样同一个index的链就会很长,会不会影响性能?HashMap里面设置一个因子,随着mapsize越来越大,Entry[]会以一定的规则加长长度。 根据上面 put 方法的源代码可以看出,当程序试图将一个 key-value 对放入 HashMap 中时,程序首先根据该 key 的 hashCode() 返回值决定该 Entry 的存储位置:如果两个 Entry 的 key 的 hashCode() 返回值相同,那它们的存储位置相同。如果这两个 Entry 的 key 通过 equals 比较返回 true,新添加 Entry 的 value 将覆盖集合中原有 Entry 的 value,但 key 不会覆盖。如果这两个 Entry 的 key 通过 equals 比较返回 false,新添加的 Entry 将与集合中原有 Entry 形成 Entry 链,而且新添加的 Entry 位于 Entry 链的头部。当向 HashMap 中添加 key-value 对,由其 key 的 hashCode() 返回值决定该 key-value 对(就是 Entry 对象)的存储位置。当两个 Entry 对象的 key 的 hashCode() 返回值相同时,将由 key 通过 eqauls() 比较值决定是采用覆盖行为(返回 true),还是产生 Entry 链(返回 false)。

2getvalue=map.get(key)

 public V get(Object key) {

 // 如果 key 是 null,调用 getForNullKey 取出对应的 value

        if (key == null)

            return getForNullKey();

// 根据该 key 的 hashCode 值计算它的 hash 码 

        int hash = hash(key.hashCode());

        //先定位到数组元素,再遍历该元素处的链表

 // 直接取出 table 数组中指定索引处的值

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

             e != null;

             e = e.next) {

            Object k;

// 如果该 Entry 的 key 与被搜索 key 相同

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

                return e.value;

        }

        return null;

}

3null key的存取

null key总是存放在Entry[]数组的第一个元素。

   private V putForNullKey(V value) {

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

            if (e.key == null) {

                V oldValue = e.value;

                e.value = value;

                e.recordAccess(this);

                return oldValue;

            }

        }

        modCount++;

        addEntry(0, null, value, 0);

        return null;

    }

    private V getForNullKey() {

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

            if (e.key == null)

                return e.value;

        }

        return null;

    }

4)确定数组indexhashcode % table.length取模

HashMap存取时,都需要计算当前key应该对应Entry[]数组哪个元素,即计算数组下标;算法如下:

 //Returns index for hash code h.

    static int indexFor(int h, int length) {

        return h & (length-1);

    }

按位取并,作用上相当于取模mod或者取余%

这意味着数组下标相同,并不表示hashCode相同。

 

1.3. 解决hash冲突的办法

1. 开放定址法(线性探测再散列,二次探测再散列,伪随机探测再散列)

2. 再哈希法

3. 链地址法

4. 建立一个公共溢出区

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/195491.html原文链接:https://javaforall.net

(0)
上一篇 2025年10月16日 上午10:22
下一篇 2025年10月16日 上午11:01


相关推荐

  • ES6数组常用方法总结[通俗易懂]

    ES6数组常用方法总结[通俗易懂]这里写自定义目录标题欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题,有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants创建一个自定义列表如何创建一个注脚注释也是必不可少的KaTeX数学公式新的甘特图功能,丰富你的文章UML图表FLowchart流程图导出与导入导出导入欢迎使用Markdown编辑器你好!这是你第一次使用Markdown编辑器所展示的欢迎页。如果你想学习如何使用Mar

    2022年6月3日
    47
  • error: Microsoft Visual C++ 14.0 is required问题最佳解决方法

    error: Microsoft Visual C++ 14.0 is required问题最佳解决方法对于程序员来说 经常 pip 安装自己所需要的包 大部分的包基本都能安装 但是总会遇到包安装不了的问题 预研学习的动力第一步就被安装包给扼杀了 其中最受困扰的就是这个问题 error MicrosoftVis 14 0isrequired Getitwith MicrosoftVis 微软就是霸道 看到这个问题上网搜索一下 我都不想解决 感觉需要重装系统才能

    2026年3月20日
    2
  • 分治算法详细讲解(含经典例题分析)

    分治算法详细讲解(含经典例题分析)分治法思路 将整个问题分解成若干小问题后再分而治之 如果分解得到的子问题相对来说还是太大 则可反复使用分治策略将这些子问题分成更小的同类型子问题 直至产生方便求解的子问题 必要时逐步合并这些子问题的解 从而得到问题的解 分治算法可以由递归过程来表示 因为分治法就是一种找大规模问题与小规模问题关系的方法 是递归设计的一种具体策略 步骤 1 分解将原问题分解为若干规模较小 相互独立 与原问题相同的子问题 2 解决若干子问题较小而容易被解决则直接解决 否则再继续分解为更小的子问题 直到容易解决 3

    2026年3月20日
    2
  • js 取模 取余

    var i=10;var j=3;var mo=Math.floor(i/j);var yu=i%j;

    2022年4月9日
    44
  • 考研数学二常用公式_考研数学写公式有分吗

    考研数学二常用公式_考研数学写公式有分吗面(体)积公式一元二次方程基础极坐标方程与直角坐标转换切线与法线方程因式分解公式阶乘与双阶乘函数的奇偶性排列组合等差数列等比数列常用数列前n项和不等式三角函数公式诱导公式平方关系两角和与差的三角函数积化和差公式和差化积公式倍角公式半角公式万能公式其他公式反三角函数恒等式极限相关公式数列极限递推式重要极限公式常用等价无穷小1^∞型导数相关公式导数定义微分定义连续,可导及可微关系一元函数多元函数导数四则运算复合函数求导反函数求导参数方程

    2022年8月11日
    27
  • prototype.js的系列文章——关于prototype.js

    prototype.js的系列文章——关于prototype.js 很早就知道prototype.js是一个javascript的工具函数库,平时的开发中使用频率也非常的高,但是,由于工作时间问题,一直都没有静下心来研究学习一下,最近又萌发了系统学习prototype.js的念头,刚好手头比较闲,就决定边学习边将学习心得记录下来,以和更多的同仁交流分享。关于prototype.js如果你曾经使用过prototype.js,那么,本系列文章希望能够给你提供

    2022年7月23日
    12

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号