如何快速自己实现Map
第一步: 定义一个接口public interface MyMap { /** * 向集合中插入值 * * @param k * @param v * @return */ V put(K k, V v); /** * 根据Key 获取集合中的值 * * @param k * @return */ V get(K k); /** * 获取集合中元素个数 * * @return */ int size(); /** * 清空 */ void clear(); /** * 判断是否存在 某值 * * @param value * @return */ boolean containsValue(V value); /** * 判断是否存在 某键 * * @param key * @return */ boolean containsKey(K key); /** * 判断是否为空 * * @return */ boolean isEmpty(); /** * 用于获取集合中,键值对的对象 * * @param * @param */ interface Entry { /** * 获取 Key * * @return */ K getKey(); /** * 获取 Value * * @return */ V getValue(); /** * 设置 Value * * @param value * @return */ V setValue(V value); /** * 获取 next * * @return */ Entry getNext(); /** * 设置 next * * @param next * @return */ Entry setNext(Entry next); } }
对上述编写的接口以及内容进行说明:V put(K k, V v);V get(K k);int size();void clear();boolean containsValue(V value);boolean containsKey(K key);boolean isEmpty();interface Entry {}第二步 定义一个类实现 这个接口
首先定义这个类的成员变量/** * 用于存放 Entry 数据 */ private Entry[] table = null; /** * 记录HashMap 的元素个数 */ private int size; /** * HashMap 集合中数组的默认长度 2^4 位移算法 */ private static int defaultLength = 1 << 4; /** * 默认加载因子 0.75 */ private static double defaultLoad = 0.75;
V put(K k,V v) @Override public V put(K k, V v) { if (table == null) { table = new Entry[defaultLength]; } if (size >= defaultLength * defaultLoad) { resize(); } int index = getIndex(k, defaultLength); MyMap.Entry entry = table[index]; while (entry != null) { if (entry.getKey().equals(k)) { return entry.setValue(v); } else { entry = entry.getNext(); } } table[index] = new Entry<>(k, v, table[index]); this.size++; return v; }
执行过程:
第一步 判断该集合是否需要扩容;
第二步 计算位置 需要将该元素放置在哪里;
第三步 判断是否是修改(可能计算的位置已经有元素存在);
第四步 该位置可能已经形成链表(所以需要判断下一个元素)
第五步 创建 Entry 元素 并存放在 table 的 index 下标上。
int getIndex(K k,int length)private int getIndex(K k, int length) { // HashMap 是允许 null 值 和 null key if (k == null) { return 0; } // 计算的 hashCode码值 过大 如何处理 // 00000100 4 00000111 7 // 00000010 2 00000010 2 // 00000000 0 00000010 2 int hash = k.hashCode(); return hash & (length - 1); }
知识点 计算下标的方法一般有两种:
&(与)算法 ---- 与 得到的值一定不会大于这两个数的最小值 [0,最小值]
模算法 ---- 取模 后得到的值一定是比取模的数小 [0,取模数)
计算下标: hashCode 是根据对象的地址或者字符串的值计算得到的 int 类型的数值
特点: 同一对象多次调用hashCode()方法,必须返回相同的数值 [储存哪个位置 在哪个位置获取] 即调用多次 如果两个对象根据 equals() 方法比较是相等的,那么两个对象调用hashCode()方法返回的结果必须相等 如果两个对象根据equals() 方法比较是不相等的,那么两个对象调用hashCode()方法返回的结果不一定不相等
V get(K k)public V get(K k) { if (table != null) { int index = getIndex(k, defaultLength); MyMap.Entry entry = table[index]; while (entry != null) { if (entry.getKey() != null && entry.getKey().equals(k)) { return entry.getValue(); } else { entry = entry.getNext(); } } } return null; }
执行过程:
第一步:根据 getIndex(K k, int length) 查询下标 (注意此时获取的下标可能代表好多值)
第二步: 根据 index 获取 Entry
第三步: 循环判断 需要满足两个条件(1.entry 不为空 2.entry 的key等于该值) ;如果不满足则获取下一个 entry 继续判断
void clear()public void clear() { Entry[] tab; if ((tab = table) != null && size > 0) { size = 0; for (int i = 0; i < tab.length; ++i) { tab[i] = null; } } }
boolean containsValue(V value)public boolean containsValue(V value) { Entry[] tab = table; if (tab != null && size > 0) { for (MyMap.Entry e : tab) { for (; e != null; e = e.getNext()) { V v = e.getValue(); if (v == value) { return true; } else if (value != null && value.equals(v)) { return true; } } } } return false; }
执行过程:
第一步: 将类的成员变量 赋值给该方法中的成员变量
第二步: 判断该成员变量是否为空,若为空直接返回 false
第三步: 循环遍历 Entry ,再次遍历 Entry 链表中的所有元素
第四步: 需要考虑到 V 可能是 对象,所以不能简单的通过 == 来判断。
boolean containsKey(K key)public boolean containsKey(K key) { // 1.计算该key 对应的 hashCode int index = getIndex(key, defaultLength); return getEntry(index, key); } private boolean getEntry(int hash, K key) { Entry[] tab = table; if (tab != null) { MyMap.Entry entry = table[hash]; while (entry != null) { if (entry.getKey() != null && entry.getKey().equals(key)) { return true; } else { entry = entry.getNext(); } if (entry == key) { return true; } } } return false; }
执行过程:
第一步: 根据 key 计算出 对应的 hashCode getIndex(K k,int length);
第二步: 将类的成员变量 赋值给该方法中的成员变量;
第三步: 判空,如果为 null 直接返回 false;
第四步: 不为空,根据 hashCode 获取对应的 Entry,如果 Entry 不为空 循环判断
第五步: 如果 entry.getKey() 不为空 且 两者的 equals 比较相同 则返回 true;
否则 获取 entry.getNext(),继续判断;如果都不满足 则直接 判断 entry == key。(考虑到对象的存在)
boolean isEmpty()public boolean isEmpty() { Entry[] tab = table; return (tab == null) ? true : false; }
int size()public int size() { return this.size; }
void resize()
最重要的是扩容方法private void resize() { // 重新散列 if (size >= defaultLength * defaultLoad) { System.out.println("-----------------扩容开始----------------"); Entry[] newTable = new Entry[defaultLength << 1]; MyMap.Entry entry = null; for (int i = 0; i < table.length; i++) { entry = table[i]; // while 中的 五行代码每一行都是精髓 while (entry != null) { int index = getIndex(entry.getKey(), newTable.length); MyMap.Entry oldEntry = entry.getNext(); entry.setNext(newTable[index]); newTable[index] = (Entry) entry; entry = oldEntry; } } table = newTable; defaultLength = newTable.length; } }
执行过程:
第一步:判断当前的 size 是否大于 当前的默认的长度 * 默认因子
第二步:如果不大于,则什么都不用做
第三步:如果大于,就需要扩容:
a. 定义一个新的 table 用于临时存储 扩容的 Entry 对象
b. 定义一个临时变量 entry 用于存储每一个下标的 Entry 对象
c. 准备遍历 old 的 table [0,table.length)
d. 取出 table[i] 赋值给 b 步骤定义的 entry(这是一个链表)
e.while 循环 判断 entry 是否为空
以下步骤是操作链表(精华):拿到节点重新散列(拿到 entry 的 key,以及临时 table 的长度)计算新的下标entry 并不简单,需要处理 key,value以及 next 得有一个中间变量 相当于 a b的值互换 即 c = a,a = b,b = c内部类的实现
具有的成员变量:
K k;V value
MyMap.Entry next;(指针,指向下一个 Entry)。
通过 setNext()方法实现。static class Entry implements MyMap.Entry { K key; V value; MyMap.Entry next; /** * 拿到存放的位置后 , 只需要将 key / value 存放进来 * 首先得先有一个 key / value ---> 创建一个 key/value 对象 来 存储 * 对象如何去创建 我们有一个 Entry ,通过 Entry 的构造方法 来创建 * * @param key * @param value * @param next */ public Entry(K key, V value, MyMap.Entry next) { this.key = key; this.value = value; this.next = next; } @Override public final K getKey() { return this.key; } @Override public V getValue() { return this.value; } @Override public V setValue(V value) { V oldValue = this.value; this.value = value; return oldValue; } @Override public final String toString() { return key + "=" + value; } @Override public final boolean equals(Object o) { if (o == this) { return true; } if (o instanceof MyMap.Entry) { MyMap.Entry<?, ?> e = (MyMap.Entry<?, ?>) o; if (Objects.equals(key, e.getKey()) && Objects.equals(value, e.getValue())) return true; } return false; } @Override public final int hashCode() { return Objects.hashCode(key) ^ Objects.hashCode(value); } @Override public MyMap.Entry getNext() { return this.next; } @Override public MyMap.Entry setNext(MyMap.Entry next) { MyMap.Entry oldNext = next; this.next = next; return oldNext; } }
人工智能时代教师角色的危机与重构人工智能(ArtificialIntelligence,AI),是指通过软硬件的结合使机器具有一定程度的类人智慧思维和行为。20世纪40年代,图灵提出著名的图灵测试如果一台机器能够
助听器有什么品牌的,怎么选择?助听器品牌很多,有全进口的还有在国内组装的,唯一的区别就是进口的价格高,国内组装便宜一些,但是里面的芯片导线各种组装件都是进口的,选择适合自己的品牌首先要到当业的听力中心去测一下听
学生党,预算只有300元,能否推荐一款比较好的性价比高一点的蓝牙耳机?推荐你看一下魔浪家的,价格很亲民,而且总体使用感受很不错。漫步者Lollipods,活动价会到218元左右。推荐它是因为它性价比真的很高,毕竟是一家专门做音响设备的公司,在这个价位
谁能详细的解释一下比特币的价值到底是什么?作者这么问肯定是把比特币和货币画上等号了,或者潜意识里认为是差不多的东西。其实两者差的很远,比特币的概念最初由中本聪在2008年11月1日提出,并于2009年1月3日正式诞生。根据
华米科技Amazfit跃我GTR3Pro,正式上线血压筛查项目2021年11月22日,智能可穿戴和健康云服务公司华米科技(NYSEZEPP)宣布,旗下智能手表Amazfit跃我GTR3Pro正式上线血压筛查研究项目。该项目由国内知名三甲医院北
一枚核电池能用50年,为什么祝融号火星车不用核电池?祝融号是小型漫游车,载荷带不动核电池有一个常识上的误区,认为航天器的核电池是跟充电宝差不多储存电量,连跟线就可以使用。其实不然,航天器的核电池实际上是台小型热能发电机,放射性材料衰
为什么有那么强大的抖音在,还会存在一个快手?2011年程一笑创立快手。之后2013年,宿华加入!而抖音在2018年的时候才横空出世,抢下了短视频王者宝座!而在这之前短视频王者的宝座一直由快手占据!并且称霸了很多年!有人会说抖
列入失信人限高了怎么坐飞机高铁?着急出差怎么办?这个事着急不了,没个十天半个月是办不下来的,商业机会早就错过了。司法惩戒的目的就是锁住你的手脚,让你今后不能再创业经营,也不指望你还债了。今后主要靠吃低保靠子女过日子了,或者找昔日
营收年四五千万与营收年1亿以上企业财务管理有什么区别?营收年四五千万的企业(剔除单品价值过大的企业),月数据量相对较小,月出现的财税问题也相对较少,对于刚晋升的财务主管来说,花点时间能够应付与理顺。但是到了营收年1亿以上,企业财务数据
京东重新定义618不止于消费作为年度重量级营销节点,京东618年中大促正式拉开序幕。5月20日,在第18个京东618来临之际,以让热爱不止于消费为主题的2021京东618,18周年庆启动暨趋势发布会在北京举行
助听器有什么优点?盒式助听器外形类似微型收音机。麦克风放大器和电池都放在一个盒里,配戴在胸前的口袋内,由一条细小的电线把助听器与附加在耳模上的耳机连接起来。此种助听器因为体积较大,可制成大功率宽频响
桌面提醒自己工作内容的便签软件哪个好人们在工作的时候,有不同的工作任务,而在一天的工作任务当中,往往会有很多项工作要做。想要让自己的工作效率更高,可以将自己的工作内容记录下来,这样更有条理,执行的时候更清晰。桌面提醒
Java高频面试题每日三连问?Day1Redis篇作者浩说编程来源公众号浩说编程大厂技术资源研发必备安装包限时免费获取问题导读一你在哪些应用场景用到了Redis?二Redis都支持哪些数据类型?追问1在业务中如何选择要用的数据类型
浩抒己见(回复私信)有哪些表现让你一看就知道他是程序员?作者浩说编程来源公众号浩说编程大厂技术资源研发必备安装包经典必读电子书限时免费获取01丨万物皆错唯我独对程序员基本素质,不管如何做,怎么做,在对方的眼里都是错误的。别问为什么,问就
Java性能调优代码篇String内存优化作者浩说编程来源公众号浩说编程大厂技术资源研发必备安装包限时免费获取String在日常开发中的使用频率应该不需要我过多形容,大家闭着眼睛都能手写出来,但也正因如此,对于String
Java高频面试题每日三连问?Day4MyBatis篇作者浩说编程来源公众号浩说编程大厂技术资源研发必备安装包经典必读电子书限时免费获取问题导读一MyBatis中的和有哪些区别?二你常用的MyBatis标签有哪些?三说一下Hibern
数据结构与算法链表(Linkedlist)作者浩说编程来源公众号浩说编程大厂技术资源研发必备安装包限时免费获取数据结构与算法不管是在Java还是在任何语言中都是核心基础知识,就像是盖楼的地基一样,它被广泛的应用于架构的最底
并发编程进阶三深入理解锁机制作者浩说编程来源公众号浩说编程大厂技术资源研发必备安装包限时免费获取通过前两篇的内容我们了解了并发的潜在问题,以及解决部分潜在问题的方法。本篇我们继续探寻如何解决并发的原子性问题?
Java高频面试题每日三连问?Day3集合容器篇作者浩说编程来源公众号浩说编程大厂技术资源研发必备安装包经典必读电子书限时免费获取问题导读一说一下ListSetmap的区别吧二说一下VectorArrayListLinkedLi
Java高频面试题每日三连问?Day2Redis篇2作者浩说编程来源公众号浩说编程大厂技术资源研发必备安装包限时免费获取问题导读一你对Redis的持久化了解吗?追问1能否说一下Redis持久化有几种方式?二聊一下Redis的内存淘汰
日常开发踩坑前端数据并不可信作者浩说编程来源公众号浩说编程大厂技术资源研发必备安装包限时免费获取在我们的日常业务开发中,和前端的数据交互是必不可少的,通常前端会将参数以json格式发送至后端进行逻辑处理,但是
Java高频面试题每日三连问?Day5MyBatis篇2作者浩说编程来源公众号浩说编程大厂技术资源研发必备安装包经典必读电子书限时免费获取问题导读一了解MyBatis的一级缓存二级缓存吗?追问1如何配置二级缓存?追问2一级缓存和二级缓存