目录
2.第一次开辟的空间有多少?那初始化时指定开辟空间长度是否更有利于内存资源节省?
3.扩容是在什么时候扩的,达到的阈值是多少?初始申请的空间与扩容阈值之间的关系,围绕第二次扩容阈值与初始指定申请空间的关系说明?
6.在HashMap的使用中,for循环数据过程中删除数据是否异常?如果异常,寻找为何异常?如何规避这个异常?
1.初始化做了哪些事儿?
HashMap的初始化主要包括以下几个方面:
-
创建底层为数组的HashMap对象,并将每个桶初始化为空链表。
-
根据键的哈希值计算出它在数组中的位置,并将键值对存储到对应的桶中。
-
处理哈希冲突,将相同哈希值的键值对存储到同一个桶中形成链表。
-
HashMap的底层是一个数组,数组中的每个元素被称为桶(bucket),每个桶存储一个链表,链表中的每个节点都是一个键值对。
2.第一次开辟的空间有多少?那初始化时指定开辟空间长度是否更有利于内存资源节省?
当使用默认构造函数创建HashMap对象时,会默认创建一个大小为16的数组。如果能够准确估计HashMap需要存储的键值对数量,指定容量大小进行初始化可以更有效利用内存资源,避免扩容操作。否则,使用默认大小进行初始化也是不错的选择,因为HashMap会自动进行扩容操作。
3.扩容是在什么时候扩的,达到的阈值是多少?初始申请的空间与扩容阈值之间的关系,围绕第二次扩容阈值与初始指定申请空间的关系说明?
当HashMap中存储的键值对数量达到容量的0.75时,就会进行扩容操作。初始申请的空间与扩容阈值有关系,如果我们能够准确估计HashMap需要存储的键值对数量,指定容量大小进行初始化可以更有效利用内存资源,避免过多的扩容操作。第二次扩容阈值为初始容量的0.5。
4.模拟写一个新增或删除或扩容的方法
// 定义哈希函数,使用Python内置的hash()函数
def hash_function(self, key):
return hash(key)
// 定义扩容方法
def resize(self):
# 保存旧的存储桶数组
old_buckets = self.buckets
# 新容量为旧容量的两倍
new_capacity = len(old_buckets) * 2
# 新建存储桶数组
self.buckets = [[] for _ in range(new_capacity)]
# 重置元素个数和阈值
self.size = 0
self.threshold = int(new_capacity * self.load_factor)
# 将旧存储桶数组中的元素重新哈希到新的存储桶数组中
for bucket in old_buckets:
for key, value in bucket:
self.add(key, value)
# 定义新增元素方法
def add(self, key, value):
# 计算哈希值
hashed_key = self.hash_function(key)
# 计算存储桶数组下标
index = hashed_key % len(self.buckets)
# 获取存储桶
bucket = self.buckets[index]
# 遍历存储桶,如果key已存在,则更新value
for i in range(len(bucket)):
if bucket[i][0] == key:
bucket[i] = (key, value)
return
# 如果key不存在,则添加新的键值对
bucket.append((key, value))
# 元素个数加1
self.size += 1
# 如果元素个数超过阈值,则触发扩容
if self.size >= self.threshold:
self.resize()
# 定义删除元素方法
def remove(self, key):
# 计算哈希值
hashed_key = self.hash_function(key)
# 计算存储桶数组下标
index = hashed_key % len(self.buckets)
# 获取存储桶
bucket = self.buckets[index]
# 遍历存储桶,如果找到key,则删除对应的键值对
for i in range(len(bucket)):
if bucket[i][0] == key:
del bucket[i]
# 元素个数减1
self.size -= 1
return
# 定义获取元素方法
def get(self, key):
# 计算哈希值
hashed_key = self.hash_function(key)
# 计算存储桶数组下标
index = hashed_key % len(self.buckets)
# 获取存储桶
bucket = self.buckets[index]
# 遍历存储桶,如果找到key,则返回对应的value
for i in range(len(bucket)):
if bucket[i][0] == key:
return bucket[i][1]
# 如果找不到key,则返回None
return None
5.是否线程安全?为何不安全?如果不安全如何规避或替代类?
该HashMap实现不是线程安全的。因为在多线程环境下,多个线程可能会同时访问和修改存储桶数组,导致数据不一致或者出现竞态条件等问题。
为了规避线程安全问题,可以使用线程安全的数据结构,例如Python标准库中的concurrent.futures.ThreadPoolExecutor
或concurrent.futures.ProcessPoolExecutor
。也可以使用第三方线程安全的HashMap库,例如concurrent.futures.ThreadPoolExecutor
或concurrent.futures.ProcessPoolExecutor
。
另外,如果需要实现自己的线程安全HashMap,可以使用锁机制来保护存储桶数组的访问和修改。例如,可以为每个存储桶数组的元素维护一个读写锁,保证同一时刻只有一个线程能够对该元素进行读写操作。这种方式虽然可以保证线程安全,但是会增加锁的开销,降低HashMap的性能表现。
6.在HashMap的使用中,for循环数据过程中删除数据是否异常?如果异常,寻找为何异常?如何规避这个异常?
在HashMap的使用中,如果在for循环遍历数据过程中删除数据,可能会抛出`ConcurrentModificationException`异常。这是因为在遍历过程中,如果对HashMap进行修改操作,会导致modCount计数器的值发生变化,而在遍历过程中会检查modCount计数器的值是否发生变化,如果发生变化就会抛出`ConcurrentModificationException`异常。
要规避这个异常,可以使用`Iterator`来遍历HashMap,而不是使用普通的for循环。`Iterator`提供了`remove()`方法来删除元素,并且在删除元素之后会更新modCount计数器的值。示例代码如下:
Map<String, String> map = new HashMap<>();
Iterator<Map.Entry<String, String>> iter = map.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry<String, String> entry = iter.next();
if (需要删除该元素) {
iter.remove(); // 使用Iterator的remove方法删除元素
}
}
另外,如果需要在多线程环境下遍历和修改HashMap,可以使用线程安全的ConcurrentHashMap来代替HashMap,它提供了线程安全的遍历和修改方法,不会抛出`ConcurrentModificationException`异常。
from concurrent.futures import ThreadPoolExecutor
from typing import Dict
# 创建一个ConcurrentHashMap
concurrent_map: Dict[str, str] = {"key1": "value1", "key2": "value2", "key3": "value3"}
# 使用ThreadPoolExecutor提交多个线程任务
with ThreadPoolExecutor(max_workers=10) as executor:
for key in concurrent_map.keys():
executor.submit(delete_key, key)
# 打印ConcurrentHashMap的内容
print(concurrent_map)
# 定义一个删除ConcurrentHashMap中指定key的函数
def delete_key(key: str) -> None:
if key in concurrent_map:
del concurrent_map[key]