HashMap 是我们经常使用的一种数据结构,而且最近面试也经常被问到HashMap的底层原理,然后呢给面试官说的简单点他会觉得你学习不够深,然后说的复杂点,自己又觉得不切实际,因为自己本来就没有经历过那些场景,感觉自己很虚。因此今天,我先简单实现一下HashMap的最基本的功能。(目前未实现红黑树部分)
package MyHashMap;
public interface Map {
/**
* 向map中插入值
* @param k 键
* @param v 值
* @return V 若非更新操作,则返回当前值,若是更新操作,则返回旧值
*/
public V put(K k, V v);
/**
* 根据K获得V
* @param k
* @return
*/
public V get(K k);
/**
* 获取map中元素的个数
* @return
*/
public int size();
/**
* 打印当前map中的元素,测试使用
*/
public void printMap();
/**
* 集合map中存储键值对(元素)的对象
* @author 康茜
*
* @param
* @param
*/
interface Entry {
K getKey();
V getValue();
V setValue(V v);
}
}
2、HashMap实现类
package MyHashMap;
public class HashMap implements Map {
//HashMap中数组的长度,默认为16
private int defaultLength = 16;
//数组table
Node[] table;
//table中元素(Entry)的个数,不是table的长度
int size;
//加载因子,其实就是设置一个参数,通过这个参数与size的乘积和defaultLength相比看是否需要扩容
private double loadFactor = 0.75d;
//这个model是一个测试参数,可以是%、&,其含义是定位数组下标的两种方式,在position()函数中具体体现
private static String model = "&";
public HashMap() {
}
public HashMap(int capacity) {
this.defaultLength = capacity;
}
@Override
public V put(K k, V v) {
//懒加载
if(table == null) {
table = new Node[defaultLength];
}
//判断是否扩容
if(size * loadFactor > defaultLength) {
System.out.println("进行扩容:" + size + ":" + defaultLength);
reSize();
}
int index = position(k, defaultLength);
Node nodeHead = table[index];
//如果当前链表为空,则直接插入并且返回插入值
if(nodeHead == null) {
Node node = new Node(k, v, null);
table[index] = node;
size++;
return v;
} else {
Node temp = nodeHead;
//若是更新操作
if(temp.k.equals(k)) {
V oldValue = temp.getValue();
temp.setValue(v);
return oldValue;
}
while(temp.next != null) {
if(temp.k.equals(k)) {
V oldValue = temp.getValue();
temp.setValue(v);
return oldValue;
}
temp = temp.next;
}
//无更新操作则将新节点头插到链表上
Node newNode = new Node(k, v, nodeHead);
table[index] = newNode;
//插入成功size++
size++;
//或者使用尾插法,减少一个断链操作即:table[index] = newNode;
// Node newNode = new Node(k, v, null);
// temp.next = newNode
}
return v;
}
@Override
public V get(K k) {
int index = position(k, defaultLength);
Node nodeHead = table[index];//直接定位到数组的链表上
if(nodeHead == null) {
return null;
}
Node temp = nodeHead;
while(temp != null) {
if(temp.k.equals(k)) {
return temp.getValue();
}
}
//如果找不到,则返回null
return null;
}
@Override
public int size() {
return size;
}
@Override
public void printMap() {
System.out.println("===================打印map======================");
for(int i = 0; i < table.length; i++) {
Node node = table[i];
System.out.println("下标为【" + i
+ "】的链表");
if(node == null) {
System.out.println("NULL");
continue;
}
while(node != null) {
System.out.print(node.toString());
node = node.next;
}
System.out.println();
}
}
/**
* 定位元素在数组中的下标
* @param k
* @return
*/
private int position(K k, int tableLength) {
int index = 0;
if(k == null) {
System.out.println("这里键为空,需要处理键为空的情况");
//TODO
}
if(model.equals("&")) {
index = k.hashCode() & (tableLength - 1);
} else if(model.equals("%")) {
index = k.hashCode() % tableLength;
}
// System.out.println("本次put操作的index:" + index);
return index;
}
/**
* 扩容:扩容并且重新排列元素
* 如果size*loadFactor>defaultLength,则扩容
*/
private void reSize() {
//翻倍扩容
//1、创建临时数组
Node[] temp = new Node[defaultLength << 2];
//2、重新计算散列值,将元素插入到temp中
for(int i = 0; i < table.length; i++) {
Node node = table[i];
while(node != null) {
int index = position((K)node.getKey(), temp.length);
//头插法插入节点
Node next = node.next;
node.next = temp[index];
temp[index] = node;
node = next;
}
}
/**
* Node tempNode = temp[index];
if(tempNode == null) {
//这里我本来想选择更改引用的方式,而不是选择重新new一个新的node,但是写了之后发现存在一个问题:因为这个nodeHead指向原链表的
//,所以如果传递引用的话后续操作就不能轻易对newNode进行修改,所以我采用直接new的方式
//newNode = nodeHead;
tempNode = new Node
3、测试
package MyHashMap;
public class Test {
public static void main(String[] args) {
Map map = new HashMap();
map.put("000号:", "000");
map.put("001号:", "001");
map.put("002号:", "002");
map.put("003号:", "003");
map.put("004号:", "004");
map.put("005号:", "005");
map.put("006号:", "006");
map.put("007号:", "007");
map.put("008号:", "008");
map.put("009号:", "009");
map.put("010号:", "010");
map.put("011号:", "011");
map.put("012号:", "012");
map.put("013号:", "013");
map.put("014号:", "014");
map.put("015号:", "015");
map.put("016号:", "016");
map.put("017号:", "017");
map.put("018号:", "018");
map.put("019号:", "019");
map.put("020号:", "010");
map.put("021号:", "011");
map.put("022号:", "012");
map.put("023号:", "013");
map.put("024号:", "014");
map.put("025号:", "015");
map.put("026号:", "016");
map.put("027号:", "017");
map.put("028号:", "018");
map.put("029号:", "019");
map.put("030号:", "010");
map.put("031号:", "011");
map.put("032号:", "012");
map.put("033号:", "013");
map.put("034号:", "014");
map.put("035号:", "015");
map.put("036号:", "016");
map.put("037号:", "017");
map.put("038号:", "018");
map.put("039号:", "019");
map.printMap();
System.out.println(map.size());
}
}
目前只完成了数组+链表形式的hashMap实现,因为我现在还不太了解红黑树的算法实现,后续应该会完成数组+链表+红黑树的实现,或者是选择一种合适的算法加入到自己的hashMap中,使其性能更优。