HashMap的实现

本文详细解析了HashMap的工作原理,包括其线程安全性和与HashTable的区别。探讨了HashMap如何处理哈希冲突,链表转红黑树的过程,以及其在多线程环境下的表现。同时对比了HashSet与HashMap的实现差异。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

HashMap与HashTable的区别

这里写图片描述

  • HashMap不是线程安全的,而HashTable是线程安全的,但是最好用ConcurrentHashMap代替。或Map m = Collections.synchronizeMap(hashMap)
  • 两者的继承类也不同
  • HashTable的key和value都不可以是null, ConcurrentHashMap也一样。而HashMap可以有一个键(只能一个)为null,而value的值可以为null。用HashMap的get()方法返回null,有2种可能(1.没有该键;2.该键对应的值为null),所以应该用containsKey()方法来判断。
  • 哈希值的使用不同,HashTable直接使用对象的hashCode。而HashMap重新计算hash值

HashMap的实现

  • Map类型的实现类,要求key为不可变对象,即其hashCode不能改变,如果发生改变的话,Map对象就可能定位不到映射的value。
  • 从实现来说:由数组+单链表+红黑树(1.8新增)

这里写图片描述

  • 数组:transient Node<K,V>[] table;初始化和扩展数组在resize()方法内,在第一次调用HashMap的put()时会去初始化数组,默认大小为16,门槛threshold为size*0.75,当put等操作后会判断是否需要去扩充。
  • 单链表:Node实现自Map.Entry接口
FieldsMethods
final int hashK getKey()
K keyV getValue()
V valueV setValue()=>用于更新
Node<K,V> nexthashCode(), equal(), toString()

*

  • #### 计算hash
static final int hash(Object key) {   //jdk1.8 & jdk1.7
     int h;
     // h = key.hashCode() 为第一步 取hashCode值
     // h ^ (h >>> 16)  为第二步 取高16位与低16位参与异或运算
     return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);

这里写图片描述

  • put()操作

这里写图片描述

 1 public V put(K key, V value) {
 2     // 对key的hashCode()做hash
 3     return putVal(hash(key), key, value, false, true);
 4 }
 5
 6 final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
 7                boolean evict) {
 8     Node<K,V>[] tab; Node<K,V> p; int n, i;
 9     // 步骤①:tab为空则创建
10     if ((tab = table) == null || (n = tab.length) == 0)
11         n = (tab = resize()).length;
12     // 步骤②:计算index,并对null做处理 
13     if ((p = tab[i = (n - 1) & hash]) == null) 
14         tab[i] = newNode(hash, key, value, null);
15     else {
16         Node<K,V> e; K k;
17         // 步骤③:节点key存在,直接覆盖value
18         if (p.hash == hash &&
19             ((k = p.key) == key || (key != null && key.equals(k))))
20             e = p;
21         // 步骤④:判断该链为红黑树
22         else if (p instanceof TreeNode)
23             e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
24         // 步骤⑤:该链为链表
25         else {
26             for (int binCount = 0; ; ++binCount) {
27                 if ((e = p.next) == null) {
28                     p.next = newNode(hash, key,value,null);
                        //链表长度大于8转换为红黑树进行处理
29                     if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st  
30                         treeifyBin(tab, hash);
31                     break;
32                 }
                    // key已经存在直接覆盖value
33                 if (e.hash == hash &&
34                     ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
36                 p = e;
37             }
38         }
39        
40         if (e != null) { // existing mapping for key
41             V oldValue = e.value;
42             if (!onlyIfAbsent || oldValue == null)
43                 e.value = value;
44             afterNodeAccess(e);
45             return oldValue;
46         }
47     }

48     ++modCount;
49     // 步骤⑥:超过最大容量 就扩容
50     if (++size > threshold)
51         resize();
52     afterNodeInsertion(evict);
53     return null;
54 }
  • resize()方法

 1 final Node<K,V>[] resize() {
 2     Node<K,V>[] oldTab = table;
 3     int oldCap = (oldTab == null) ? 0 : oldTab.length;
 4     int oldThr = threshold;
 5     int newCap, newThr = 0;
 6     if (oldCap > 0) {
 7         // 超过最大值就不再扩充了,就只好随你碰撞去吧
 8         if (oldCap >= MAXIMUM_CAPACITY) {
 9             threshold = Integer.MAX_VALUE;
10             return oldTab;
11         }
12         // 没超过最大值,就扩充为原来的2倍
13         else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
14                  oldCap >= DEFAULT_INITIAL_CAPACITY)
15             newThr = oldThr << 1; // double threshold
16     }
17     else if (oldThr > 0) // initial capacity was placed in threshold
18         newCap = oldThr;
19     else {               // zero initial threshold signifies using defaults
20         newCap = DEFAULT_INITIAL_CAPACITY;
21         newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
22     }
23     // 计算新的resize上限
24     if (newThr == 0) {
25
26         float ft = (float)newCap * loadFactor;
27         newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
28                   (int)ft : Integer.MAX_VALUE);
29     }
30     threshold = newThr;
31     @SuppressWarnings({"rawtypes""unchecked"})
32         Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
33     table = newTab;
34     if (oldTab != null) {
35         // 把每个bucket都移动到新的buckets中
36         for (int j = 0; j < oldCap; ++j) {
37             Node<K,V> e;
38             if ((e = oldTab[j]) != null) {
39                 oldTab[j] = null;
40                 if (e.next == null)
41                     newTab[e.hash & (newCap - 1)] = e;
42                 else if (e instanceof TreeNode)
43                     ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
44                 else { // 链表优化重hash的代码块
45                     Node<K,V> loHead = null, loTail = null;
46                     Node<K,V> hiHead = null, hiTail = null;
47                     Node<K,V> next;
48                     do {
49                         next = e.next;
50                         // 原索引
51                         if ((e.hash & oldCap) == 0) {
52                             if (loTail == null)
53                                 loHead = e;
54                             else
55                                 loTail.next = e;
56                             loTail = e;
57                         }
58                         // 原索引+oldCap
59                         else {
60                             if (hiTail == null)
61                                 hiHead = e;
62                             else
63                                 hiTail.next = e;
64                             hiTail = e;
65                         }
66                     } while ((e = next) != null);
67                     // 原索引放到bucket里
68                     if (loTail != null) {
69                         loTail.next = null;
70                         newTab[j] = loHead;
71                     }
72                     // 原索引+oldCap放到bucket里
73                     if (hiTail != null) {
74                         hiTail.next = null;
75                         newTab[j + oldCap] = hiHead;
76                     }
77                 }
78             }
79         }
80     }
81     return newTab;
82 }
  • 非线程安全
  • 在put()方法中会遍历链表,在多线程情况链表会成环。造成死循环。
  • 链表转为红黑树
final void treeify(Node<K,V>[] tab) {
            TreeNode<K,V> root = null;
            for (TreeNode<K,V> x = this, next; x != null; x = next) {
                next = (TreeNode<K,V>)x.next;
                x.left = x.right = null;
                if (root == null) {
                    x.parent = null;
                    x.red = false;
                    root = x;
                }
                else {
                    K k = x.key;
                    int h = x.hash;
                    Class<?> kc = null;
                    for (TreeNode<K,V> p = root;;) {
                        int dir, ph;
                        K pk = p.key;
                        if ((ph = p.hash) > h)
                            dir = -1;
                        else if (ph < h)
                            dir = 1;
                        else if ((kc == null &&
                                  (kc = comparableClassFor(k)) == null) ||
                                 (dir = compareComparables(kc, k, pk)) == 0)
                            dir = tieBreakOrder(k, pk);

                        TreeNode<K,V> xp = p;
                        if ((p = (dir <= 0) ? p.left : p.right) == null) {
                            x.parent = xp;
                            if (dir <= 0)
                                xp.left = x;
                            else
                                xp.right = x;
                            root = balanceInsertion(root, x);
                            break;
                        }
                    }
                }
            }
            moveRootToFront(tab, root);
        }

HashSet的实现,和HashMap的区别

  • 都实现了Set接口,仅存储不同的对象。
  • HashSet内部封装了一个HashMap对象,仅保存不同的Object作为键。内部有一个private static final Object PRESENT = new Object();作为所有键的值
  • TreeSet内部封装一个NavigableMap引用,默认构造器传入一个TreeMap对象,也可以传入别的实现NavigableMap接口的对象。(JDK中另一个实现的叫ConcurrentSkipListSet)
  • LinkedHashSet调用父类HashSet的构造器,改为封装一个LinkedHashMap。LinkedHashMap继承了HashMap,其内部重写了newNode()方法【put()的底层实现】,当调用put()的时候会将先将这个节点放进一个双链表的尾部,为的是遍历Set时候能按照添加顺序来。
CH341A编程器是一款广泛应用的通用编程设备,尤其在电子工程和嵌入式系统开发领域中,它被用来烧录各种类型的微控制器、存储器和其他IC芯片。这款编程器的最新版本为1.3,它的一个显著特点是增加了对25Q256等32M芯片的支持。 25Q256是一种串行EEPROM(电可擦可编程只读存储器)芯片,通常用于存储程序代码、配置数据或其他非易失性信息。32M在这里指的是存储容量,即该芯片可以存储32兆位(Mbit)的数据,换算成字节数就是4MB。这种大容量的存储器在许多嵌入式系统中都有应用,例如汽车电子、工业控制、消费电子设备等。 CH341A编程器的1.3版更新,意味着它可以与更多的芯片型号兼容,特别是针对32M容量的芯片进行了优化,提高了编程效率和稳定性。26系列芯片通常指的是Microchip公司的25系列SPI(串行外围接口)EEPROM产品线,这些芯片广泛应用于各种需要小体积、低功耗和非易失性存储的应用场景。 全功能版的CH341A编程器不仅支持25Q256,还支持其他大容量芯片,这意味着它具有广泛的兼容性,能够满足不同项目的需求。这包括但不限于微控制器、EPROM、EEPROM、闪存、逻辑门电路等多种类型芯片的编程。 使用CH341A编程器进行编程操作时,首先需要将设备通过USB连接到计算机,然后安装相应的驱动程序和编程软件。在本例中,压缩包中的"CH341A_1.30"很可能是编程软件的安装程序。安装后,用户可以通过软件界面选择需要编程的芯片类型,加载待烧录的固件或数据,然后执行编程操作。编程过程中需要注意的是,确保正确设置芯片的电压、时钟频率等参数,以防止损坏芯片。 CH341A编程器1.3版是面向电子爱好者和专业工程师的一款实用工具,其强大的兼容性和易用性使其在众多编程器中脱颖而出。对于需要处理25Q256等32M芯片的项目,或者26系列芯片的编程工作,CH341A编程器是理想的选择。通过持续的软件更新和升级,它保持了与现代电子技术同步,确保用户能方便地对各种芯片进行编程和调试。
<think>好的,我现在需要帮助用户了解如何在Java中使用HashMap实现键值对映射。根据用户的问题,他们可能对HashMap的基本用法、实现原理以及相关注意事项感兴趣。首先,我应该整理用户提供的引用内容,看看有哪些信息可以直接利用。 用户提供了四个引用,其中引用[1]提到了HashMap的基本操作,包括创建实例、添加、获取、检查、删除和遍历键值对。引用[2]和[4]讨论了如何排序HashMap,而引用[3]强调了键对象必须正确实现hashCode()和equals()方法。这些信息都是回答用户问题的重要部分。 首先,我需要从基础开始,逐步引导用户了解HashMap的使用。应该先介绍如何创建HashMap实例,然后依次讲解添加、获取、检查存在性、删除和遍历操作。这部分内容在引用[1]中有示例代码,可以直接参考。例如,创建实例的部分需要导入java.util.HashMap,并初始化泛型类型。 接下来,键对象的hashCode()和equals()方法的正确实现是关键,这一点在引用[3]中提到。需要解释为什么这两个方法很重要,以及如何正确覆盖它们,尤其是在使用自定义对象作为键时。比如,如果两个对象相等,它们的hashCode必须相同,否则会导致HashMap无法正确找到对应的值。 关于排序,用户可能想知道虽然HashMap本身无序,但可以通过转换为List并排序来实现,如引用[2]和[4]所述。这部分可以作为进阶内容,说明如何按键或值排序,可能需要使用Comparator和Collections.sort方法。 在实现方式方面,用户可能对HashMap的底层数据结构感兴趣。需要解释HashMap是基于哈希表实现的,使用数组和链表(或红黑树,在Java 8之后)来处理冲突。这涉及负载因子、扩容机制等概念,这些内容在引用[2]的源码分析部分有所提及。 还要注意示例代码的正确性,确保语法无误,比如泛型的使用和方法的正确调用。例如,在遍历键值对时,使用entrySet()获取所有条目,然后遍历每个Entry对象来获取键和值。 最后,生成相关问题时要涵盖用户可能进一步探索的方向,比如性能优化、线程安全、处理冲突的方法以及与其他集合类的比较。这些问题可以帮助用户深入理解HashMap的应用场景和限制。 总结一下,回答的结构应该包括基本用法、实现原理、注意事项和进阶操作,每个部分结合引用内容,确保信息准确。同时,示例代码要清晰,注释详细,帮助用户快速上手。</think>### 使用HashMap实现键值对映射 #### 1. **HashMap的基本用法** HashMap是Java中基于哈希表的键值对存储结构,支持高效插入、删除和查找操作。以下是核心实现步骤: **1.1 创建HashMap实例** ```java import java.util.HashMap; public class Main { public static void main(String[] args) { // 声明键为String类型,值为Integer类型的HashMap HashMap<String, Integer> map = new HashMap<>(); } } ``` 键值类型需通过泛型指定,支持任意对象类型[^1]。 **1.2 添加键值对** ```java map.put("apple", 10); // 添加键"apple"对应值10 map.put("banana", 5); // 添加键"banana"对应值5 ``` 若键已存在,新值会覆盖旧值[^1]。 **1.3 获取值** ```java int count = map.get("apple"); // 返回10 int missing = map.getOrDefault("grape", 0); // 键不存在时返回默认值0 ``` **1.4 检查键/值存在性** ```java boolean hasKey = map.containsKey("apple"); // true boolean hasValue = map.containsValue(5); // true ``` **1.5 删除键值对** ```java map.remove("banana"); // 删除键为"banana"的条目 ``` **1.6 遍历操作** ```java // 遍历所有键值对 for (HashMap.Entry<String, Integer> entry : map.entrySet()) { System.out.println(entry.getKey() + ": " + entry.getValue()); } ``` --- #### 2. **实现原理** - **数据结构**:基于数组+链表(Java 8后链表长度>8时转为红黑树)[^2]。 - **哈希函数**:键的`hashCode()`计算哈希值,决定存储位置。 - **冲突解决**:链表法处理哈希冲突,Java 8优化为红黑树提升查询效率[^2]。 - **扩容机制**:默认负载因子$0.75$,容量翻倍时重新哈希[^2]。 --- #### 3. **键对象的特殊要求** - **`hashCode()`与`equals()`**:必须正确覆盖这两个方法,保证: - 相等的对象必须返回相同的哈希值。 - 不相等的对象尽量返回不同的哈希值(减少冲突)[^3]。 **示例:自定义键对象** ```java class Student { String id; @Override public int hashCode() { return id.hashCode(); } @Override public boolean equals(Object obj) { return obj instanceof Student && this.id.equals(((Student)obj).id); } } ``` --- #### 4. **进阶操作:排序** HashMap本身无序,但可通过以下方式排序: ```java // 按键排序 List<String> sortedKeys = new ArrayList<>(map.keySet()); Collections.sort(sortedKeys); // 按值排序(需借助Stream API) List<Map.Entry<String, Integer>> sortedEntries = map.entrySet() .stream() .sorted(Map.Entry.comparingByValue()) .collect(Collectors.toList()); ``` 此方法利用了列表和比较器实现排序[^4]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值