为什么jdk1.8之前HashMap是由List+链表组成?

103 阅读3分钟

@TOC

在这里插入图片描述 欢迎关注微信公众号:数据科学与艺术

在jdk1.8之前,Java中的HashMap实现是由数组+链表的方式组成的。这样的设计是为了解决冲突问题,即当两个不同的键值映射到数组的同一个位置时,需要通过链表将它们连接起来。

优点:

  1. 实现简单:使用链表的方式可以方便地处理冲突问题,实现相对简单。
  2. 空间利用率高:链表的插入和删除操作相对高效,可以更好地利用内存空间。

缺点:

  1. 查找效率低:由于链表只能顺序查找,当需要查找特定键值对时,需要遍历整个链表,效率较低。
  2. 遇到长链表时性能下降:如果链表过长,会导致插入和查找操作的性能下降,因为需要遍历整个链表。

链表是由一系列节点组成的数据结构,每个节点包含一个数据元素和一个指向下一个节点的指针。链表的特点是插入和删除操作比较高效,但是查找操作需要遍历整个链表。

代码实现

在JDK1.8之前,HashMap是由数组+链表的形式实现的。下面是一个简单的代码示例:

import java.util.LinkedList;

public class HashMapBeforeJDK1_8<K, V> {
    private LinkedList<Pair<K, V>>[] buckets;
    private int capacity;
    
    public HashMapBeforeJDK1_8(int capacity) {
        this.capacity = capacity;
        buckets = new LinkedList[capacity];
    }
    
    public void put(K key, V value) {
        int index = indexForKey(key);
        if (buckets[index] == null) {
            buckets[index] = new LinkedList<>();
        }
        
        LinkedList<Pair<K, V>> bucket = buckets[index];
        
        Pair<K, V> newPair = new Pair<>(key, value);
        for (Pair<K, V> pair : bucket) {
            if (pair.getKey().equals(key)) {
                pair.setValue(value);
                return;
            }
        }
        
        bucket.add(newPair);
    }
    
    public V get(K key) {
        int index = indexForKey(key);
        LinkedList<Pair<K, V>> bucket = buckets[index];
        
        if (bucket != null) {
            for (Pair<K, V> pair : bucket) {
                if (pair.getKey().equals(key)) {
                    return pair.getValue();
                }
            }
        }
        
        return null;
    }
    
    private int indexForKey(K key) {
        return Math.abs(key.hashCode()) % capacity;
    }
    
    private static class Pair<K, V> {
        private K key;
        private V value;
        
        public Pair(K key, V value) {
            this.key = key;
            this.value = value;
        }
        
        public K getKey() {
            return key;
        }
        
        public V getValue() {
            return value;
        }
        
        public void setValue(V value) {
            this.value = value;
        }
    }
}

在这个示例中,HashMapBeforeJDK1_8类使用一个数组来表示HashMap的桶,每个桶是一个LinkedList<Pair<K, V>>类型的链表。put()方法用于向HashMap中添加键值对,get()方法用于根据键获取对应的值。indexForKey()方法用于计算键的哈希值并映射到对应的桶。Pair<K, V>是一个简单的键值对类,用于存储键值对的数据。

分析

以一个简单的案例来说明,假设我们要存储学生的姓名和对应的分数。使用HashMap可以实现快速根据姓名查找分数的功能。当两个不同学生的姓名映射到同一个数组位置时,HashMap会使用链表将它们连接起来。

例如,我们有两个学生"张三"和"李四",他们的姓名都映射到数组的索引位置1。如果使用了链表结构,将两个学生的信息存储在同一个位置上,可以更好地利用空间。当我们需要查找"张三"的分数时,只需要遍历链表即可。

而如果使用数组结构,则需要使用额外的数据结构存储冲突的元素,例如使用List。在这种情况下,当两个学生的姓名映射到同一个数组位置时,需要将它们存储在同一个List中。如果我们要查找"张三"的分数,需要遍历整个List才能找到对应的分数。这样的设计会导致查找效率的降低。

因此,通过使用数组+链表的方式实现HashMap可以在一定程度上提高空间利用率和插入/删除操作的效率,但在查找操作上有一些性能上的牺牲。在jdk1.8之后,HashMap的实现方式进行了改进,引入了红黑树来替代链表,以优化查找操作的性能。