HashMap的底层实现原理

161 阅读2分钟

HashMap的原理与实现

image.png

image.png

image.png

  • 为什么使用数组+链表
  • 用LinkedList代替数组可以吗,既然是可以的,为什么不用反而用数组。 数组,也可以称为桶,通过连续的存储单元存储,需要指定下标存储,时间复杂度o(n) 线性链表,新增插入修改o(1),查找 时间复杂度o(n) 红黑树:

重要变量介绍:

ps:重要的变量

  • DEFAULT_initial_CAPACITY Table数组的初始化长度: 1 << 4``2^4=16(为什么要是 2的n次方?- 简化求余操作; 减少扩容时元素移动的概率,提高resize操作的性能。)

image.png 数组的初始长度就是默认的长度,是一个静态常量DEFAULT_INITIAL_CAPACITY = 16; ``` 对于 i << n,计算方式是 i * (2^n) 1<<4 = 1*(2^4) 对于 i >> n,计算方式是 i / (2^n)

  • maxImum_CAPACITY Table数组的最大长度: 1<<30 1*2^30=1073741824`

  • DEFAULT_load_factor  负载因子:默认值为0.75当元素的总个数>当前数组的长度 * 负载因子。数组会进行扩容,扩容为原来的两倍(todo:为什么是两倍?)

  • treeIFY_threShold 链表树 阙值: 默认值为 8 。 表示在一个node(Table)节点下的值的个数大于8时候,会将链表转换成为红黑树

  • UNTREEIFY_threShold 红黑树链 阙值: 默认值为 6 。 表示在进行扩容期间,单个Node节点下的红黑树节点的个数小于6时候,会将红黑树转化成为链表

  • MIN_treeify_CAPACITY = 64 最小树化阈值,当Table所有元素超过该值,才会进行树化(为了防止前期阶段频繁扩容和树化过程冲突)。

实现原理: 在HashMap中,采用 数组+链表 的方式来实现对数据的储存。

源码分析

/**
 * Constructs an empty <tt>HashMap</tt> with the specified initial
 * capacity and load factor.
 *
 * @param  initialCapacity the initial capacity
 * @param  loadFactor      the load factor
 * @throws IllegalArgumentException if the initial capacity is negative
 *         or the load factor is nonpositive
 */
public HashMap(int initialCapacity, float loadFactor) {
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal initial capacity: " +
                                           initialCapacity);
    if (initialCapacity > MAXIMUM_CAPACITY)
        initialCapacity = MAXIMUM_CAPACITY;
    if (loadFactor <= 0 || Float.isNaN(loadFactor))
        throw new IllegalArgumentException("Illegal load factor: " +
                                           loadFactor);
    this.loadFactor = loadFactor;
    this.threshold = tableSizeFor(initialCapacity);
}



/**
返回一个大于n且是最近的2的整次幂的数
 * Returns a power of two size for the given target capacity.
 */
static final int tableSizeFor(int cap) {
    int n = cap - 1;
    n |= n >>> 1;
    n |= n >>> 2;
    n |= n >>> 4;
    n |= n >>> 8;
    n |= n >>> 16;
    return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}


其他FQA List 转 Map

List转为Map<Object, List<Object>>

Map<Integer, List<String>> ans = new HashMap<>();
    for(String str: list) {
        List<String> sub = ans.get(str.length());
        if(sub == null) {
            sub = new ArrayList<>(); 
            ans.put(str.length(), sub); 
        }
        sub.add(str); 
}

jdk8+, for循环中的内容可以利用Map.computeIfAbsent来替换

Map<Integer, List<String>> ans = new HashMap<>();
for (String str : list) {
    ans.computeIfAbsent(str.length(), v -> new ArrayList<>()).add(str); 
}


default V computeIfAbsent(K key,
        Function<? super K, ? extends V> mappingFunction) {
    Objects.requireNonNull(mappingFunction);
    V v;
    if ((v = get(key)) == null) {
        V newValue;
        if ((newValue = mappingFunction.apply(key)) != null) {
            put(key, newValue);
            return newValue;
        }
    }

    return v;
}

jdk8+ 的写法

public static <K, V> Map<K, List<V>> toMapList(List<V> list, Function<V, K> func) { 
return list.stream().collect(Collectors.groupingBy(func));

}

jdk < 1.8的写法


public static <K, V> Map<K, List<V>> toMapList(List<V> list, KeyFunc<V, K> keyFunc) {
    Map<K, List<V>> result = new HashMap<>();
    for (V item : list) {
        K key = keyFunc.getKey(item);
        if (!result.containsKey(key)) {
            result.put(key, new ArrayList<>());
        }
        result.get(key).add(item);
    }
    return result;
}

public static interface KeyFunc<T, K> {
    K getKey(T t);
}


Map<Integer, List<String>> res = toMapList(list, new KeyFunc<String, Integer>() {
    @Override
    public Integer getKey(String s) {
        return s.length();
    }
});

Guava

 Map<Long, User> maps = Maps.uniqueIndex(userList, new Function<User, Long>() {
            @Override
            public Long apply(User user) {
                return user.getId();
            }
   });