Map接口的概述
将键映射到值的对象
一个映射不能包含重复的键
每个键最多只能映射到一个值
Map接口和Collection接口的不同
Map是双列的,Collection是单列的
Map的键唯一,Collection的子体系Set是唯一的
Map集合的数据结构值针对键有效,跟值无关;Collection集合的数据结构是针对元素有效
Map集合的遍历
1.通过键找值
public static void main(String[] args) {
HashMap<String, Integer> hashMap = new HashMap<>();
hashMap.put("张三", 23);
hashMap.put("李四", 24);
hashMap.put("王五", 25);
hashMap.put("赵六", 26);
Set<String> keySet = hashMap.keySet(); //获取所有key的集合
Iterator<String> iterator = keySet.iterator(); //获取keySet的迭代器
while(iterator.hasNext()) {
String key = iterator.next();//遍历key,根据key查找value
System.out.print(key + "=" + hashMap.get(key) + " ");
}
//以下是简写版
for (String key : hashMap.keySet()) {
System.out.print(key + "=" + hashMap.get(key) + " ");
}
}
//outPut:李四=24 张三=23 王五=25 赵六=26 (相同省略一处)
2.通过键值对对象找键和值
import java.util.Map.Entry;
public static void main(String[] args) {
HashMap<String, Integer> hashMap = new HashMap<>();
hashMap.put("张三", 23);
hashMap.put("李四", 24);
hashMap.put("王五", 25);
hashMap.put("赵六", 26);
Set<Entry<String, Integer>> entrySet = hashMap.entrySet(); //获取所有键值对对象的集合
Iterator<Entry<String, Integer>> iterator = entrySet.iterator(); //获取entrySet的迭代器
while(iterator.hasNext()) {
Entry<String, Integer> entry = iterator.next();//遍历entry,根据key查找key和value
System.out.print(entry.getKey() + "=" + entry.getValue() + " ");
}
//以下是简写版
for (Entry<String, Integer> entry : hashMap.entrySet()) {
System.out.print(entry.getKey() + "=" + entry.getValue() + " ");
}
}
//outPut:李四=24 张三=23 王五=25 赵六=26 (相同省略一处)
通过两处代码的比较,可以发现其实两处代码只是其类型不一致,其主要代码框架可以说是一样的。一个是String类型(Key),一个是Entry<String, Integer>类型(键值对对象)。
来简单打个比方,一个人有姓名和年龄两个属性,第一种方式是我们可以把所有人的姓名找出来,再根据姓名查找对应的年龄。而第二种方式是我们创建一个类(Person),Person里面就封装了姓名和属性,我们只需把所有Person对象找出来,通过该对象查找对应的姓名和年龄。
现在,我们来看看HashMap里面的这个Entry(JDK1.8)
//存储键值对
static class Node<K,V> implements Map.Entry<K,V> {
final int hash; //key的value
final K key; //key
V value; //value
Node<K,V> next; //下一个Entry
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; }
//省略部分源码
}
为什么里面还要存放 Node<K,V> next 呢?这样应该会形成链表吧?下面对其HashMap进行讲解
HashMap
hashMap其数据结构是通过哈希表来实现的。
为什么会有哈希表这东西,哈希表又是什么?
先来讲讲数组和链表
数组的特点:寻址快,插入和删除困难
链表的特点:寻址慢,插入和删除容易
还有更好的数据结构吗?
当然有,那就是哈希表。哈希表综合了上述两种数据结构,其实现的方法有多种,下面介绍最常见的一种方法——拉链法,简单理解为拉链数组
//一个Entry的数组,构建哈希表
transient Node<K,V>[] table;
现在应该知道了 Node<K,V> next这个是干什么用的吧,就是为了形成一条链表。
对数据结构大致了解后,我们该如何存储呢?
数据存储说明
元素是如何存储的呢?table内部存储的为Entry对象
假设如下代码:
//以下假设为key的哈希码,方便起见(取小一点的数),5为table的size
hashMap.put("张三", 23); //23 % 5 = 3
hashMap.put("李四", 24); //36 % 5 = 1
hashMap.put("王五", 25); //41 % 5 = 1
hashMap.put("张三", 25); //23 % 5 = 3
第一次put,发现其table[3] = null ,所以直接创建了一个Entry对象(存储key和value一些信息),并将其地址值赋值给table[3]
//newNode为一个方法,其内部创建了一个Node对象(实现了Entry接口)
tab[3] = newNode(hash, key, value, null);//null值为next
第二次put也是如此,那么现在的存储图为:
第三次put的时候问题来了,table[1]已经不为空了,那怎么办呢?(hash冲突,这里使用链地址法解决) 那么现在就是equals()该上场了,如图,他会在该列上依次使用key.equal()进行比较,比较到末尾时还未发现,那么就加入到该列末尾。拿图说话,很明显,该列只有1个对象,而其key明显不同(李四&王五)
第四次put的时候,按照上一次的步骤,可是与上一次不同的是,这次equals比较有相同的了(张三&张三),那怎么办呢?很简单,直接将其值替换了即可
综合对其源码简单分析(JDK1.8)
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null) //这里是第一种情况,table[i]为null
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {//循环该列查找
if ((e = p.next) == null) {//这是第二种情况,将Entry直接添加到末尾
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;//退出循环
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))//这是第三种情况,有相同的key,中途退出
break;
p = e;//这一步不可少,进行下一次next循环
}
}
if (e != null) { // existing mapping for key//如果e不为null,说明出现了第三种情况
V oldValue = e.value;//记录旧值
if (!onlyIfAbsent || oldValue == null)
e.value = value;//替换新值
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
简单实现一个HashMap
Entry类:
public class MapEntry<K, V> implements Map.Entry<K, V> {
private K key;
private V value;
public MapEntry(K key, V value) {
super();
this.key = key;
this.value = value;
}
@Override
public K getKey() {
return key;
}
@Override
public V getValue() {
return value;
}
@Override
public V setValue(V value) {
V result = this.value;
this.value = value;
return result;
}
@Override
public int hashCode() {
return (key == null ? 0 : key.hashCode())^(value == null ? 0 : value.hashCode());
}
@Override
public boolean equals(Object obj) {
if(!(obj instanceof MapEntry))
return false;
MapEntry me = (MapEntry)obj;
return
(key == null ? me.getKey() == null : key.equals(me.getKey())) &&
(value == null ? me.getValue() == null : value.equals(me.getValue()));
}
@Override
public String toString() {
return key + "=" + value;
}
}
简单map类:
public class SimpleHashMap<K, V> extends AbstractMap<K, V> {
static final int SIZE = 997;//散列表大小
LinkedList<MapEntry<K, V>>[] buckets = new LinkedList[SIZE];//构建链表数组:哈希表
public V put(K key,V value) {
V oldvalue = null;
int index = Math.abs(key.hashCode()) % SIZE;//获取散列码
if(buckets[index] == null) {
buckets[index] = new LinkedList<>();
}
LinkedList<MapEntry<K, V>> bucket = buckets[index];
MapEntry<K, V> pair = new MapEntry<K, V>(key, value);
boolean found = false;
ListIterator<MapEntry<K, V>> it = bucket.listIterator();
while(it.hasNext()) {//对链表进行迭代
MapEntry<K, V> iPair = it.next();
if(iPair.getKey().equals(key)) {//根据equals方法对key进行比较
oldvalue = iPair.getValue();//链表中已存在则更新
it.set(pair);
found = true;
break;
}
}
if(!found) {//链表中没有发现则插入
buckets[index].add(pair);
}
return oldvalue;
}
public V get(Object key) {
int index = Math.abs(key.hashCode()) % SIZE;
if(buckets[index] == null) {
return null;
}
for(MapEntry<K, V> iPair : buckets[index]) {
if(iPair.getKey().equals(key)) {
return iPair.getValue();
}
}
return null;
}
@Override
public Set<java.util.Map.Entry<K, V>> entrySet() {
Set<Map.Entry<K, V>> set = new HashSet<>();
for(LinkedList<MapEntry<K, V>> bucket : buckets) {//对数组进行外循环
if(bucket == null)
continue;
for(MapEntry<K, V> mpair : bucket) {//对数组中存放的链表进行内循环
set.add(mpair);
}
}
return set;
}
public static void main(String[] args) {
SimpleHashMap<String, Integer> map = new SimpleHashMap<>();
map.put("张三", 23);
map.put("李四", 24);
map.put("王五", 25);
map.put("张三", 25);
System.out.println(map);
}
}
//outPut:{李四=24, 张三=25, 王五=25}