1. Map
Map接口中,键和值一一映射,可以通过键来获取值。
在数组中我们是通过数组下标来对其内容索引的,而在Map中我们通过对象来对对象进行索引,用来索引的对象叫做key,其对应的对象叫做value。这就是我们平时说的键值对。

  1. 给定一个键和一个值,你可以将该值存储在一个Map对象. 之后,你可以通过键来访问对应的值。
  2. 当访问的值不存在的时候,方法就会抛出一个NoSuchElementException异常.
  3. 当对象的类型和Map里元素类型不兼容的时候,就会抛出一个 ClassCastException异常。
  4. 当在不允许使用Null对象的Map中使用Null对象,会抛出一个NullPointerException 异常。
  5. 当尝试修改一个只读的Map时,会抛出一个UnsupportedOperationException异常。

Map接口的方法

序号 方法描述
1 void clear( )
 从此映射中移除所有映射关系(可选操作)。
2 boolean containsKey(Object k)
如果此映射包含指定键的映射关系,则返回 true。
3 boolean containsValue(Object v)
如果此映射将一个或多个键映射到指定值,则返回 true。
4 Set entrySet( )
返回此映射中包含的映射关系的 Set 视图。
5 boolean equals(Object obj)
比较指定的对象与此映射是否相等。
6 Object get(Object k)
返回指定键所映射的值;如果此映射不包含该键的映射关系,则返回 null。
7 int hashCode( )
返回此映射的哈希码值。
8 boolean isEmpty( )
如果此映射未包含键-值映射关系,则返回 true。
9 Set keySet( )
返回此映射中包含的键的 Set 视图。
10 Object put(Object k, Object v)
将指定的值与此映射中的指定键关联(可选操作)。
11 void putAll(Map m)
从指定映射中将所有映射关系复制到此映射中(可选操作)。
12 Object remove(Object k)
如果存在一个键的映射关系,则将其从此映射中移除(可选操作)。
13 int size( )
返回此映射中的键-值映射关系数。
14 Collection values( )
返回此映射中包含的值的 Collection 视图。

下面的关于HashMap的原文地址:https://baijiahao.baidu.com/s?id=1618550070727689060&wfr=spider&for=pc
2. HashMap

  1. HashMap是一个散列桶(数组和链表),它存储的内容是键值对(key-value)映射
  2. 最常用的Map,它根据键的HashCode 值存储数据,根据键可以直接获取它的值,
  3. HashMap最多只允许一条记录的键为Null(多条会覆盖);允许多条记录的值为 Null;允许键和值同时为空。
  4. 因为HashMap是非同步(非synchronized)的,所以具有很快的访问速度。
  5. HashMap采用了数组和链表的数据结构,能在查询和修改方便继承了数组的线性查找和链表的寻址修改
  6. key的hash值是先计算key的hashcode值,然后再进行计算,每次容量扩容会重新计算所以key的hash值,会消耗资源,要求key必须重写equals和hashcode方法
  7. 默认初始容量16,加载因子0.75,扩容为旧容量乘2,查找元素快,如果key一样则比较value,如果value不一样,则按照链表结构存储value,就是一个key后面有多个value;

HashMap的工作原理
HashMap是基于hashing的原理,我们使用put(key, value)存储对象到HashMap中,使用get(key)从HashMap中获取对象。当我们给put()方法传递键和值时,我们先对键调用hashCode()方法,计算并返回的hashCode是用于找到Map数组的bucket位置来储存Node 对象。这里关键点在于指出,HashMap是在bucket中储存键对象和值对象,作为Map.Node 。

a. 以下是HashMap初始化 ,简单模拟数据结构
Node[] table=new Node[16] 散列桶初始化,tableclass Node { hash;//hash值 key;//键 value;//值 node next;//用于指向链表的下一层(产生冲突,用拉链法)}

b.以下是具体的put过程(JDK1.8版)

  1. 对Key求Hash值,然后再计算下标

  2. 如果没有碰撞,直接放入桶中(碰撞的意思是计算得到的Hash值相同,需要放到同一个bucket中)

  3. 如果碰撞了,以链表的方式链接到后面

  4. 如果链表长度超过阀值( TREEIFY THRESHOLD==8),就把链表转成红黑树,链表长度低于6,就把红黑树转回链表

  5. 如果节点已经存在就替换旧值

  6. 如果桶满了(容量16*加载因子0.75),就需要 resize(扩容2倍后重排)

c. 以下是具体get过程(考虑特殊情况如果两个键的hashcode相同,你如何获取值对象?)
当我们调用get()方法,HashMap会使用键对象的hashcode找到bucket位置,找到bucket位置之后,会调用keys.equals()方法去找到链表中正确的节点,最终找到要找的值对象。


有什么方法可以减少碰撞?

  1. 扰动函数可以减少碰撞,原理是如果两个不相等的对象返回不同的hashcode的话,那么碰撞的几率就会小些,这就意味着存链表结构减小,这样取值的话就不会频繁调用equal方法,这样就能提高HashMap的性能。(扰动即Hash方法内部的算法实现,目的是让不同对象返回不同hashcode。)
  2. 使用不可变的、声明作final的对象,并且采用合适的equals()和hashCode()方法的话,将会减少碰撞的发生。不可变性使得能够缓存不同键的hashcode,这将提高整个获取对象的速度,使用String,Interger这样的wrapper类作为键是非常好的选择。为什么String, Interger这样的wrapper类适合作为键?因为String是final的,而且已经重写了equals()和hashCode()方法了。不可变性是必要的,因为为了要计算hashCode(),就要防止键值改变,如果键值在放入时和获取时返回不同的hashcode的话,那么就不能从HashMap中找到你想要的对象。

HashMap中hash函数怎么是是实现的?
我们可以看到在hashmap中要找到某个元素,需要根据key的hash值来求得对应数组中的位置。如何计算这个位置就是hash算法。前面说过hashmap的数据结构是数组和链表的结合,所以我们当然希望这个hashmap里面的元素位置尽量的分布均匀些,尽量使得每个位置上的元素数量只有一个,那么当我们用hash算法求得这个位置的时候,马上就可以知道对应位置的元素就是我们要的,而不用再去遍历链表。 所以我们首先想到的就是把hashcode对数组长度取模运算,这样一来,元素的分布相对来说是比较均匀的。但是,“模”运算的消耗还是比较大的,能不能找一种更快速,消耗更小的方式,我们来看看JDK1.8的源码是怎么做的(被楼主修饰了一下)

static final int hash(Object key){if (key == null){ return 0; }int h;h=key.hashCode();return (n-1)&(h ^ (h >>> 16));}

返回散列值也就是hashcode // ^ :按位异或 // >>>:无符号右移,忽略符号位,空位都以0补齐 //其中n是数组的长度,即Map的数组部分初始化长度 return (n-1)&(h ^ (h >>> 16));}

简单来说就是

1、高16bt不变,低16bit和高16bit做了一个异或(得到的HASHCODE转化为32位的二进制,前16位和后16位低16bit和高16bit做了一个异或)

2、(n·1)&hash=->得到下标

如果HashMap的大小超过了负载因子(load factor)定义的容量,怎么办?

默认的负载因子大小为0.75,也就是说,当一个map填满了75%的bucket时候,和其它集合类(如ArrayList等)一样,将会创建原来HashMap大小的两倍的bucket数组,来重新调整map的大小,并将原来的对象放入新的bucket数组中。这个过程叫作rehashing,因为它调用hash方法找到新的bucket位置。这个值只可能在两个地方,一个是原下标的位置,另一种是在下标为<原下标+原容量>的位置

重新调整HashMap大小存在什么问题吗?

当重新调整HashMap大小的时候,确实存在条件竞争,因为如果两个线程都发现HashMap需要重新调整大小了,它们会同时试着调整大小。在调整大小的过程中,存储在链表中的元素的次序会反过来,因为移动到新的bucket位置的时候,HashMap并不会将元素放在链表的尾部,而是放在头部,这是为了避免尾部遍历(tail traversing)。如果条件竞争发生了,那么就死循环了。(多线程的环境下不使用HashMap)

为什么多线程会导致死循环,它是怎么发生的?
HashMap的容量是有限的。当经过多次元素插入,使得HashMap达到一定饱和度时,Key映射位置发生冲突的几率会逐渐提高。这时候,HashMap需要扩展它的长度,也就是进行Resize。1.扩容:创建一个新的Entry空数组,长度是原数组的2倍。2.ReHash:遍历原Entry数组,把所有的Entry重新Hash到新数组。

HashMap为什么是线程不安全的?
HashMap 在并发时可能出现的问题主要是两方面:

  1. put的时候导致的多线程数据不一致
    比如有两个线程A和B,首先A希望插入一个key-value对到HashMap中,首先计算记录所要落到的 hash桶的索引坐标,然后获取到该桶里面的链表头结点,此时线程A的时间片用完了,而此时线程B被调度得以执行,和线程A一样执行,只不过线程B成功将记录插到了桶里面,假设线程A插入的记录计算出来的 hash桶索引和线程B要插入的记录计算出来的 hash桶索引是一样的,那么当线程B成功插入之后,线程A再次被调度运行时,它依然持有过期的链表头但是它对此一无所知,以至于它认为它应该这样做,如此一来就覆盖了线程B插入的记录,这样线程B插入的记录就凭空消失了,造成了数据不一致的行为。
  2. resize而引起死循环
    这种情况发生在HashMap自动扩容时,当2个线程同时检测到元素个数超过 数组大小 × 负载因子。此时2个线程会在put()方法中调用了resize(),两个线程同时修改一个链表结构会产生一个循环链表(JDK1.7中,会出现resize前后元素顺序倒置的情况)。接下来再想通过get()获取某一个元素,就会出现死循环。

作者:小北觅
链接:https://www.jianshu.com/p/ee0de4c99f87

HashMap的三种遍历方法:

package HashMap_demo;import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;public class HashMap_demo {public static void main(String[] args) {//Map<Integer, String> map=new HashMap<>();//这个和底下的一个写法都可以。Map<Integer, String> map=new HashMap<Integer, String>();map.put(1, "A");map.put(8, "D");map.put(3, "C");map.put(4, "D");map.put(3, "E");map.put(2, "");map.put(null, "C");//通过Map中的keySet()方法遍历获得key值进行输出//看别的大佬都管这叫增强for循环遍历System.out.println("通过Map中的keySet()方法遍历获得key值进行输出:");for(Integer s:map.keySet()) {System.out.print("Key="+s+":");System.out.print("value="+map.get(s)+"--");}//通过Map中的keySet()方法与valueSet()方法遍历获得key值和value值进行输出System.out.println("\n"+"通过Map中的keySet()方法与valueSet()方法遍历获得key值和value值进行输出");for(Integer s:map.keySet()) {System.out.print("Key="+s+":");}for(String v:map.values()) {System.out.print("value="+v+":");}//使用Iterator迭代器迭代输出System.out.println("\n"+"使用Iterator迭代器进行迭代输出:");Iterator<Entry<Integer, String>> iter=map.entrySet().iterator();while(iter.hasNext()) {Entry<Integer, String> entry=iter.next();System.out.print("Key="+entry.getKey()+":");System.out.print("value="+entry.getValue()+"--");}}}

运行结果

通过Map中的keySet()方法遍历获得key值进行输出:
Key=null:value=C--Key=1:value=A--Key=2:value=--Key=3:value=E--Key=4:value=D--Key=8:value=D--
通过Map中的keySet()方法与valueSet()方法遍历获得key值和value值进行输出
Key=null:Key=1:Key=2:Key=3:Key=4:Key=8:value=C:value=A:value=:value=E:value=D:value=D:
使用Iterator迭代器进行迭代输出:
Key=null:value=C--Key=1:value=A--Key=2:value=--Key=3:value=E--Key=4:value=D--Key=8:value=D--

注意:hashMap的遍历输出结果应该是无序的,我这里不知道为啥,一直是有序的…

下段代码来自:https://www.cnblogs.com/biehongli/p/6443701.html 做了一点点小改动

package Map_demo;import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;public class HashMap_demo {public static void main(String[] args) {// 1:key,value都是object类型的// 2:key必须是唯一的,不唯一,那么后面的value会把前面的value覆盖// 3:对于HashMap,key可以为空// 4:value可以为空,也可以为空// 5:HashTable的key和value不能为空// 6:properties的key和value必须为String类型的Map<String, String> map = new HashMap<>();map.put("null", "this is null 1");map.put("null", "this is null 2");map.put(null, "NO NUll");System.out.println(map.size());System.out.println(map.get("null"));System.out.println(map.get(null));System.out.println("=============================");// 循环显示map类型的key以及对应的value// 三个集合,key的集合,value的集合,键值对的集合Set<String> keys = map.keySet();for (String s : keys) {System.out.println(s);}System.out.println("=============================");Collection<String> values = map.values();// 值的集合System.out.println(values);System.out.println("=============================");Set<Map.Entry<String, String>> entrys = map.entrySet();// 键值对的集合for (Map.Entry<String, String> entry : entrys) {System.out.println(entry.getKey() + " " + entry.getValue());}}
}

输出结果:

2
this is null 2
NO NUll
=============================
null
null
=============================
[NO NUll, this is null 2]
=============================
null NO NUll
null this is null 2

总结:上段代码说明了

TreeMap:https://mp.csdn.net/mdeditor/98071869#

HashMap与TreeMap的区别

a.)Map是接口,HashMap与TreeMap是类(即HashMap和TreeMap实现了Map的所有的方法)
b.)HashMap非线程安全,TreeMap线程安全
c.)HashMap:适用于在Map中插入、删除和定位元素。
Treemap:适用于按自然顺序或自定义顺序遍历键(key)。

d.)HashMap通常比TreeMap快一点(树和哈希表的数据结构使然),建议多使用HashMap,在需要排序的Map时候才用TreeMap。
e.)HashMap的结果是没有排序的,而TreeMap输出的结果是排好序的。

HashMap和HashTable的区别
HashMap和Hashtable都实现了Map接口,但决定用哪一个之前先要弄清楚它们之间的分别。主要的区别有:线程安全性,同步(synchronization),以及速度。

HashMap几乎可以等价于Hashtable,除了HashMap是非synchronized的,并可以接受null(HashMap可以接受为null的键值(key)和值(value),而Hashtable则不行)。
HashMap是非synchronized,而Hashtable是synchronized,这意味着Hashtable是线程安全的,多个线程可以共享一个Hashtable;而如果没有正确的同步的话,多个线程是不能共享HashMap的。Java 5提供了ConcurrentHashMap,它是HashTable的替代,比HashTable的扩展性更好。
另一个区别是HashMap的迭代器(Iterator)是fail-fast迭代器,而Hashtable的enumerator迭代器不是fail-fast的。所以当有其它线程改变了HashMap的结构(增加或者移除元素),将会抛出ConcurrentModificationException,但迭代器本身的remove()方法移除元素则不会抛出ConcurrentModificationException异常。但这并不是一个一定发生的行为,要看JVM。这条同样也是Enumeration和Iterator的区别。
由于Hashtable是线程安全的也是synchronized,所以在单线程环境下它比HashMap要慢。如果你不需要同步,只需要单一线程,那么使用HashMap性能要好过Hashtable。
HashMap不能保证随着时间的推移Map中的元素次序是不变的。

作者:小北觅
链接:https://www.jianshu.com/p/ee0de4c99f87

ps:补充小知识
线程安全
在Java里,线程安全一般体现在两个方面:

1、多个thread对同一个java实例的访问(read和modify)不会相互干扰,它主要体现在关键字synchronized。如ArrayList和Vector,HashMap和Hashtable
(后者每个方法前都有synchronized关键字)。如果你在interator一个List对象时,其它线程remove一个element,问题就出现了。

2、每个线程都有自己的字段,而不会在多个线程之间共享。它主要体现在java.lang.ThreadLocal类,而没有Java关键字支持,如像static、transient那样。

Map,HashMap,TreeMap相关推荐

  1. Map集合HashMap TreeMap的输出方法

    Map集合HashMap TreeMap的输出方法     [尊重原创,转载请注明出处]http://blog.csdn.net/guyuealian/article/details/51934301 ...

  2. java hashmap api_JAVA基础学习-集合三-Map、HashMap,TreeMap与常用API

    一.Map简述 1.1.简述 public interface Map 类型参数:K - 此映射所维护的键的类型 keyV - 映射值的类型 value该集合提供键--值的映射.key不能重复,一对对 ...

  3. Map集合(HashMap,TreeMap)

    Map 总想赢者必输,不怕输者必赢 首先对Map进行一个整体的理解. 查看API可以知道,Map 其实就是将键映射到值的对象,每个键最多映射到一个值. Map 与Collection接口的不同: Ma ...

  4. HashMap TreeMap专题

    刷leetcode的时候,经常性碰到需要使用HashMap或者TreeMap的场景,今天来总结一些它们的用法: TreeMap public class MapCase {public static ...

  5. 笔记 09-集合(HashSet HashMap TreeMap) 练习

    题目1 假如你有3个室友,请使用HashSet集合保存3个室友的信息; 信息如下: 赵丽颖,18 范冰冰,20 杨幂,19 要求: 1:室友以对象形式存在,包含姓名和年龄两个属性; 2:使用代码保证集 ...

  6. 新手小白学JAVA Set HashSet Map HashMap

    1 Map接口 1.1 概述 Java.util接口Map<K,V> 类型参数 : K - 表示此映射所维护的键 V – 表示此映射所维护的对应的值 也叫做哈希表.散列表. 常用于键值对结 ...

  7. 线程安全的HashMap,TreeMap,ArrayList,TreeSet,Set

    1.线程安全的集合:集合类中,用于实现线程安全的有两种办法,一种是使用Collections.synchronizedList来替代ArrayList 具体的API如下图: 但是该类会出现并发异常:代 ...

  8. Java专题 Map,hashMap 多种遍历方式 总结+统计字符出现次数案例

    描述 Map 与Collection同级 键值对 数据按一对为单位存储 python中的字典 Map ->双列集合的顶层接口 HashMap-> 哈希表存储 没有固定顺序 LinkedHa ...

  9. 5.概念(maven,ssm,springMvc,spring,自定义注解,二级缓存,范式,事务,mysql,线程池,map,hashmap,redis,饿汉,懒汉)

    maven是啥: 1.Maven是一个项目管理和综合工具.Maven提供了开发人员构建一个完整的生命周期框架. 创建-导入jar报–编写配置文件-实现业务功能-测试-发布上线. 2.开发团队可以自动完 ...

最新文章

  1. linux 命令-f1,linux之‘cut -f1’命令详解
  2. C# 接口的作用浅谈举例(转)
  3. python彩色图像如何进行高斯滤波ValueError: correlate2d inputs must both be 2-D arrays解决方法
  4. boost::to_address用法实例
  5. 预览速度提升30倍,这是什么黑科技?(天猫618之3D渲染篇)
  6. Tomcat7基于Redis的Session共享实战二
  7. phpcms v9 数据源
  8. CTFshow web15
  9. cocos2d-x Schedule详解
  10. 接口压力测试:Jmeter【专门做接口压力测试】
  11. 信息源按加工深度划分_信息检索教程
  12. 网站安全性之js注入
  13. 东北大学计算机学院杨金柱院长,电子信息学院赴东北大学走访、调研
  14. storj主网挖矿指南
  15. vue-router配置路由实现返回上一页,上一页页面数据留存
  16. Free Video to JPG Converter(视频转图片的软件)简体中文版V5.0.101.201 | 如何将视频转图片
  17. iFIERO -- (一) 宇宙大战 SPACE BATTLE — 新建场景SCENE、精灵节点、PARTICLE粒子及背景音乐
  18. 江工网:报到证过期对公务员有影响吗
  19. 上课期间禁止使用手机
  20. Sencha Cmd创建Ext JS示例项目

热门文章

  1. CephFS管理命令
  2. Kingbase金仓更改表空间
  3. 解决Visual SVN在linux下不能访问的问题
  4. 各种梯度下降 bgd sgd mbgd adam
  5. 在MFC对话框中快速集成三维控件
  6. 热烈祝贺Polymer中文组织站点上线
  7. Zabbix 2.4.5 自定义 key 监控 apache
  8. [jillzhang]ExtJs与WCF交互:生成树 --数据库版补充
  9. 采用HttpModules来重写URLs(实践篇)
  10. 姚文详(Joseph Yiu):《ARM Cortex-M0权威指南》中文版目录