hashmap源码解析可以说是面试中被问及频率比较高的了,主要还是因为hashmap的设计思路比较好,效率高。我今天不分析hashmap的扩容等内容,只分析hashmap的原理内容,以及手写一个hashmap。
hashmap采用的数据结构是数组+链表,数组特点是增删效率低,但是查找(根据下角标查找)效率高,当然如果数有序数组则根据二分查找等算法效率也很高,而链表的特点是,增删效率高,但是查找需要遍历效率低(改为树结构后查找效率也很高了)。所以,我们能不能把二者的各自有点结合一下呢?既有数组的高效查找又有链表的高效增删,答案是有的,那就是hashmap。先说hash,hash是散列算法,指的是任意长度的内容经过计算后生成一个固定长度的内容,这个过程就是hash,比如MD5算法。如果两个内容的hash结果一样,我们成为hash碰撞,根据这个算法定义我们可以知道hash碰撞是不可避免的,所以我们能做的就是如何降低hash碰撞。好了,说完了hash,我们再回头看看hashmap,数组+链表,如下图:
看到这个图,再回头想想散列函数的理解,什么是散列?就是将数据分散开来,hashmap中hash就是将数据分到不同的数据中,然后每个数组中存放的是什么?链表。所以再把数据放到链表中保存起来。那么如何分组?就是hash算法,最简单的方法也是hashmap中用的方法,就是将数据的某个唯一的值取模数组的长度,得到的结果就是数组的下角标,这样就把数据初步确定了要放到哪个链表中了,是不是很简单,也很精妙!那这样比我们直接放到链表中保存数据,查找速度快多少呢?数组的长度倍!
好了,那么有了上面的分析,我们先手写一个简易版的hashmap!
1.定义节点,我使用的是双向链表,目的是为了添加效率更高
class HashData {
private int id;
private String name;
public HashData next;
public HashData pre;
public HashData(int id, String name) {
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "HashData{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
2.定义双向链表,以及链表的增删改查
class CusLinkData {
//头结点,默认为空
private HashData mHead;
private HashData mLast;
/**
* 添加数据
*
* @param item
*/
public void add(HashData item) {
if (mHead == null) {
mHead = item;
mLast = mHead;
return;
}
HashData search = search(item.getId());
if (null == search) {
mLast.next = item;
item.pre = mLast;
mLast = item;
} else {
updata(search);
}
}
/**
* 删除节点
*
* @param item
*/
public void remove(HashData item) {
if (null == mHead) {
return;
}
HashData temp = mHead;
while (true) {
if (temp.getId() == item.getId()) {//找到了
break;
}
if (temp.next == null) {//没找到
return;
}
temp = temp.next;
}
if (mHead == temp) {//头节点
HashData headNext = mHead.next;
mHead.next = null;
if (null != headNext) {
headNext.pre = null;
}
mHead = headNext;
} else { //A B C
HashData t = temp.pre;
temp.pre.next = temp.next;
if (null != temp.next)
temp.next = t;
temp.pre = null;
temp.next = null;
}
}
/**
* 查找节点
*
* @param id
* @return
*/
public HashData search(int id) {
if (null == mHead) {
return null;
}
HashData temp = mHead;
while (true) {
if (temp.getId() == id)
return temp;
if (null == temp.next) {
return null;
}
temp = temp.next;
}
}
/**
* 更新节点
*
* @param item
*/
public void updata(HashData item) {
HashData hashData = search(item.getId());
if (null == hashData) {
return;
}
hashData.setName(item.getName());
}
/**
* 打印链表所有数据
*/
public void printAllData() {
if (mHead == null) {
return;
}
HashData temp = mHead;
while (true) {
System.out.print(temp.toString() + " ");
if (null == temp.next) {
return;
}
temp = temp.next;
}
}
}
3.自定义hashmap登场了,hashmap中定义链表数组和hash算法,然后也有增删改查方法就可以了。
class CusHashMap {
private int mArrSize = 16;
private CusLinkData[] mDataArr;
public CusHashMap() {
mDataArr = new CusLinkData[mArrSize];
initData();
}
public CusHashMap(int size) {
this.mArrSize = size;
mDataArr = new CusLinkData[mArrSize];
initData();
}
private void initData() {
for (int i = 0; i < mArrSize; i++) {
mDataArr[i] = new CusLinkData();
}
}
public void add(HashData item) {
int index = hash(item);
mDataArr[index].add(item);
}
public void remove(HashData item) {
int index = hash(item);
mDataArr[index].remove(item);
}
public HashData search(HashData item) {
int index = hash(item);
return mDataArr[index].search(item.getId());
}
public void updata(HashData item) {
int index = hash(item);
mDataArr[index].updata(item);
}
/**
* 散列函数计算方式,确定在那个分组
*
* @param item
* @return
*/
private int hash(HashData item) {
return item.getId() % mArrSize;
}
public void print() {
for (int i = 0; i < mArrSize; i++) {
System.out.print("第" + (i + 1) + "个链表的数据为 ");
mDataArr[i].printAllData();
System.out.println();
}
}
}
我们可以看到,其实自定义hashmap中的增删改查,就做了两件事,第一找到节点所在分组,第二操作所在链表对应的增删改查。好了,简单不!!!
好了,上面我们自定义的hashmap分析完了,然后再对照系统hashmap源码,分析源码去吧。一一对照,还是很简单的。