目录
一、基于 hash 算法、单向链表、数组实现 HashMap
一、基于 hash 算法、单向链表、数组实现 HashMap
说明:使用hash算法实现hashMap要注意发生hash冲突,为了解决这个问题,因此使用了单向链表对集合中的键值对进行存储。
对上述图片进行讲解:entyr 即一个黄色小框框,为数组中一个对象,代表hashMap集合中的键值对元素;0,1,2...1000则为该数据的链表;“key=2”和“key=4”为两个键值对对象,这里假设“key=2”以及“key=4”的两个对象的hashCode值相等,由于“key=2”的键值对先存进数组中,因此“key=4”的键值对通过单向链表的方式追加到“key=2”的键值对后面。那么为什么会出现链表的方式?原因是通过hash算法会存在hash冲突的问题,本文的hash算法为:存放地址 = key的hash值 取余 数组的长度。代码意思就是:index = key.hashCode() % entries.length,因此若两个键值对对象的key的hash值相同,则存放的位置肯定是相同,因此通过链表的方式解决这一hash冲突。
本文实现了HashMap的重要的方法以及特点,如:只能存放唯一个key值,并且key值以及Value值均可以存放自定义对象,并且解决了hash冲突。但当存放自定义对象作为key值的时候,需要重写该对象的equals()方法以及hashCode()方法。
hash冲突:根据key(键)即经过一个函数f(key)得到的结果的作为地址去存放当前的key value键值对(这个是hashmap的存值方式),但是却发现算出来的地址上已经被占用了。这就是所谓的hash冲突。而本文的函数 f 的实现大概就是:存放地址 = key的hash值 取余 数组的长度。代码意思就是:index = key.hashCode() % entries.length
注:本文还存在一个问题,本文存储集合中的键值对的主要方式是通过数组的方式,而为了提高查询效率,有时候增加数组的长度是非常必要的,需要用到数组内容的扩容机制,后续会慢慢更新,读者也可以自行的去解决
1.1 MyHashMap类
package com.mingfa.work01;
import java.util.ArrayList;
import java.util.Map;
/**
* 基于 hash 算法、单向链表、数组实现 HashMap
* @param <K>
* @param <V>
*/
public class MyHashMap<K,V> {
//Entry为存放集合的键值对,即 <key,value>
private Entry[] entries = new Entry[10];//假设数组容量为10,但是实际上是可以动态修改,因为存在扩容机制
/**
* 为集合添加数据
* @param k key 值
* @param v value 值
*/
public void put(K k,V v){
/**
* 1、根据 key 的 hashcode 取余 entrys.length , 余数为该 key 存放在我们数组中对应的 index 位置
* 2、判断该位置是否存在键值对
* 3、如果是,则查看该结点以及相应的链表是否存在以 k 为 key 的值
* 4、如果没有,则在最后面插入
*/
int hash = Math.abs(k.hashCode());//去 key 值的hash值,注意这里需要确保为整数,因为 hash 值是用来觉得该键值对存放在数据的位置,而数组的索引必须大于等于0
int index = hash % entries.length;//这是本文的 hash 算法,根据key的哈希值决定键值对的存放索引
Entry oldEntry = entries[index];//取该索引的键值对
//看当前数组节点是否为空
if (oldEntry == null) {//若当前索引不存在键值对
entries[index] = new Entry<>(k,v,hash);//则将该键值对插入
return;
}
//若不为空,则遍历链表
for(;;){
//查看链表(包括首结点)是否存在当前key值,若是,则更换 value 值
if (oldEntry.getKey().equals(k)){
oldEntry.setValue(v);
return;
}
//若不存在,则查找该结点的下一个结点
if (oldEntry.getNext() == null){//若下一个结点为空,则追加到链结点尾部
oldEntry.setNext(new Entry(k,v,hash));
return;
}else{//否则取下一个结点,继续循环
oldEntry = oldEntry.getNext();
}
}
}
/**
* 根据 key 取 value
* @param k key 值
* @return value 值
*/
public V get(K k){
/**
* 1、通过 hash 算法找到数组中的链表首结点
* 2、通过for循环查找 key 为 k 的 value
* 注:如果是存放自定义对象作为 key 值,这里需要注意 hashCode() 以及 equals() 方法的重写
*/
//根据 hash 算法先获取 entries 数组的某个位置
int hash = Math.abs(k.hashCode());
int index = hash % entries.length;
//判断该位置上是否存在键值对,如果是,则遍历以该结点为头的链表,从而寻找 key 为 k 的 value
for (Entry<K,V> entry = entries[index];entry != null;entry = entry.getNext()){
if (entry.getHash() == hash && (entry.getKey() == k || entry.getKey().equals(k)))
return entry.getValue();
}
return null;
}
/**
* 获取集合大小
* @return 集合大小
*/
public int size(){
int count = 0;//记录集合大小
Entry entry = null;//用来遍历键值对
//以数组的长度为循环结束条件
for (int i = 0;i<entries.length;i++){
if (entries[i] != null){
entry = entries[i];
count++;//如果当前有对象,则集合大小+1
//遍历以该结点为首结点的链表
while (entry.getNext() != null){
count++;//当下一个结点不为空时,则+1
entry = entry.getNext();
}
}
}
return count;
}
/**
* 通过 ArrayList 取集合中全部的 value 值
* @return 集合中全部的 value 值
*/
public ArrayList arrayListGetValues(){
ArrayList arrayList = new ArrayList();//存放结果
Entry entry = null;//用来遍历键值对
//以数组的长度为循环结束条件
for (int i = 0;i < entries.length;i++){
if (entries[i] != null){
arrayList.add(entries[i].getValue());//如果结点不为空,则将该结点的 value 值加入到列表中
entry = entries[i];//取该结点
//死循环,遍历以该结点为首结点的链表
for (;;){
if (entry.getNext() != null){//如果该结点存在下一个结点,则将下一个结点存放在列表
arrayList.add(entry.getNext().getValue());
entry = entry.getNext();
}else break;//遍历完整条链表,退出死循环
}
}
}
return arrayList;
}
/**
* 通过 ArrayList 取集合中全部的 key 值
* @return 集合中全部的 key 值
*/
public ArrayList arrayListGetKeys(){
//该方法实现逻辑同上
ArrayList arrayList = new ArrayList();
Entry entry = null;
for (int i = 0;i < entries.length;i++){
if (entries[i] != null){
arrayList.add(entries[i].getKey());
entry = entries[i];
for (;;){
if (entry.getNext() != null){
arrayList.add(entry.getNext().getKey());
entry = entry.getNext();
}else break;
}
}
}
return arrayList;
}
/**
* 通过 ArrayList 取集合中的全部键值对
* @return 集合中的全部键值对
*/
public ArrayList<Entry> arrayListGetKeyAndValue(){
//该方法实现逻辑同上
ArrayList<Entry> arrayList = new ArrayList();
Entry entry = null;
for (int i = 0;i < entries.length;i++){
if (entries[i] != null){
entry = entries[i];
arrayList.add(new Entry(entry.getKey(),entry.getValue()));
for (;;){
if (entry.getNext() != null){
entry = entry.getNext();
arrayList.add(new Entry(entry.getKey(),entry.getValue()));
}else break;
}
}
}
return arrayList;
}
}
1.2 Entry类
存放集合中的键值对,一个Entry对象为一个键值对 <key,value>
package com.mingfa.work01;
/**
* 存放集合中的 键值对
* @param <K>
* @param <V>
*/
public class Entry<K,V> {
private K key;//存放 key 值
private V value;//存放 value 值
private int hash;// 存放 hashCode 值
public Entry<K,V> next;//存放集合元素,解决hash冲突
public Entry(K k, V v, int hash) {
this.key = k;
this.value = v;
this.hash = hash;
}
public Entry(K k,V v){
this.key = k;
this.value = v;
}
public K getKey() {
return key;
}
public void setKey(K key) {
this.key = key;
}
public V getValue() {
return value;
}
public void setValue(V value) {
this.value = value;
}
public int getHash() {
return hash;
}
public void setHash(int hash) {
this.hash = hash;
}
public Entry<K, V> getNext() {
return next;
}
public void setNext(Entry<K, V> next) {
this.next = next;
}
}
1.3 Student类
package com.mingfa.work01;
public class Student {
private Integer id;
private String name;
public Student(Integer id, String name) {
this.id = id;
this.name = name;
}
//注:无论是手写的hashMap集合,还是java提供的hashMap集合,当以自定义对象为 key 值的时候,需要重写 equals 以及 hashMap 方法
/**
* 重写 equals 方法
* @param o
* @return
*/
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return this.id.equals(student.id) && this.name.equals(student.name);
//return Objects.equals(this.id,student.id) && Objects.equals(this.name,student.name);
}
/**
* 重写 hashCode 方法
* @return
*/
@Override
public int hashCode() {
return this.id.hashCode() + this.name.hashCode();
//return Objects.hash(id, name);
}
/**
* 重写 toString 方法,方便打印
* @return
*/
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
1.4 启动类/测试类
package com.mingfa.work01;
import java.util.*;
public class test {
public static void main(String[] args) {
MyHashMap map = new MyHashMap<>();
/**
* 存放 a ,97 或者 b ,98,是为了测试 hash 冲突对该集合的影响
* 原因是 a 的哈希值与97的哈希值均是97。其他雷同
*/
map.put("a","123");
map.put(97,"789");
map.put("a","aaa");
map.put(97,"977");
map.put("b","bbb");
map.put(98,"988");
map.put("c","ccc");
map.put(99,"999");
/**
* 测试存放两个内容相同的自定义对象,集合是否能够有效处理
*/
map.put(new Student(1,"mingfa"),"mingfafa");
map.put(new Student(1,"mingfa"),"mingfafa1");
map.put(new Student(2,"mingfa"),"mingfafa2");
map.put("mingfa",new Student(1,"jinjin"));
map.put("mingfa",new Student(2,"jinjin"));
map.put("haha",new Student(3,"jinjin"));
System.out.println("集合长度为:"+map.size());//输出:10
System.out.println("key为a的value为:"+ map.get("a"));//输出:aaa
System.out.println("key为97的value为:"+ map.get(97));//输出:977
System.out.println("集合中全部的key:"+map.arrayListGetKeys());
System.out.println("集合中全部的value:"+map.arrayListGetValues());
System.out.print("集合中全部的键值对:");
ArrayList<Entry> arrayList = map.arrayListGetKeyAndValue();
for (Entry entry : arrayList){
System.out.print(entry.getKey()+"-"+entry.getValue()+" ");
}
System.out.println();
System.out.println(map.get(new Student(1, "mingfa")));//输出:mingfafa1
}
}
二、基于ArrayList实现HashMap
2.1 MyHashMap类
package com.mingfa.work01;
import java.util.ArrayList;
/**
* 使用 ArrayList 实现 HashMap
* 缺点:
* 1、效率低,原因是 ArrayList 的底层实现是数据的形式实现
* 优点:
* 1、保证存放的键值对是有序存放,而不是无序的
* @param <K>
* @param <V>
*/
public class MyHashMap<K,V> {
private ArrayList<Entry<K,V>> arrayList = new ArrayList<>();
/**
* 插入数据
* @param k key 值
* @param v value 值
*/
public void put(K k,V v){
//先查看集合中是否存在该 key 值
for (Entry entry : arrayList){
if (entry.getK().equals(k)) {//当 map 集合存放自定义对象,需要重写 equals() 以及 hashCode() 方法
entry.setV(v);
return;
}
}
//若不存在,则添加
arrayList.add(new Entry<>(k,v));
}
/**
* 根据 key 得到 value
* @param k key 值
* @return value 值
*/
public V get(K k){
for (Entry entry: arrayList) {
if (entry.getK().equals(k)) return (V)entry.getV();
}
return null;
}
/**
* 返回集合大小
* @return 集合大小
*/
public int size(){
return arrayList.size();
}
}
2.2 Entry类
package com.mingfa.work01;
/**
* 存放集合中的 键值对
* @param <K>
* @param <V>
*/
public class Entry<K,V> {
private K k;//存放 key 值
private V v;//存放 value 值
public Entry(K k, V v) {
this.k = k;
this.v = v;
}
public K getK() {
return k;
}
public V getV() {
return v;
}
public void setK(K k) {
this.k = k;
}
public void setV(V v) {
this.v = v;
}
}
2.3 启动类/测试类
package com.mingfa.work01;
import java.util.ArrayList;
/**
* 使用 ArrayList 实现 HashMap
* 缺点:
* 1、效率低,原因是 ArrayList 的底层实现是数据的形式实现
* 优点:
* 1、保证存放的键值对是有序存放,而不是无序的
* @param <K>
* @param <V>
*/
public class MyHashMap<K,V> {
private ArrayList<Entry<K,V>> arrayList = new ArrayList<>();
/**
* 插入数据
* @param k key 值
* @param v value 值
*/
public void put(K k,V v){
//先查看集合中是否存在该 key 值
for (Entry entry : arrayList){
if (entry.getK().equals(k)) {//当 map 集合存放自定义对象,需要重写 equals() 以及 hashCode() 方法
entry.setV(v);
return;
}
}
//若不存在,则添加
arrayList.add(new Entry<>(k,v));
}
/**
* 根据 key 得到 value
* @param k key 值
* @return value 值
*/
public V get(K k){
for (Entry entry: arrayList) {
if (entry.getK().equals(k)) return (V)entry.getV();
}
return null;
}
/**
* 返回集合大小
* @return 集合大小
*/
public int size(){
return arrayList.size();
}
}