一、前情回顾:在程序中有时候需要存放对象,容器应运而生。容器分为集合和Map。集合在这里不说,说说Map。Map在英语中是地图的意思,这个名字真是起的好,可以让人顾名思义。Map,就是存放键值对的结构。也就是说,只要找到键,就能找到对应的值,就跟查字典一样。
二、Map工作效率的深层原理:
1.上面说到查询map就是查询键,只要键找得到,值就会对应的找得到。所以怎么找到键,就是访问Map的效率的瓶颈所在。
2.那么如何找到键呢?其中一个好办法就是把键排序,然后按照二分法查找。二分法就不用介绍了吧?
3.散列(HashMap)则更进一步。他把键相关的信息保存在一个数组中。因为数组时访问速度最快的数据结构。这样可以大大提高效率。但是数组中存储的是键的相关信息,而不是键本身。因为Map的大小是不确定的,而数组的大小是确定的,所以问题来了,怎么把不确定size的Map的键的相关信息存储在数组中呢?
4.那就是数组并不保存键本身,只保存于键相关的信息,这个相关信息就是每一个键对象生成的数字,将其作为数组的下表,这个数字就是散列码,即hashCode()方法。
5.不同的键可以产生相同的散列码,对应于数组的相同下标。这样就解决了数组大小不变的问题。即:数组中的每一个具体的位置,可以根据Map的大小存储一个或多个Map基本元素,这样就会保证Map可以自由调节大小。Map小了,把数组填不满,Map大了,就往数组的同一个位置多填几个值。其数据结构如下图:
代码如下
package test; import java.util.Map; /** * @author 笑傲独行侠 * @description: * @Date: 2019/7/9 13:55 */ public class MapEntry<K, V> implements Map.Entry<K, V> { private K key; private V value; public MapEntry(K key, V value) { 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 oldValue = this.value; this.value = value; return oldValue; } }
package test; import java.util.*; /** * @author 笑傲独行侠 * @description: 理解HashMap的深层原理 * @Date: 2019/7/9 11:33 */ public class SimpleHashMap<K, V> extends AbstractMap<K, V> { static final int SIZE = 997;//定义数组的大小,尽量大一下,使得一个map尽量存入不同的下标数组中 LinkedList<MapEntry<K, V>>[] buckets = new LinkedList[SIZE];//定义一个数组,存放键的相关信息,数组中是List public V put(K key, V value) { V oldValue = null; //先计算key的hashCode值,再处理一下算出对应的数组下标 int index = Math.abs(key.hashCode()) % SIZE; //如果该下标对应的还没有值,就先new一个List if (buckets[index] == null) { buckets[index] = new LinkedList<MapEntry<K, V>>(); } //如果该下标已经有一个key存放在其中,就将当前key添加到该List中。 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)) { oldValue = iPair.getValue(); it.set(pair); found = true; break; } } //定义一个Boolean变量,如果要存入的键已经存在,则替换该键对应的值,如果不存在则添加 if (!found) buckets[index].add(pair); return oldValue; } @Override 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<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, String> m = new SimpleHashMap<>(); m.put("111", "huhhu"); m.put("111", "huhhu"); m.put("111", "huhhu"); m.put("111", "huhhu"); System.out.println(m); System.out.println(m.get("ERITREA")); System.out.println(m.entrySet()); } }