java哈希表

哈希表是什么

数组和向量都可以存储对象,但对象的存储位置是随机的,也就是说对象本身与其存储位置之间没有必然的联系。当要查找一个对象时,只能以某种顺序(如顺序查找或二分查找)与各个元素进行比较,当数组或向量中的元素数量很多时,查找的效率会明显的降低。

一种有效的存储方式,是不与其他元素进行比较,一次存取便能得到所需要的记录。这就需要在对象的存储位置和对象的关键属性(设为 k)之间建立一个特定的对应关系(设为 f),使每个对象与一个唯一的存储位置相对应。在查找时,只要根据待查对象的关键属性 k 计算f(k)的值即可。如果此对象在集合中,则必定在存储位置 f(k)上,因此不需要与集合中的其他元素进行比较。称这种对应关系 f 为哈希(hash)方法,按照这种思想建立的表为哈希表。
HashSet底层是用了HashMap

  • 哈希算法,是一类算法;
  • 哈希表(Hash Table)是一种数据结构;
  • 哈希函数,是支撑哈希表的一类函数;
  • Map是映射、地图的意思,在Java中Map表示一种把K映射到V的数据类型;
  • HashMap是Java中用哈希数据结构实现的Map;
    Hash表:根据设定的Hash函数 - 和处理冲突的方法,将一组关键字映象 到一个有限的连续的地址集(区间)上,并以关键字在地址集中的象 作为记录在表中的存储位置,这样的表便称为Hash表 ;
    Hash函数构造方法
    直接寻址法:
    取k 或k 的某个线性函数为Hash地址 。
    特点:由于直接地址法相当于有多少个关键字就必须有多少个相应地址去对应,所以不会产生冲突,也正因为此,所以实际中很少使用这种构造方法。
    数字分析法:

首先分析待存的一组关键字 ,比如是一个班级学生的出生年月日 ,我们发现他们的出生年 大体相同,那么我们肯定不能用他们的年 来作为存储地址 ,这样出现冲突 的几率很大;但是,我们发现月日 的具体数字差别很大,如果我们用月日 来作为Hash地址 ,则会明显降低冲突几率。因此,数字分析法就是找出关键字 的规律,尽可能用差异数据来构造Hash地址 ;

特点:需要提前知道所有可能的关键字,才能分析运用此种方法,所以不太常用。

平方取中法:

先求出关键字的平方值,然后按需要取平方值的中间几位作为哈希地址。这是因为:平方后中间几位和关键字中每一位都相关,故不同关键字会以较高的概率产生不同的哈希地址。

例:我们把英文字母在字母表中的位置序号作为该英文字母的内部编码。例如K的内部编码为11,E的内部编码为05,Y的内部编码为25,A的内部编码为01, B的内部编码为02。由此组成关键字“KEYA”的内部代码为11052501,同理我们可以得到关键字“KYAB”、“AKEY”、“BKEY”的内部编码。之后对关键字进行平方运算后,取出第7到第9位作为该关键字哈希地址。
折叠法:

将关键字分割成位数相同的几部分(最后一部分位数可以不同),然后取这几部分的叠加和(去除进位)作为散列地址。数位叠加可以有移位叠加和间界叠加两种方法。移位叠加是将分割后的每一部分的最低位对齐,然后相加;间界叠加是从一端向另一端沿分割界来回折叠,然后对齐相加。

随机数法:

选择一个随机函数,取关键字的随机函数值作为Hash地址 ,通常用于关键字长度不同的场合。即
特点:通常,关键字长度不相等时,采用此法构建Hash函数 较为合适。

除留取余法:


取关键字被某个不大于Hash表 长m 的数p 除后所得的余数为Hash地址 。

特点:这是最简单也是最常用的Hash函数构造方法。可以直接取模,也可以在平法法、折叠法之后再取模。

值得注意的是,在使用除留取余法 时,对p 的选择很重要,如果p 选的不好会容易产生同义词 。由经验得知:p 最好选择不大于表长m的一个质数 、或者不包含小于20的质因数的合数。

数据结构

  1. 基本的数据结构:集合、线性结构、树形结构、图;
  2. 物理结构:顺序储存、链式储存;

HashMap

/*** 1. 构造方法:最终使用的是这个构造方法 ***/
// 初始容量initialCapacity为16,装填因子loadFactor为0.75
public HashMap(int initialCapacity, float loadFactor) {
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal initial capacity: " +
                                           initialCapacity);
    if (initialCapacity > MAXIMUM_CAPACITY)
        initialCapacity = MAXIMUM_CAPACITY;
    if (loadFactor <= 0 || Float.isNaN(loadFactor))
        throw new IllegalArgumentException("Illegal load factor: " +
                                           loadFactor);
 
    this.loadFactor = loadFactor;
    threshold = initialCapacity;
    init();//init可以忽略,方法默认为空{},当你需要集成HashMap实现自己的类型时可以重写此方法做一些事
}
 
/*** 2. (静态/实例)成员变量 ***/
/** 默认的容量,容量必须是2的幂 */
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
/** 最大容量2的30次方 */
static final int MAXIMUM_CAPACITY = 1 << 30;
/** 默认装填因子0.75 */
static final float DEFAULT_LOAD_FACTOR = 0.75f;
/** 默认Entry数组 */
static final Entry<?,?>[] EMPTY_TABLE = {};
 
/** Entry数组:table */
transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;
 
/** table中实际的Entry数量 */
transient int size;
 
/** 
 * size到达此门槛后,必须扩容table;
 * 值为capacity * load factor,默认为16 * 0.75 也就是12。
 * 意味着默认情况构造情况下,当你存够12个时,table会第一次扩容
 */
int threshold;
 
/** 装填因子,值从一开构造HashMap时就被确定了,默认为0.75 */
final float loadFactor;
 
/**
 * 哈希种子,实例化HashMap后在将要使用前设置的随机值,可以使得key的hashCode冲突更难出现
 */
transient int hashSeed = 0;
 
/**
 * The number of times this HashMap has been structurally modified
 * Structural modifications are those that change the number of mappings in
 * the HashMap or otherwise modify its internal structure (e.g.,
 * rehash).  This field is used to make iterators on Collection-views of
 * the HashMap fail-fast.  (See ConcurrentModificationException).
 */
transient int modCount;
 
/*** 3. Map.Entry<K,V>:数组table中实际存储的类型 ***/
static class Entry<K,V> implements Map.Entry<K,V> {
    final K key;       // "Key-Value对"的Key
    V value;           // "Key-Value对"的Key
    Entry<K,V> next;    
    int hash;
 
    Entry(int h, K k, V v, Entry<K,V> n) {
        value = v;
        next = n;//链表的下一个Entry
        key = k;
        hash = h;
    }
    public final int hashCode() {
        return Objects.hashCode(getKey()) ^ Objects.hashCode(getValue());
    }
}
 
**- put(key, value)、解决冲突**

```java
/** 存放 **/
public V put(K key, V value) {
    if (table == EMPTY_TABLE) {
        inflateTable(threshold);//table会被初始化为长度16,且hashSeed会被赋值;
    }
    if (key == null)
        //HashMap允许key为null:在table中找到null key,然后设置Value,同时其hash为0;
        return putForNullKey(value);
 
    // a). 计算key的hashCode,下面详细说
    int hash = hash(key);
 
    // b). 根据hashCode计算index
    int i = indexFor(hash, table.length);
 
    // c). 做覆盖,遍历index位置的Entry链表,*不是解决*冲突
    for (Entry<K,V> e = table[i]; e != null; e = e.next) {
        Object k;
        if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
            // hashCode和equals都相等则表明:本次put是覆盖操作,下面return了被覆盖的老value
            V oldValue = e.value;
            e.value = value;
            e.recordAccess(this);
            return oldValue;
        }
    }
 
    modCount++;
    // d). 添加Entry,并解决冲突
    // 如果需要增加table长度(size>threshold)就乘2增加,并重新计算每个元素在新table中的位置和转移
    addEntry(hash, key, value, i);
    return null;//增加成功最后返回null
}
 
 
//详细说说上面的a). b). d).
 
/** a). 为了防止低质量的hash函数,HashMap在这里会重新计算一遍key的hashCode **/
final int hash(Object k) {
    int h = hashSeed;
    if (0 != h && k instanceof String) {//字符串会被特殊处理,返回32bit的整数(就是int)
        return sun.misc.Hashing.stringHash32((String) k);
    }
 
    h ^= k.hashCode();//将key的hashCode与h按位异或,最后赋值给h
 
    // This function ensures that hashCodes that differ only by
    // constant multiples at each bit position have a bounded
    // number of collisions (approximately 8 at default load factor).
    h ^= (h >>> 20) ^ (h >>> 12);
    return h ^ (h >>> 7) ^ (h >>> 4);
}
 
/**
 * b). 计算此hashCode该被放入table的哪个index
 */
static int indexFor(int h, int length) {
    return h & (length-1);//与table的length - 1按位与,就能保证返回结果在0-length-1内
}
 
/**
 * 解决冲突:链地址法
 * d).  addEntry(hash, key, value, i)最终是调用了此函数
 */
void createEntry(int hash, K key, V value, int bucketIndex) {
    Entry<K,V> e = table[bucketIndex];// index的Entry拿出来
    // put添加新元素是直接new Entry放在链头,如果有老的(有冲突)则将next设置为老的,如果没有正好设置next为null
    table[bucketIndex] = new Entry<>(hash, key, value, e);// 在构造函数中,e代表next
    size++;
}

取 - get(key)

  1. 根据k使用hash(k)重新计算出hashCode
  2. 根据indexFor(int h, int length)计算出该k的index
  3. 如果该index处Entry的key与此k相等,就返回value,否则继续查看该Entry的next
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值