一、哈希表
了解哈希表之前我们先介绍一下我们常见的数据结构,数组和链表。
数组:数组是相同类型数据的有序集合。数组描述的是相同类型的若干个数据,按照一定的先后次序排列组合而成。其中每一个数据称为一个数组元素,每个数组元素可以通过一个下标来访问他们。
数组的缺点也很明显,根据内容查找元素慢,数组的大小在定义时确定无法改变,在查询时往往需要遍历,比较费时。增加删除元素效率慢。
链表:链表可以称为性能更强大的数组,链表可以动态的进行存储分配,他可以在节点中定义多种数据类型,还可以根据需要随意增添,删除,插入节点。
同样链表虽然更改了数组无法动态增加的缺点,但是在查询时一样需要遍历。
哈希表:哈希表通俗的来说是数组和链表的叠加,它集合了数组和链表的优点,无论数据有多少,处理起来都特别的快,能够快速地进行插入修改元素
、删除元素
、查找元素
等操作,是一个比较常用且实用的数据结构。
哈希表是储存键值对类型的数据(key,value),在遇到相同的key值时,value值会被覆盖。
哈希表也叫作散列表,它能够如此高效的进行增删改查,是因为它所拥有的哈希函数,举一个简单的例子,我们假设哈希函数是对16取余,初始长度是16,在我们存入(13,7)的时候,这个数据就被存在13的位置上了,在对数据进行拿取时,又进行哈希函数,在13这个位置来查询所要的数据,提高了的增删改查的速度。当然哈希函数远比我们所自己设定的对16取余复杂。
哈希表扩容机制:当哈希表的装载因子过大,哈希冲突严重时,其增删改查的性能也随之降低。当负载因子过大时就会进行扩容,扩容到原先的两倍。
二、自制一个简易的哈希表
通俗来讲我们认为哈希表是数组+链表,所以我们自制的简易哈希表也用这种结构,首先我们先初始化哈希表的长度
public MyHashMap(int size) {
this.size = size;
linkArr = new LinkList[size];
//在数组中初始化链表的root节点
for (int i = 0; i < size; i++) {
linkArr[i] = new LinkList();
}
}
LinkList是我自己自制的一个链表类,源码如下仅供参考。
/**
* 链表
*/
public class LinkList<K,V> {
public Node root; //保存首节点
private Node last;
private int size;
public LinkList(){
root = new Node();
}
public void add(K key,V value){
Node newNode = new Node(key,value);
Node head = root.next;
if(head == null){
root.next = newNode;
last = newNode;
}else{
last.next = newNode;
last = newNode;
}
size++;
}
public V get(K key){
Node search = root.next;
for(int i = 0;i<size;i++){
if(search.key.equals(key)){
return (V)search.value;
}
search = search.next;
}
return null;
}
public int size(){
return size;
}
}
//哈希表节点对象
class Node<K,V>{
public Node next;
public K key;
public V value;
public Node(){
}
public Node(K key,V value){
this.value = value;
this.key = key;
}
}
我们自定义哈希表初始长度为16
private static int size = 16;
public MyHashMap() {
this(size);
}
散列函数由于我们还不能够做出比较完美的散列算法我们就使用系统自带的
public int hash(K key) {
return key.hashCode() % size;
}
自定义put方法
//put
public void put(K key, V value) {
//扩容
int index;
//根据散列函数计算key的位置
if (key == null) {
index = 0;
} else {
index = hash(key);
}
for (Node x = linkArr[index].root; x != null; x = x.next) {
if (x.key == key) {
x.value = value;
return;
}
}
linkArr[index].add(key, value);
for(int i = 0;i<linkArr.length;i++){
if(linkArr[i].size() == size){
size *= 2;
linkArr = myclone(size);
}
}
}
扩容的同时我们要考虑是否要扩容,我自己定义的机制是当其中有一个链表达到size时就扩容,将size扩容到原来的两倍,同时克隆原本的哈希表
public LinkList[] myclone(int size) {
LinkList[] cloneArr = new LinkList[size];
for(int i = 0;i<size;i++){
if(i<(size/2))
cloneArr[i] = linkArr[i];
else
cloneArr[i] = new LinkList<>();
}
return cloneArr;
}
自定义get方法
public V get(K key) {
int index;
if (key == null) {
index = 0;
} else {
index = hash(key);
}
return (V) linkArr[index].get(key);
}
这样我们一个简易的自定义哈希表就完成了。