Java基础-Map集合之HashMap


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}

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值