文章目录
一.前言
在Java中,集合分成三大类,Set、List、Map。其中Set和Map均为Collection接口的子接口。Map是以键值对的结果来存储数据的,将键映射到值的对象。映射不能包含重复的键;每个键最多只能映射到一个值。
public interface Map<K,V>
这个接口取代了Dictionary类,Dictionary是一个完全抽象的类而不是接口。
二.Map源码及常用方法
先来看看Map接口的源码
public interface Map<K,V> {
// Query Operations 查询操作
int size();
boolean isEmpty();
boolean containsKey(Object key);
boolean containsValue(Object value);
V get(Object key);
// Modification Operations 修改操作
V put(K key, V value);
V remove(Object key);
// Bulk Operations 批量操作
//将所有映射从指定映射复制到此映射
void putAll(Map<? extends K, ? extends V> m);
//删除所有映射
void clear();
// Views 视图
//所有key的集合,不允许重复
Set<K> keySet();
//所有value的集合
Collection<V> values();
//所有键值对的集合,不允许重复
Set<Map.Entry<K, V>> entrySet();
interface Entry<K,V> {
K getKey();
V getValue();
V setValue(V value);
boolean equals(Object o);
int hashCode();
/**
* @since 1.8 返回一个比较器,比较的规则是key的自然大小
*/
public static <K extends Comparable<? super K>, V> Comparator<Map.Entry<K,V>> comparingByKey() {
return (Comparator<Map.Entry<K, V>> & Serializable)
(c1, c2) -> c1.getKey().compareTo(c2.getKey());
}
/**
* @since 1.8 返回一个比较器,比较规则是value的自然大小
*/
public static <K, V extends Comparable<? super V>> Comparator<Map.Entry<K,V>> comparingByValue() {
return (Comparator<Map.Entry<K, V>> & Serializable)
(c1, c2) -> c1.getValue().compareTo(c2.getValue());
}
/**
* @since 1.8 返回一个比较器,比较规则用cmp参数传入,比较的是key
*/
public static <K, V> Comparator<Map.Entry<K, V>> comparingByKey(Comparator<? super K> cmp) {
Objects.requireNonNull(cmp);
return (Comparator<Map.Entry<K, V>> & Serializable)
(c1, c2) -> cmp.compare(c1.getKey(), c2.getKey());
}
/**
* @since 1.8 返回一个比较器,比较规则用cmp参数传入,比较的是value
*/
public static <K, V> Comparator<Map.Entry<K, V>> comparingByValue(Comparator<? super V> cmp) {
Objects.requireNonNull(cmp);
return (Comparator<Map.Entry<K, V>> & Serializable)
(c1, c2) -> cmp.compare(c1.getValue(), c2.getValue());
}
}
// Comparison and hashing 比较和散列
int hashCode();
// Defaultable methods @since 1.8
default V getOrDefault(Object key, V defaultValue) {
V v;
return (((v = get(key)) != null) || containsKey(key)) ? v : defaultValue;
}
default void forEach(BiConsumer<? super K, ? super V> action) {
Objects.requireNonNull(action);
for (Map.Entry<K, V> entry : entrySet()) {
K k;
V v;
try {
k = entry.getKey();
v = entry.getValue();
} catch(IllegalStateException ise) {
// this usually means the entry is no longer in the map. 这通常意味着entry不再在映射中
throw new ConcurrentModificationException(ise);
}
action.accept(k, v);
}
}
default void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
Objects.requireNonNull(function);
for (Map.Entry<K, V> entry : entrySet()) {
K k;
V v;
try {
k = entry.getKey();
v = entry.getValue();
} catch(IllegalStateException ise) {
throw new ConcurrentModificationException(ise);
}
// ise thrown from function is not a cme.从函数抛出的ise不是cme
v = function.apply(k, v);
try {
entry.setValue(v);
} catch(IllegalStateException ise) {
throw new ConcurrentModificationException(ise);
}
}
}
default V putIfAbsent(K key, V value) {
V v = get(key);
if (v == null) {
v = put(key, value);
}
return v;
}
default boolean remove(Object key, Object value) {
Object curValue = get(key);
if (!Objects.equals(curValue, value) ||
(curValue == null && !containsKey(key))) {
return false;
}
remove(key);
return true;
}
default boolean replace(K key, V oldValue, V newValue) {
Object curValue = get(key);
if (!Objects.equals(curValue, oldValue) ||
(curValue == null && !containsKey(key))) {
return false;
}
put(key, newValue);
return true;
}
default V replace(K key, V value) {
V curValue;
if (((curValue = get(key)) != null) || containsKey(key)) {
curValue = put(key, value);
}
return curValue;
}
default V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) {
Objects.requireNonNull(mappingFunction);
V v;
if ((v = get(key)) == null) {
V newValue;
if ((newValue = mappingFunction.apply(key)) != null) {
put(key, newValue);
return newValue;
}
}
return v;
}
default V computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
Objects.requireNonNull(remappingFunction);
V oldValue;
if ((oldValue = get(key)) != null) {
V newValue = remappingFunction.apply(key, oldValue);
if (newValue != null) {
put(key, newValue);
return newValue;
} else {
remove(key);
return null;
}
} else {
return null;
}
}
default V compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
Objects.requireNonNull(remappingFunction);
V oldValue = get(key);
V newValue = remappingFunction.apply(key, oldValue);
if (newValue == null) {
// delete mapping
if (oldValue != null || containsKey(key)) {
// something to remove
remove(key);
return null;
} else {
// nothing to do. Leave things as they were.
return null;
}
} else {
// add or replace old mapping
put(key, newValue);
return newValue;
}
}
default V merge(K key, V value,BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
Objects.requireNonNull(remappingFunction);
Objects.requireNonNull(value);
V oldValue = get(key);
V newValue = (oldValue == null) ? value :
remappingFunction.apply(oldValue, value);
if(newValue == null) {
remove(key);
} else {
put(key, newValue);
}
return newValue;
}
}
————————————————
版权声明:本文为CSDN博主「Mia_li」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/sinat_23324343/article/details/88019235
三.HashMap特性及源码
3.1 HashMap需要掌握的几点
- HashMap的成员变量及作用
- Map的创建:HashMap()
- 往Map中添加键值对:即put(Object key, Object value)方法
- 获取Map中的单个对象:即get(Object key)方法
- 删除Map中的对象:即remove(Object key)方法
- 判断对象是否存在于Map中:containsKey(Object key)
- 遍历Map中的对象:即keySet(),在实际中更常用的是增强型的for循环去做遍历
- Map中对象的排序:主要取决于所采取的排序算法
- HashMap死循环
3.2 HashMap概述
在JDK1.8之前,HashMap使用数组+链表实现,即使用链表处理冲突,同一hash值的节点都存储在一个链表里。但是当位于一个桶中的元素较多,即hash值相等的元素较多时,通过key值依次查找的效率较低。而JDK1.8中,HashMap采用数组+链表+红黑树实现,当链表长度超过阈值(8)时,将链表转换为红黑树,这样大大减少了查找时间。
下图中代表jdk1.8之前的hashmap结构,左边部分即代表哈希表,也称为哈希数组,数组的每个元素都是一个单链表的头节点,链表是用来解决冲突的,如果不同的key映射到了数组的同一位置处,就将其放入单链表中。
jdk1.8之前的hashmap都采用上图的结构,都是基于一个数组和多个单链表,hash值冲突的时候,就将对应节点以链表的形式存储。如果在一个链表中查找其中一个节点时,将会花费O(n)的查找时间,会有很大的性能损失。到了jdk1.8,当同一个hash值的节点数不小于8时,不再采用单链表形式存储,而是采用红黑树,如下图所示:
说明:上图很形象的展示了HashMap的数据结构(数组+链表+红黑树),桶中的结构可能是链表,也可能是红黑树,红黑树的引入是为了提高效率。
3.2.1 HashMap结构中的链表
Node是HashMap的一个内部类,实现了Map.Entry接口,本质上是一个映射(键值对)。上图中每一个黑圆点就是一个Node对象。来看具体代码:
static class Node<k,v> implements Map.Entry<k,v>{
final int hash;
final K key;
V value;
Node<k,v> next;
//构造函数Hash值 键 值 下一个节点
Node(int hash,K key, V value, Node<k,v> next){
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
public final K getKey() { return key; }
public final V getValue() { return value; }
public final String toString() { return key + = + value; }
public final int hashCode() {
return Objects.hashCode(key) ^ Objects.hashCode(value);
}
public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}
//判断两个node是否相等,若key和value都相等,返回true。可以与自身比较为true
public final boolean equals(Object o){
if (o == this)
return true;
if (o isinstanceof Map.Entry){
Map.Entry<!--?,?--> e = (Map.Entry<!--?,?-->) o;
if (Object.equals(key,e.getKey())&&
Objects.equals(value,e.getValue()))
return true;
}
return false;
}
}
可以看到,node中包含一个next变量,这个就是链表的关键点,hash结果相同的元素就是通过这个next进行关联的。
3.2.2 HashMap结构中的红黑树
//红黑树
static final class TreeNode<k,v> extends LinkedHashMap.Entry<k,v>{
TreeNode<k,v> parent; //父节点
TreeNode<k,v> left; //左子树
TreeNode<k,v> right; //右子树
TreeNode<k,v> prev; //needed to unlink next upon deletion
boolean red; //颜色属性
TreeNode(int hash,K key, V val, Node<k,v> next){
super(hash, key, val, next);
}
//返回当前节点的根节点
final TreeNode<k,v> root(){
for(TreeNode<k,v> r =this, p;;){
if((p = r.parent) == null)
return r;
r = p;
}
}
}
红黑树比链表多了四个变量,parent父节点、left左节点、right右节点、prev上一个同级节点,红黑树内容较多,不在赘述。
3.2.3 HashMap数据结构之位桶
transient Node<k,v>[] table;//存储(位桶)的数组
HashMap类中有一个非常重要的字段,就是 Node[] table,即哈希桶数组,明显它是一个Node的数组。
有了以上3个数据结构,只要有一点数据结构基础的人,都可以大致联想到HashMap的实现了。首先有一个每个元素都是链表(可能表述不准确)的数组,当添加一个元素(key-value)时,就首先计算元素key的hash值,以此确定插入数组中的位置,但是可能存在同一hash值的元素已经被放在数组同一位置了,这时就添加到同一hash值的元素的后面,他们在数组的同一位置,但是形成了链表,所以说数组存放的是链表。而当链表长度太长时,链表就转换为红黑树,这样大大提高了查找的效率。
四.小结
在jdk1.8中,HashMap的特色为转变成了数组(位桶+链表+红黑树),笔者水平有限,许多方面介绍不足,关于链表和红黑树的相互转化及其他HashMap的实现细节,可以参考HashMap源码解析。