MyHashMap
package com.cskaoyan.hashmap;
数组 + 链表
import java.util.LinkedHashSet;
import java.util.Set;
/*
API:
void put(K key, V value)//添加键值对
V get(K key)//获取键所对应的值
void delete(K key)//删除key
boolean contains(K key) //判断是否包含key
void clear() //清空map
boolean isEmpty() //判断是否为空
int size() //map大小
Set keys() //返回所有key的集合
*/
public class MyHashMap {
private static final int DEFAULT_CAPACITY = 16;//默认容量 0 100000000000000 2的30吃饭
private static final int MAX_CAPACITY = 1 << 30;//最大容量,取2的次幂效率高(复习位运算)
private static final double DEFAULT_LOAD_FACTOR = 0.75;//经验指数,没有为什么。大数据试验
// 字段
private Entry[] table;
//创建一个节点数组,含义是 table数组里每个位置的对象是entry节点,每个entry节点里有键值对和hash值,还有一个后驱节点。
private int size; //大小
private double loadFactor; //装填因子,默认为0.75
private int threshold; // 阈值,达到阈值就要开始扩容
/**
*如何理解这个Entry?
* 1.了解HashMap构造,HashMap底层是数组加向链表。数组支持随机访问,故HashMap查找数组位置为 * 0(1),然后再到链表上去差找为0(1) 常量级。
* 2.hash值是通过hash算法计算出的随机数,具有高度保密性。(撞库攻击)
* 3.构造方法里的this是调用其他构造方法的意思。
*/
private static class Entry {
K key;
V val;
int hash;
Entry next;
Entry(K key, V val, int hash) {
this.key = key;
this.val = val;
this.hash = hash;
}
@Override
public String toString() {
return key + "=" + val;
}
}
// 构造方法
public MyHashMap() {
this(DEFAULT_CAPACITY, DEFAULT_LOAD_FACTOR);
}
public MyHashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
@SuppressWarnings("unchecked")
public MyHashMap(int initialCapacity, double loadFactor) {
// initialCapacity: 大概存储键值对的个数
if (initialCapacity <= 0) {
throw new IllegalArgumentException("initialCapacity=" + initialCapacity);
}
if (loadFactor <= 0) {
throw new IllegalArgumentException("loadFactor=" + loadFactor);
}
this.loadFactor = loadFactor;
int cap = (int) (initialCapacity / loadFactor);
// 计算大于等于n的最小2的次幂
int n = tableLength(cap);
table = new Entry[n];
threshold = (int) (table.length * loadFactor);//阈值等于容量乘以装载因子
}
// 计算大于cap的最小的2^n (见文件:计算大于cap的最小的2的次幂)
private int tableLength(int cap) {
if (cap >= MAX_CAPACITY) return MAX_CAPACITY;
int n = cap - 1;
n |= n >>> 1;
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
return n + 1;
}
/**
* 添加键值对,如果key存在,就更新它对应的值
*
* @param key 键
* @param value 值
* @return 如果key不存在, 返回null, 如果key存在,返回原来的值
*/
public V put(K key, V value) {
if (key == null || value == null) {
throw new IllegalArgumentException("Key or value can not be null");
}
int hash = hash(key);
int idx = indexFor(hash, table.length);
// 遍历链表
for (Entry e = table[idx]; e != null; e = e.next) {
if (hash == e.hash && ((key == e.key) || key.equals(e.key))) {
// key存在
V oldValue = e.val;
e.val = value;
return oldValue;
}
}
// key不存在, 在头结点添加键值对。
addEntry(key, value, hash, idx);
return null;
}
private void addEntry(K key, V value, int hash, int idx) {
// 判断是否需要进行扩容
if (size == threshold) {
if (table.length == MAX_CAPACITY) {
// 也可以抛出异常
threshold = Integer.MAX_VALUE;
} else {
grow(table.length << 1); //两倍扩容
idx = indexFor(hash, table.length);
}
}
// 添加键值对
Entry entryToAdd = new Entry<>(key, value, hash);
entryToAdd.next = table[idx];//头插
table[idx] = entryToAdd;
size++;
}
@SuppressWarnings("unchecked")
private void grow(int newCapacity) {
Entry[] newTable = new Entry[newCapacity];
//遍历table头插到新数组
for (Entry e : table) {
while (e != null) {
Entry next = e.next;
int idx = indexFor(e.hash, newCapacity);
e.next = newTable[idx];
newTable[idx] = e;
e = next;
}
}
table = newTable;//吧新数组的地址赋值给table
threshold = (int) (table.length * loadFactor);
}
private int hash(K key) {
int h = key.hashCode();
return (h >> 16) ^ (h << 16);
}
private int indexFor(int hash, int length) {
return hash & (length - 1);(见附件2)
}
/**
* 根据key获取值
*
* @param key 指定的key
* @return 对应的值, 如果key不存在返回null
*/
public V get(K key) {
if (key == null) {
throw new IllegalArgumentException("Key cannot be null.");
} jdk可以存null null建或者值 ? 可以同时吗?
int hash = hash(key);
int idx = indexFor(hash, table.length);
for (Entry e = table[idx]; e != null; e = e.next) {
if (hash == e.hash && ((key == e.key) || (key.equals(e.key)))) {
return e.val;
}
}
return null;???我就说JDK
}
/**
* 根据指定的key, 删除键值对
*
* @param key 指定的key
* @return key对应的value, 如果key不存在返回null
*/
public V delete(K key) {
if (key == null) {
throw new IllegalArgumentException("Key cannot be null.");
}
int hash = hash(key);
int idx = indexFor(hash, table.length);
Entry prev = null;//定义一个前驱节点
for (Entry e = table[idx]; e != null; e = e.next) {
if (hash == e.hash && ((key == e.key) || (key.equals(e.key)))) {
V deleteValue = e.val;
if (prev == null) table[idx] = e.next; // // 删除头结点
else prev.next = e.next;
size--;
return deleteValue;
}
prev = e;
}
return null;
}
/**
* 判断哈希表中是否包含指定的键
*
* @param key 指定的键
* @return 如果包含返回true, 否则返回false
*/
public boolean contains(K key) {
if (key == null) {
throw new IllegalArgumentException("Key cannot be null.");
}
int hash = hash(key);
int idx = indexFor(hash, table.length);
for (Entry e = table[idx]; e != null; e = e.next) {
if (hash == e.hash && ((key == e.key) || (key.equals(e.key)))) {
return true;
}
}
return false;
}
public int size() {
return size;
}
public boolean isEmpty() {
return size == 0;
}
public void clear() {
///foreach??? 我记得老师好像是这么说的,大问题
for(int i = 0; i < table.length; i++) {
table[i] = null;
}
size = 0;
}
/**
* 获取哈希表中键的集合
*
* @return 哈希表中键的集合
*/
public Set keys() {
Set set = new LinkedHashSet<>();
for (Entry e : table) {
while (e != null) {
set.add(e.key);
e = e.next;
}
}
return set;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder("{");
for (Entry e : table) {
while (e != null) {
sb.append(e).append(", ");
e = e.next;
}
}
if (!isEmpty()) sb.delete(sb.length() - 2, sb.length());
return sb.append("}").toString();
}
public static void main(String[] args) {
MyHashMap map = new MyHashMap<>();
/*System.out.println(map);
System.out.println(map.size());
System.out.println(map.isEmpty());*/
map.put("王宝强", "马蓉");
map.put("刘强东", "章泽天");
map.put("文章", "马伊利");
map.put("贾乃亮", "李小璐");
/* System.out.println(map);
System.out.println(map.size());
System.out.println(map.isEmpty());*/
/*System.out.println(map.put("谢霆锋", "王菲"));
System.out.println(map);
System.out.println(map.put("谢霆锋", "张柏芝"));
System.out.println(map);*/
// System.out.println(map.put(null, "A"));
// System.out.println(map.put("A", null));
// V get(K key)
// System.out.println(map.get(null));
/*System.out.println(map.get("刘强东"));
System.out.println(map.get("邓超"));*/
// V delete(K key)
// System.out.println(map.delete(null));
/*System.out.println(map.delete("刘强东"));
System.out.println(map);
System.out.println(map.size());*/
/*System.out.println(map.delete("邓超"));
System.out.println(map);
System.out.println(map.size());*/
// contains(K key)
// System.out.println(map.contains(null));
/*System.out.println(map.contains("文章"));
System.out.println(map.contains("邓超"));*/
System.out.println(map.keys());
map.clear();
System.out.println(map.keys());
System.out.println(map.size());
}
}
附件1:计算大于cap的最小2的次幂
| (或运算):只要有1 结果就是1,0 | 0 = 0&( 与运算):同时为1结果才为1,否则为0.
^(异或运算符) :0^0 =0, 0^1=1,1^0=1,1^1=0 (相同为0 不同为1)
例如:cap = 100 ,那么 n = 99;转换为二进制为:0000 0000 0110 0011,执行第一步:n |= n>>>1,
先把n右移1位:0000 0000 0011 0001,然后进行 | 运算。
0000 0000 0110 0011
0000 0000 0011 0001
0000 0000 0111 0011
然后执行第二步:
0000 0000 0111 0011
0000 0000 0001 1100
0000 0000 0111 1111
然后第三步:
0000 0000 0111 1111
0000 0000 0000 0111
0000 0000 0111 1111
循环下去:得到0000 0000 0111 1111 = 127
127+1 = 128;
所以 大于100的最小的2的次幂是 128
附件2:hash & (length -1)
比如hash = 100;length -1 = 63;(只写低八位)hash = 0110 0100
length -1 = 0011 1111
= 0010 0100 (36)
所以 hash 为 100的值 应该存在索引36处。