Map,HashMap,TreeMap
1. Map
Map接口中,键和值一一映射,可以通过键来获取值。
在数组中我们是通过数组下标来对其内容索引的,而在Map中我们通过对象来对对象进行索引,用来索引的对象叫做key,其对应的对象叫做value。这就是我们平时说的键值对。
- 给定一个键和一个值,你可以将该值存储在一个Map对象. 之后,你可以通过键来访问对应的值。
- 当访问的值不存在的时候,方法就会抛出一个NoSuchElementException异常.
- 当对象的类型和Map里元素类型不兼容的时候,就会抛出一个 ClassCastException异常。
- 当在不允许使用Null对象的Map中使用Null对象,会抛出一个NullPointerException 异常。
- 当尝试修改一个只读的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
- HashMap是一个散列桶(数组和链表),它存储的内容是键值对(key-value)映射
- 最常用的Map,它根据键的HashCode 值存储数据,根据键可以直接获取它的值,
- HashMap最多只允许一条记录的键为Null(多条会覆盖);允许多条记录的值为 Null;允许键和值同时为空。
- 因为HashMap是非同步(非synchronized)的,所以具有很快的访问速度。
- HashMap采用了数组和链表的数据结构,能在查询和修改方便继承了数组的线性查找和链表的寻址修改
- key的hash值是先计算key的hashcode值,然后再进行计算,每次容量扩容会重新计算所以key的hash值,会消耗资源,要求key必须重写equals和hashcode方法
- 默认初始容量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版)
对Key求Hash值,然后再计算下标
如果没有碰撞,直接放入桶中(碰撞的意思是计算得到的Hash值相同,需要放到同一个bucket中)
如果碰撞了,以链表的方式链接到后面
如果链表长度超过阀值( TREEIFY THRESHOLD==8),就把链表转成红黑树,链表长度低于6,就把红黑树转回链表
如果节点已经存在就替换旧值
如果桶满了(容量16*加载因子0.75),就需要 resize(扩容2倍后重排)
c. 以下是具体get过程(考虑特殊情况如果两个键的hashcode相同,你如何获取值对象?)
当我们调用get()方法,HashMap会使用键对象的hashcode找到bucket位置,找到bucket位置之后,会调用keys.equals()方法去找到链表中正确的节点,最终找到要找的值对象。
有什么方法可以减少碰撞?
- 扰动函数可以减少碰撞,原理是如果两个不相等的对象返回不同的hashcode的话,那么碰撞的几率就会小些,这就意味着存链表结构减小,这样取值的话就不会频繁调用equal方法,这样就能提高HashMap的性能。(扰动即Hash方法内部的算法实现,目的是让不同对象返回不同hashcode。)
- 使用不可变的、声明作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 在并发时可能出现的问题主要是两方面:
- put的时候导致的多线程数据不一致
比如有两个线程A和B,首先A希望插入一个key-value对到HashMap中,首先计算记录所要落到的 hash桶的索引坐标,然后获取到该桶里面的链表头结点,此时线程A的时间片用完了,而此时线程B被调度得以执行,和线程A一样执行,只不过线程B成功将记录插到了桶里面,假设线程A插入的记录计算出来的 hash桶索引和线程B要插入的记录计算出来的 hash桶索引是一样的,那么当线程B成功插入之后,线程A再次被调度运行时,它依然持有过期的链表头但是它对此一无所知,以至于它认为它应该这样做,如此一来就覆盖了线程B插入的记录,这样线程B插入的记录就凭空消失了,造成了数据不一致的行为。 - 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相关推荐
- Map集合HashMap TreeMap的输出方法
Map集合HashMap TreeMap的输出方法 [尊重原创,转载请注明出处]http://blog.csdn.net/guyuealian/article/details/51934301 ...
- java hashmap api_JAVA基础学习-集合三-Map、HashMap,TreeMap与常用API
一.Map简述 1.1.简述 public interface Map 类型参数:K - 此映射所维护的键的类型 keyV - 映射值的类型 value该集合提供键--值的映射.key不能重复,一对对 ...
- Map集合(HashMap,TreeMap)
Map 总想赢者必输,不怕输者必赢 首先对Map进行一个整体的理解. 查看API可以知道,Map 其实就是将键映射到值的对象,每个键最多映射到一个值. Map 与Collection接口的不同: Ma ...
- HashMap TreeMap专题
刷leetcode的时候,经常性碰到需要使用HashMap或者TreeMap的场景,今天来总结一些它们的用法: TreeMap public class MapCase {public static ...
- 笔记 09-集合(HashSet HashMap TreeMap) 练习
题目1 假如你有3个室友,请使用HashSet集合保存3个室友的信息; 信息如下: 赵丽颖,18 范冰冰,20 杨幂,19 要求: 1:室友以对象形式存在,包含姓名和年龄两个属性; 2:使用代码保证集 ...
- 新手小白学JAVA Set HashSet Map HashMap
1 Map接口 1.1 概述 Java.util接口Map<K,V> 类型参数 : K - 表示此映射所维护的键 V – 表示此映射所维护的对应的值 也叫做哈希表.散列表. 常用于键值对结 ...
- 线程安全的HashMap,TreeMap,ArrayList,TreeSet,Set
1.线程安全的集合:集合类中,用于实现线程安全的有两种办法,一种是使用Collections.synchronizedList来替代ArrayList 具体的API如下图: 但是该类会出现并发异常:代 ...
- Java专题 Map,hashMap 多种遍历方式 总结+统计字符出现次数案例
描述 Map 与Collection同级 键值对 数据按一对为单位存储 python中的字典 Map ->双列集合的顶层接口 HashMap-> 哈希表存储 没有固定顺序 LinkedHa ...
- 5.概念(maven,ssm,springMvc,spring,自定义注解,二级缓存,范式,事务,mysql,线程池,map,hashmap,redis,饿汉,懒汉)
maven是啥: 1.Maven是一个项目管理和综合工具.Maven提供了开发人员构建一个完整的生命周期框架. 创建-导入jar报–编写配置文件-实现业务功能-测试-发布上线. 2.开发团队可以自动完 ...
最新文章
- Django ModelForm操作及验证
- 转:Silverlight样式写法
- SSH基本简介及连接交互过程
- bootstrapdatetimepicker 隐藏触发 bootstrap modal隐藏事件
- Fedora配置网络DHCP
- Kotlin 与 Java 比较
- VC++ 常见编译错误
- html三元运算符 模板,AngularJS模板中的三元运算符
- 如何让body背景图自适应浏览器窗口大小
- 操作系统进程学习(Linux 内核学习笔记)
- 冲刺阶段(二)第五天 5月16日
- JStorm—实时流式计算框架入门介绍
- vSAN架构解析与6.7功能介绍
- 数学建模:评价模型——主成分分析 PCA SPSS实现、python实现
- 在服务器群集节点安装DHCP服务
- Unity在NGUI中默认动态字体Arial字体显示不完整解决方案
- win10自带计算机应用恢复,win10重置电脑后怎么恢复应用_win10重置后恢复软件的方法...
- /deep/在chrome89+中出现样式混乱的问题
- destoon网站mysql分表_destoon二次开发常用数据库操作_PHP
- 《Java并发编程的艺术》——Java并发的前置知识(笔记)