本文共 3881 字,大约阅读时间需要 12 分钟。
HashMap是一种基于散列(Hashing)的数据结构,适用于快速的键值对存储与检索。其主要优势在于:
与Hashtable相比,HashMap的优势体现在:
HashMap以数组为核心存储结构,每个数组元素称为桶(Bucket),每个桶可以存储一个或多个键值对。键值对由节点(Node)组成,每个节点包含以下信息:
HashMap中的哈希函数用于计算键的哈希值,从而确定键在数组中的位置。JDK8版本的哈希函数实现如下:
static final int hash(Object key) { if (key == null) { return 0; } int h = key.hashCode(); h = h ^ (h >>> 16); return h & (capacity - 1);} 哈希函数的作用是:
当插入键值对时,首先计算键的哈希值,确定对应的桶位置。如果该位置为空,直接存入;如果存在哈希冲突,链表形式逐个检查,直到找到空槽或替换旧值。
当链表长度超过8时,转换为红黑树以减少查询深度;链表长度小于6时,转回链表以优化性能。
哈希冲突的主要原因是不同的键可能有相同的哈希值。扰动函数通过增强哈希函数的混乱度,减少冲突概率。例如,JDK8的哈希函数采用了高低位异或和右移操作。
确保键对象不可变,并重写equals和hashCode方法。不可变性确保哈希值一致性,避免因对象状态变化导致哈希值不匹配。
选择不可变的对象(如String、Integer)作为键,减少哈希冲突。这些对象已预定义了equals和hashCode方法,保证一致性。
哈希函数的关键在于高效计算和分布均匀。JDK8版本的哈希函数通过高低位异或和右移操作,减少冲突。具体实现如下:
在实际应用中,哈希函数的选择至关重要。优化好的哈希函数能显著提升性能,减少碰撞率。
当链表长度超过8时,查询深度会显著增加,影响性能。因此,转换为红黑树以平衡查询深度。
红黑树的插入和删除操作需要额外的旋转操作,但查询深度仅为log级别,优于链表。
当哈希冲突发生时,采用探查序列逐个检查,直到找到空槽。常见探查方法包括线性探查和二次探查。
通过使用双哈希函数,降低碰撞概率。例如,计算两个不同哈希值,只有两者都冲突时才认为是碰撞。
默认负载因子为0.75。当容量达到75%时,触发扩容。新容量为原容量的两倍,重新计算哈希值并迁移数据。
在扩容过程中,多线程可能导致数据不一致。解决方法是确保扩容操作的线性时间内只允许一个线程进行。
扩容时,链表的顺序可能发生反转,避免尾部遍历带来的性能问题。
public V put(K key, V value) { // 计算哈希值 int hash = hash(key); int i = indexFor(hash, table.length); // 替换旧值或新增节点 for (Entry e = table[i]; e != null; e = e.next) { if (e.hash == hash && e.key.equals(key)) { e.value = value; return e.value; } } // 增加计数 modCount++; // 添加新节点 addEntry(hash, key, value, i); return null;} void addEntry(int hash, K key, V value, int bucketIndex) { Entry e = table[bucketIndex]; table[bucketIndex] = new Entry(hash, key, value, e); // 检查容量是否需要扩容 if (size++ >= threshold) { resize(2 * table.length); }} void resize(int newCapacity) { Entry[] oldTable = table; // 创建新数组 int oldCapacity = oldTable.length; Entry[] newTable = new Entry[newCapacity]; // 迁移数据 transfer(newTable); // 更新容量和阈值 table = newTable; threshold = (int)(newCapacity * loadFactor);} void transfer(Entry[] newTable) { Entry[] src = table; int newCapacity = newTable.length; for (int j = 0; j < src.length; j++) { Entry e = src[j]; if (e != null) { src[j] = null; do { Entry next = e.next; int i = indexFor(e.hash, newCapacity); e.next = newTable[i]; newTable[i] = e; e = next; } while (e != null); } }} Hashtable是线程安全的,但性能较低。所有方法都带有sync关键字,实现方法层次的同步。
ConcurrentHashMap采用锁分离技术,通过多个锁管理不同段,支持多个修改操作,性能优于Hashtable。
基于写时复制机制,读操作不需要锁。适用于读操作多于写操作的场景,但内存占用较高。
Vector是线程安全的随机访问容器,但性能较差,已被较少使用。
StringBuffer是线程安全的,支持多线程并发操作,但性能较低。
StringBuilder是不线程安全的,性能优于StringBuffer,适用于单线程或不需要并发控制的场景。
HashMap作为Java中最常用的散列表,其优点在于高效性和灵活性。通过优化哈希函数、处理链表与红黑树、解决哈希碰撞和容量扩展,可以进一步提升HashMap的性能。在并发环境下,选择适合的线程安全容器至关重要。
转载地址:http://wtczz.baihongyu.com/