map接口
哈希表就是一种以键-值(key-indexed) 存储数据的结构,只要输入待查找的值
- 即key,即可查找到其对应的值。
- 哈希的思路很简单,如果所有的键hashCode都是整数,那么就可以使用一个简单
- 的无序数组来实现:将键作为索引,值即为其对应的值,这样就可以快速访问任
- 意键的值。
- 它提供了一组键值的映射。其中存储的每个数据对象都有一个相应的键key,键决
- 定了值对象在Map中的存储位置。键应该是唯一的,不允许重复,每个key只能映
- 射一个value。
哈希冲突
- 当两个不同的输入值,根据同一散列函数计算出相同的散列值的现象,就把它叫做碰撞(哈希碰撞)。哈希
- 碰撞的解决方案为开放地址法和链地址法。
- 以key/value的方式存储数据,采用拉链法综合了数组和链表结构。如果key已知则存取效率较高,但是删除
- 慢,如果不知道key存取则慢,对存储空间使用不充分。最典型的实现是HashMap。
- JDK8+对桶内数据处理从链表转换为红黑树,则查询效率从O(N)提高到O(logN)。当链表中的个数超过8个时
- 会转换为红黑树
Map接口的定义
Map主要用于存储键key值value对,根据键得到值,因此键不允许重复,但允许值重复。
Map接口的实现类主要有:HashMap、TreeMap、Hashtable、ConcurrentHashMap以及Properties等
ConcurrentHashMap可以理解为HashMap的线程安全版
- public interface Map
- 表示存储数据时有2个部分key和value,key不允许重复
- int size(); 获取key-value个数
- boolean containsKey(Object key);判断集合中是否包含指定的key值
- 要求key类型需要实现equals和hashCode发方法
- 首先调用hashCode方法获取key对应的hash值,然后当key值和map中的
- key值相同时才会继续调用equals进行等值判断,否则是false
- boolean containsValue(Object value);判断集合中是否包含指定的value值
- 要求value类型需要实现equals,对于hashCode没有要求;进行判断时是直接
遍历map集合有三种方式,map提供了三种不同的视图
- 1、Iterator 2、.forEach(lambda) 3、for-each结构 for(Object tmp: map.keySet)
- Set keySet();获取所有的key所组成的Set集合
- Collection values();获取所有的value所组成的Collection集合
- Set> entrySet();获取所有的key-value对所构成的Set集合
- 在Map中一个key-value被封装成一个Entry的对象
HahMap
HashMap是一个最常用的Map,它根据键的HashCode值存储数据,根据键可以直接获取它的值,具有很快的访问速度。
* HashMap最多只允许一条记录的键为null;允许多条记录的值为null
* HashMap不支持线程的同步,即任一时刻可以有多个线程同时写HashMap;可能会导致数据的不一致,在JDK1.7中
* 会出现环形链,虽然JDK1.8不会出现环形链,但是还会有rehash操作出现死循环、脏读问题、size值不准确等问题。
* 如果需要同步,可以用Collections的synchronizedMap方法使HashMap具有同步的能力。
HashMap底层使用数组+链表+红黑树实现,默认初始化大小Defalut_capcity是1
// 当我们放置元素到HashMap中,创建的HashMap底层数组在一开始是没有初始化的,通过hash()这个方法,算出来目标hash值后,放置这个内容到数组中,但是数组当前没有初始化,所以调用resize方法来初始化数组,初始化后,在放置内容时,直接调用newNode方法来将数据放置在数组的桶中。
// 当元素发生Hash碰撞,HashMap使用拉链法来解决,将碰撞的元素,插在之前桶中的最后一个元素的后面。这样就形成了链表。
// 当单个链表的元素个数超过8时,可能会发生树化过程。这个过程不一定发生,因为如果当前数组的长度没有到64(HashMap定义的最小书画长度)那么就会先扩容数组,当扩容数组的时候,会产生rehash过程,可能就降低了链表的长度,也就使长度不到8了。但是如果当前数组长度已经达到64,那么就会将当前链表进化成红黑树。
HashMap是使用了哪些方法来有效解决哈希冲突的:
- 使用链地址法(使用散列表)来链接拥有相同hash值的数据;
- 使用2次扰动函数(hash函数)来降低哈希冲突的概率,使得数据分布更平均;
- 引入红黑树进一步降低遍历的时间复杂度,使得遍历更快
容量、负载因子和树化 因为容量和负载因子决定了可用的桶的数量,空桶太多了会浪费空间,如果使用太满则会严重影响操作的性能。 扩容发生条件为【负载因子*容量 < 元素数量】,所以预先设置的容量需要满足大于【预估元素数量/负载因子】 ,同时它是大于等于预先设置的容量的2的幂数 - 如果没有特殊需求不要轻易更改,JDK自带默认负载因子是适用于通用场景需求的 - 如果确实需要修改建议不要超过0.75,因为会显著增加冲突,降低hashmap性能 - 如果使用太小的负载因子,否则可能会导致更加频繁的扩容,增加性能开销,本身访问性能也会受到影响 如果容量小于64只会进行简单扩容,如果容量大于64则会进行树化改造。树化处理可以避免哈希碰撞攻击 扩容处理 - 在jdk1.8中,resize方法是在hashmap中的键值对大于阀值时或者初始化时,就调用resize方法进行扩容 - 每次扩展的时候,都是扩展2倍 - 扩展后Node对象的位置要么在原位置,要么移动到原偏移量两倍的位置。 在putVal()中看到在这个函数里面使用到了2次resize()方法,resize()方法表示的在进行第一次初始化时 会对其进行扩容,或者当该数组的实际大小大于其临界值,这个时候在扩容的同时也会伴随的桶上面的元素 进行重新分发,这也是JDK1.8版本的一个优化的地方,在1.7中,扩容之后需要重新去计算其Hash值,根据 Hash值对其进行分发,但在1.8版本中,则是根据在同一个桶的位置中进行判断(e.hash & oldCap)是否为 0,重新进行hash分配后,该元素的位置要么停留在原始位置,要么移动到原始位置+增加的数组大小这个位 置上
Hashtable
线程安全的,不允许null的键或值;是线程安全的但是Hashtable线程安全的策略实现代
- 价却太大了,简单粗暴,get/put所有相关操作都是synchronized的,这相当于给整个哈
- 希表加了一把大锁。多线程访问时候,只要有一个线程访问或操作该对象,那其他线程只
- 能阻塞,相当于将所有的操作串行化,在竞争激烈的并发场景中性能就会非常差
- 由于线程安全,则在非多线程环境下不建议使用
Hashtable的函数都是同步的,这意味着它是线程安全的。它的key、value都不可
- 以为null。此外,Hashtable中的映射不是有序的;在hashmap中允许key和value
- 为null,只是key只能有一个null,如果出现冲突后盖前;如果使用null的key和value
- 则会出现一个运行时异常NullPonterException
TreeMap
TreeMap是一个有序的key-value集合,它是通过红黑树实现的。该映射根据其键的自然顺序进行排序
[Comparable或者Comparator]或者根据创建映射时提供的 Comparator 进行排序,具体取决于使用
的构造方法,底层实现和hash表无关。
TreeMap 继承于AbstractMap,所以它是一个Map,即一个key-value集合。
TreeMap 实现了NavigableMap接口,意味着它支持一系列的导航方法。比如返回有序的key集合。
TreeMap 实现了Cloneable接口,意味着它能被克隆。
TreeMap 实现了java.io.Serializable接口,意味着它支持序列化。
常用方法
- put(K key, V value)用于添加一个Entry(结点,包括key和value)到TreeMap对象,
key为与指定值将要关联的键,value为使用指定键关联的值。如果key对应的值已经存在,
则将key对应的值修改为value
- V get(Object key)方法用于获取指定key的value,key为与value相关联的键
Properties