Hashmap的相关问题

哈希值运算

  • 除留余数法:用关键字k除以某个不大于哈希表长度m的数p,将所得余数作为哈希表地址。
index = hashCode % n;
  • 位运算法:运用位运算,注意这里要保证 length=2 ^n。
int index= h & (length-1);  
//例如:15%4=3 ==>15&3=1111&0011=0011=3 

原理:从二进制角度来看,X/2 ^n 相当于X>>n,就是把X后移n位,被移掉的部分,则是 X % 2 ^n,所以X%2 ^n就是要获取后n位。
为了保证数组大小是2 ^n,计算超过 capacity 的最小 2 ^n:

int getSize(int capacity) {
    int size = 1;
    while (size < capacity) {
        size <<= 1;
    }
    return size;
}

这种方法的好处是效率比普通计算高,而且可以使结果均匀散裂,减少哈希碰撞。

解决地址冲突

由于相同索引的值不一定只有一个,所以需要解决地址冲突,其实这个是哈希表的核心问题:
最经典的方式就是链地址法,将相同索引的值,通过拉链法挂在hash[index]后。

保存数据有两种方式:数组和链表。数组的特点是:寻址容易,插入和删除困难;而链表的特点是:寻址困难,插入和删除容易。上面提到的链地址法,其实就是将数组和链表组合在一起,发挥了两者的优势,我们可以将其理解为链表的数组。

如果链表长度足够长怎么办?

同时,当链表长度hashmap还有红黑树,为了实现比此时链表更高效的查找。但是红黑树的出现是有要求的,要求链表长度>8,并且数组长度>64。如果数组长度没有超过64则在冲突时首先采用扩容的方法。扩容时,负载因子会确定在它没满之前的负载量,达到就扩容,最大容量是2^30。扩容后要rehash,把哈希值映射到新的哈希值中。哈希值只会于原位置或者是原位置+旧容量(二进制的第一位决定,是1的话则扩容那一位与运算会生效)。

为何阈值是8?

因为每个空间中的节点的数量分布服从泊松分布,8的时候几乎没有,自动转为树(不想让你转为树,因为树节点内存大),是时间与空间的权衡。

如何实现均匀散列?

哈希冲突的原因:高位不同、低位相同,导致散列很多。让低位和高位结合在一起看,使任何一位发生变化都能影响到最后的index值。

HashMap想了一种办法(扰动):将Hash值的高16位右移并与原Hash值取异或运算(^),混合高16位和低16位的值,得到一个更加散列的低16位的Hash值。再通过哈希函数找到它的index值,如:

// 没有Hash碰撞
H1 = H1 ^ (H1 >>> 16) = 5;
H2 = H2 ^ (H2 >>> 16) = 250;
index1 = (2 ^n -1) & H1 = 5 & 15 = 5
index2 = (2 ^n -1) & H2 = 250 & 15 = 10

还有办法嘛?
开放地址法:要是发生冲突就往后一个,直到找到一个空位。
再哈希法:用另外一个哈希函数找到另外一个key值。
建立公共溢区

常考题:Leetcode:前K个高频元素

思路:

定义数据结构:priority_queue,unordered_map,vector;

1.定义hashmap用来存储value和对应的频率,即

unordered_map <pair<int,int>>;

2.按照频率排序,放入优先队列中(即堆排序);
3.取优先队列中,频率前k个的对应值;

代码:

vector<int> topKFrequent(vector<int>& nums, int k) {
        if(nums.empty()){return {};}
        unordered_map<int,int> map; //定义hashmap(容器)
        for(auto i:nums){
            map[i]++;
        }
         priority_queue<pair<int,int>,vector<pair<int,int>>,greater<pair<int,int>>> que; //定义一个小顶堆(优先队列默认是大顶堆)
/*
struct cmp{
        bool const operator()(pair<int,int> rn,pair<int,int> ln){ // 从小到大,小心写反!!
            if(ln.first<rn.first). // 从小到大,小心写反!!
                return true;
            return false; }
    }; */
//priority_queue<pair<int,int>,vector<pair<int,int>>,cmp; //如果需要自定义排序函数
        vector<int> res;
        for(auto it=map.begin();it!=map.end();it++)
        {
            que.push(make_pair(it->second,it->first));  
            if(int(que.size())>k) //维护一个大小为k的顶堆,节省时间
            {
            	que.pop();
            }                   
        }      
        while(que.size()>0)
        {
            res.push_back(que.top().second);
            que.pop();
        }
        return vector<int>(res.rbegin(),res.rend());//由于是小顶堆,结果应为逆序

    }
时间复杂度:
  • 存储hashmap,O(n);
  • 遍历hashmap的数据进行堆排序:遍历N个数*维护一个大小为k的小顶堆O(nlogk);
  • 转化为大小为k的结果:O(k);
  • 总共:O(nlogk)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: HashMap 本身不是线程安全的,如果需要解决线程问题,可以使用下列方法: 1. 加锁:使用 synchronized 关键字或者使用锁对象来保证 HashMap 的操作是线程安全的。 2. 使用 ConcurrentHashMap:这是 Java 并发包提供的线程安全的 HashMap 实现,可以在多线程环境下使用。 例如: ``` Map<String, Integer> map = new ConcurrentHashMap<>(); ``` 如果需要使用加锁的方式,可以如下操作: ``` private final Map<String, Integer> map = new HashMap<>(); private final Object lock = new Object(); public void put(String key, Integer value) { synchronized (lock) { map.put(key, value); } } public Integer get(String key) { synchronized (lock) { return map.get(key); } } ``` ### 回答2: HashMap本身并没有解决线程问题的特性,即它不是线程安全的数据结构。在多线程环境下,如果多个线程同时对HashMap进行读写操作,就有可能导致数据不一致或其他线程安全问题的发生。 然而,我们可以通过以下几种方式来解决HashMap在多线程环境下可能出现的线程问题: 1. 使用线程安全的HashMap类:Java提供了线程安全的HashMap实现,如ConcurrentHashMap。ConcurrentHashMap采用了分段锁的机制,可以并发地对不同的段进行操作,从而提高了并发度和性能。 2. 使用线程安全的包装类:通过使用Collections类的synchronizedMap方法对HashMap进行包装,可以将其转换为线程安全的Map,即使用synchronized关键字对所有方法进行加锁。这样,每次只允许一个线程对HashMap进行读写操作,能够确保线程安全。 3. 使用显式锁:通过使用显式锁(如ReentrantLock或ReadWriteLock)对HashMap进行加锁和解锁操作,可以实现对读写操作的互斥访问,确保线程安全。 4. 限制只读访问:如果只有读操作而没有写操作,可以将HashMap声明为final和不可变,这样就不需要考虑线程安全问题。 总之,HashMap本身是非线程安全的,但我们可以通过使用线程安全的HashMap实现、包装类、显式锁或限制只读访问来解决HashMap在多线程环境下的线程问题。 ### 回答3: HashMap是Java中常用的数据结构,用于存储键值对。在多线程环境下,HashMap可以出现线程问题,如不一致的读写操作,导致数据的丢失或者错乱。 为了解决线程问题,Java提供了ConcurrentHashMap类,它是线程安全的HashMap的实现。ConcurrentHashMap使用一种称为分段锁(Segment)的机制来实现并发访问。 ConcurrentHashMap内部分为多个小的Segment段,每个段都相当于一个独立的小的HashMap,可以独立地进行加锁操作。这样,当一个线程访问某个段时,其他段仍然可以被其他线程访问,不会阻塞其他的操作。 在写入操作时,ConcurrentHashMap只锁住相关的段,而不影响其他段的访问操作。这样就允许多个线程并发地进行读写操作,提高了并发性能。 此外,ConcurrentHashMap使用volatile和CAS(Compare and Swap)操作来保证内存的可见性和原子性,确保数据的一致性。 总结来说,HashMap可以通过ConcurrentHashMap来解决线程问题。ConcurrentHashMap使用分段锁机制,允许并发地读写操作,提高了并发性能。并且它使用volatile和CAS操作来保证内存的可见性和原子性,确保数据的一致性。因此,在多线程环境下,推荐使用ConcurrentHashMap而不是普通的HashMap来处理并发访问的问题

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值