手动实现HashMap
什么是HashMap?
利用hash计算存储位置,存储键值对(Key-Value)的Map。
HashMap的结构
数组里面放链表,链表里面存储键值对
为什么这样设计?
数组的优势是查找效率高。
我们根据Key值计算出放入数组的位置,通过Key计算出的位置查找数据。
但是因为数组的大小是确定的,难以避免地会多个数据放到一个位置,此时我们就把放到数组同一个位置的数据设计成一个链表。(链表过长的话,还是需要扩容的)。
数据的结构
基本的数据:Key、Value,链表的指针,数组的下标
class Node<K,V>{
Node<K,V> nextNode;//指向同一链表中下一个节点
K k;//数据1
V v;//数据2
int hash;//在数组里的地址
}
数组的结构
private static int Capacity=10;
private Node[] listNodes=new Node[Capacity];
方法的实现,增、删、查(改)
增:put(K k,V v)
1、根据k计算下标
2、在数组对应位置查找是否已经有元素,如果为空直接放入
3、如果已经有元素,判断是否有相同的k值,如果有相同k值则进行修改,如果没有,则在链表末端放入该元素。
下标的计算
public int hash(K k) {
return k.hashCode()%Capacity;
//通过hashcode()计算得到的值不一定是符合数组的索引,取模运算
}
泛型类k默认为Object,调用函数hashcode()得到一个int值,然后对数组容量进行取模,保证可以放入数组
而在HashMap源码中是通过与操作实现
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
把数据包装成节点
Node node=new Node<>();
node.k=k;
node.v=v;
头插法在数组对应位置插入元素
Node node2=listNodes[index];
if(node2!=null) {
node2=node.nextNode;
}
listNodes[index]=node;
以上两段代码可以优化为一个Node的构造函数
```java
Node node2=listNodes[index];
listNodes[index]=new Node(k,v,index,node2);
public Node(K k,V v,int hash,Node<K, V> node) {
this.k=k;
this.v=v;
this.hash=hash;
this.nextNode=node;
}
遍历链表查找相同k值
```java
for(Node<K, V>node3=listNodes[index];node3!=null;node3=node3.nextNode) {
if(node3.k.equals(k)) {
node3.v=v;
return ;
}
}
查get(K k)
1、根据k值计算数组下标index
2、遍历数组该位置的链表
private V get(K k) {
int index=hash(k);
Node node=listNodes[index];
while(node!=null) {
K k2=(K) node.k;
if(k2.equals(k)){
return (V) node.v;
}
node=node.nextNode;
}
return null;
}
删remove(K k)
删除和get的方法区别不大,
1、根据k计算数组下标index
2、遍历链表listNodes(index)
3、找到匹配的k值,删除该节点
public void remove(K k) {
int index=hash(k);
Node node=listNodes[index];
//如果要删除的值是第一个节点
if(node.k.equals(k)) {
listNodes[index]=node.nextNode;
}
//删除的值不是第一个节点,需要连接前一节点和后一节点
if(node.nextNode!=null) {
K k2=(K) node.nextNode.k;
if(k2.equals(k)) {
node.nextNode=node.nextNode.nextNode;
}
node=node.nextNode;
}
return;
}
扩容addd(int newCapacity)
在容量达到一定程度,链表的长度会很长,此时查找效率会降低,需要对数组进行扩容。
public void addd(int newCapacity) {//传入新的容量
Node[]newlistNodes=new Node[newCapacity];//生成新的数组
for (int i = 0; i < listNodes.length; i++) {//遍历原数组
Node<K, V> node = listNodes[i];//遍历原数组,
while(node!=null) {//遍历链表每个节点
Node<K, V> nextNode= node.nextNode;//暂存下一个节点
int index=newhash(node.k);//给节点重新计算数组下标
//头插法,把元素插入新的数组
node.nextNode=newlistNodes[index];//该节点指向数组
newlistNodes[index]=node;//数组中放节点
node=nextNode;//原链表的下个节点
}
}
listNodes=newlistNodes;
Capacity=newCapacity;
}