hash与HashMap
hash的介绍
Hash,一般翻译做“散列”,也有直接音译为“哈希”的,就是把任意长度的输入(又叫做预映射)通过散列算法变换成固定长度的输出,该输出就是散列值。这种转换是一种压缩映射,也就是,散列值的空间通常远小于输入的空间,不同的输入可能会散列成相同的输出,所以不可能从散列值来确定唯一的输入值。简单的说就是一种将任意长度的消息压缩到某一固定长度的消息摘要的函数。
优秀的哈希算法特点
效率高
不可逆性
敏感性
低碰撞性
速度、空间、碰撞性进行取舍,不同的hash算法侧重点不同
java中的hash
- hashCode:代表一个对象的签名,两个对象相等则hashCode一定要相等,但是,两个hashCode相等,对应的两个对象则不一定相等(碰撞),在数学中叫“必要条件”。
- HashMap:侧重速度。
- Object.hashCode:直接获取内存地址
- Integer.hashCode:直接返回的intValue
- String.hashCode:根据字符串内容生成hashCode,字符串内容一样则hashCode也相同,String对象会保存第一次计算出的哈希值,之后该对象用到的哈希值都是这个保存好的哈希值,不会再次计算。
其他场景中的Hash算法
- MD4,MD5
- SHA(Secure Hash Algorithm)[SHA-1, SHA-224, SHA-256, SHA-384, SHA-512]
哈希算法的用途
- 哈希查找,哈希表
- 秒传(百度网盘传电影,如果电影的哈希值在数据库中已经存在了,就直接引用)
- HashMap
- 加解密,MD5,SHA
- Git文件提交
- 区块链
碰撞性及解决方法
手写模仿HashMap主要功能
public class MyHashMap<K, V> {
private Entry<K, V>[] table;
//容量
private static final Integer CAPCITY = 1 << 4; //16
//存入个数
private int size;
public void put(K k, V v) {
//HashMap是懒加载,table在第一次存入数据时才初始化。
if (null == table) {
inflate();
}
//计算key的哈希值
int hashCode = hash(k);
//计算下标
int index = indexFor(hashCode);
for (Entry<K, V> entry = table[index]; entry != null; entry = entry.next) {
if (entry.key.equals(k)) {
entry.value = v;
return;
}
}
addEntry(k, v, index);
}
private void addEntry(K k, V v, int index) {
//如果是table[index]已经存在(产生碰撞),在原来的table[index]头上添加新的对象,新对象的next参数指向原对象,这里用的是链地址法。
Entry<K, V> newEntry = new Entry<>(k, v, table[index]);
table[index] = newEntry;
size++;
}
private int indexFor(int hashCode) {
return hashCode % table.length;
}
private int hash(K k) {
return k.hashCode();
}
private void inflate() {
table = new Entry[CAPCITY];
}
public V get(K k) {
int hashCode = hash(k);
int index = indexFor(hashCode);
for (Entry<K, V> entry = table[index]; entry != null; entry = entry.next) {
if (entry.key.equals(k)) {
return entry.value;
}
}
return null;
}
class Entry<K, V> {
public K key;
public V value;
//产生碰撞时用的链地址法往链表下面加对象,指向链表的下一个对象,或者往链表上面加。
public Entry<K, V> next;
public Entry(K key, V value) {
this.key = key;
this.value = value;
}
public Entry(K key, V value, Entry<K, V> next) {
this.key = key;
this.value = value;
this.next = next;
}
public K getKey() {
return key;
}
public V getValue() {
return value;
}
}
public static void main(String[] args) {
MyHashMap<String, String> myHashMap = new MyHashMap<>();
myHashMap.put("1", "1v");
myHashMap.put("2", "2v");
myHashMap.put("3", "3v");
System.out.println(myHashMap.get("3"));
}
}
研究HashMap源码 1.7
1.7源码解读:https://blog.youkuaiyun.com/carson_ho/article/details/79373026
HashMap是线程不安全的
扩容resize的时候会出现死循环 http://www.importnew.com/22011.html
fail-fast快速失败法,比如在遍历时元素删除,会抛出ConcurrentHashMap
HashMap<String, String> hashMap = new HashMap<>();
hashMap.put("1", "1");
hashMap.put("2", "2");
//modCount=3
for (String key : hashMap.keySet()) {
if (key.equals("2")) {
hashMap.remove(key);
}
}
System.out.println(hashMap);
解决方法1:在迭代器中删除
HashMap<String, String> hashMap = new HashMap<>();
hashMap.put("1", "1");
hashMap.put("2", "2");
//modCount=3
/*for (String key : hashMap.keySet()) {
if (key.equals("2")) {
hashMap.remove(key);
}
}*/
Iterator<String> iterator = hashMap.keySet().iterator();
while (iterator.hasNext()) {
String key = iterator.next();
if (key.equals("1")) {
iterator.remove();
}
}
System.out.println(hashMap);
解决方法2:使用ConcurrentHashMap
ConcurrentHashMap<String, String> hashMap = new ConcurrentHashMap<>();
hashMap.put("1", "1");
hashMap.put("2", "2");
//modCount=3
for (String key : hashMap.keySet()) {
if (key.equals("2")) {
hashMap.remove(key);
}
}
/*Iterator<String> iterator = hashMap.keySet().iterator();
while (iterator.hasNext()) {
String key = iterator.next();
if (key.equals("1")) {
iterator.remove();
}
}*/
System.out.println(hashMap);
为什么要用2的次方数作为数组大小?
1、新老索引可以尽可能保持一致,大大减少了之前已经散列良好的老数组的数据位置重新调换。
2、低位全是1,有更好的散列性。
为什么重写equals的时候要重写hashCode方法?
public class Equals {
public static void main(String[] args) {
Person person1 = new Person(1, "gakki");
Person person2 = new Person(1, "gakki");
System.out.println(person1.equals(person2));
HashMap<Person, String> hashMap = new HashMap<>();
hashMap.put(person1, "可爱");
System.out.println(hashMap.get(person1));//可爱
System.out.println(hashMap.get(person2));//null
//HashMap中下标是通过key的哈希计算出来的,person1和person2的哈希值不一样,所以算出的下标不一样,为了使下标一样,必须重写hashCode方法
}
//目的是id一样,person就一样
static class Person{
private int id;
private String name;
public Person(int id, String name) {
this.id = id;
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return id == person.id;
}
//应该重写hashCode
/*@Override
public int hashCode() {
return Objects.hash(id);
}*/
}
}