Day18-集合(下)集合源码及面试题


HashSet源码

package com.dream.enum_util_class;

import java.util.EnumMap;
import java.util.Map.Entry;
import java.util.Set;

public class Test02 {

	public static void main(String[] args) {
		
		/**
		 * 
		 * EnumMap工具类
		 * 
		 * 将枚举类中对象放入Map集合中
		 */
		
		EnumMap<Signal, String> map = new EnumMap<>(Signal.class);
		map.put(Signal.RED, "红灯");
		map.put(Signal.YELLOW, "黄灯");
		map.put(Signal.GREEN, "绿灯");
		
		Set<Entry<Signal,String>> entrySet = map.entrySet();
		for (Entry<Signal, String> entry : entrySet) {
			Signal key = entry.getKey();
			String value = entry.getValue();
			System.out.println(key + " -- " + value);
		}
		
	}
}

理解HashSet底层由HashMap实现

HashMap源码

public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>{
    
    //默认长度
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;//16
    //默认负载因子
    static final float DEFAULT_LOAD_FACTOR = 0.75f;
    //最大长度
    static final int MAXIMUM_CAPACITY = 1 << 30;
    //负载因子
    final float loadFactor;//0.75
    //阈值(容量*负载因子)
    int threshold;//12
    //空实例的数组
    static final Entry<?,?>[] EMPTY_TABLE = {};
    //存放数据的容器
    transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;//new Entry[16]
    //hash掩码/hash随机值
    transient int hashSeed = 0;
    
    public HashMap() {
        this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
    }
    
    public HashMap(int initialCapacity, float loadFactor) {
        //initialCapacity = 16
        //loadFactor = 0.75
        
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        if (loadFactor <= 0 || Float.isNaN(loadFactor))//NAN:not a Number
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);

        this.loadFactor = loadFactor;
        threshold = initialCapacity;
        init();
    }
    
    public V put(K key, V value) {
        //第一次添加元素时进入的判断
        if (table == EMPTY_TABLE) {
            inflateTable(threshold);
        }
        //判断key是否为null
        if (key == null)
            return putForNullKey(value);
        //获取到key的hash值
        int hash = hash(key);
        //通过hash值获取数组中的下标
        int i = indexFor(hash, table.length);
        //e = 杨晨的Entry
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                //如果key相同,就替换value值,并返回老的value
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }

        modCount++;
        addEntry(hash, key, value, i);
        return null;
    }
    
    void resize(int newCapacity) {
        Entry[] oldTable = table;
        int oldCapacity = oldTable.length;//16
        //当容量 == 最大容量时
        if (oldCapacity == MAXIMUM_CAPACITY) {
            //阈值 = Integer.MAX_VALUE
            threshold = Integer.MAX_VALUE;
            return;
        }

        Entry[] newTable = new Entry[newCapacity];//new Entry[32];
        //数据的移位:把原来数组中的数据迁移到新数组中
        transfer(newTable, initHashSeedAsNeeded(newCapacity));
        table = newTable;
        //计算阈值
        threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
    }
    
    void transfer(Entry[] newTable, boolean rehash) {
        int newCapacity = newTable.length;
        for (Entry<K,V> e : table) {
            while(null != e) {
                Entry<K,V> next = e.next;
                if (rehash) {
                    e.hash = null == e.key ? 0 : hash(e.key);
                }
                int i = indexFor(e.hash, newCapacity);
                e.next = newTable[i];
                newTable[i] = e;
                e = next;
            }
        }
    }
    
    private V putForNullKey(V value) {
        for (Entry<K,V> e = table[0]; e != null; e = e.next) {
            if (e.key == null) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }
        modCount++;
        addEntry(0, null, value, 0);
        return null;
    }
    
    void addEntry(int hash, K key, V value, int bucketIndex) {
        //判断是否扩容
        if ((size >= threshold) && (null != table[bucketIndex])) {
            resize(2 * table.length);
            hash = (null != key) ? hash(key) : 0;
            bucketIndex = indexFor(hash, table.length);
        }

        createEntry(hash, key, value, bucketIndex);
    }
    
    void createEntry(int hash, K key, V value, int bucketIndex) {
        Entry<K,V> e = table[bucketIndex];
        table[bucketIndex] = new Entry<>(hash, key, value, e);//头插法
        size++;
    }
    
    static int indexFor(int h, int length) {
        return h & (length-1);
    }
    
    final int hash(Object k) {
        int h = hashSeed;
        //如果key是String类型,则计算hash值的过程中使用到hashSeed
        if (0 != h && k instanceof String) {
            return sun.misc.Hashing.stringHash32((String) k);
        }

        h ^= k.hashCode();

        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
    }
    
    private void inflateTable(int toSize) {//16
        // 传入的数字经过计算,都会或得到2的幂
        int capacity = roundUpToPowerOf2(toSize);//capacity = 16

        //threshold = 12
        threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
        table = new Entry[capacity];
        initHashSeedAsNeeded(capacity);
    }
    
    private static int roundUpToPowerOf2(int number) {//16
        return number >= MAXIMUM_CAPACITY
                ? MAXIMUM_CAPACITY
                : (number > 1) ? Integer.highestOneBit((number - 1) << 1) : 1;
    }
    
    //获取的hash掩码
    final boolean initHashSeedAsNeeded(int capacity) {
        boolean currentAltHashing = hashSeed != 0;
        boolean useAltHashing = sun.misc.VM.isBooted() &&
                (capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
        boolean switching = currentAltHashing ^ useAltHashing;
        if (switching) {
            hashSeed = useAltHashing
                ? sun.misc.Hashing.randomHashSeed(this)
                : 0;
        }
        return switching;
    }
    
    //映射关系类/节点类
    static class Entry<K,V> implements Map.Entry<K,V> {
        final K key; ----- key
        V value; --------- value
        Entry<K,V> next; - 下一个节点的地址
        int hash; -------- key的hash值

        Entry(int h, K k, V v, Entry<K,V> n) {
            value = v;
            next = n;
            key = k;
            hash = h;
        }
    }
    
}
  1. HashMap的数组长度为什么是2的幂?

    数组长度2的幂会使元素在数组中分布的更加均匀

    因为如果不是2的幂,length-1的二进制有可能某一位出现0,就会导致数组中某个下标上永远存不到元素,就会使得空间的浪费

  2. HashMap的扩容机制/扩容倍数?

    HashMap扩容的长度是原来的2倍

  3. HashMap默认负载因子是多少?

    0.75

  4. 为什么HashMap默认负载因子是0.75

    取得了时间和空间的平衡

    负载因子过小:存储元素个数较少的时候就扩容,浪费空间

    负载因子过大:存储元素个数较多的时候就扩容,浪费时间

  5. HashMap的数组最大长度是多少?

    1<<30

  6. HashMap的数组最大长度为什么是1<<30?

    HashMap数组长度必须是2的幂,并且数组长度的类型是int

    所以1<<30是int类型取值范围内能取到最大的2的幂

  7. 什么是HashMap的hash表

    就是数组

  8. 什么是hash碰撞/冲突

    准元素在数组中的下标上有元素,这就是hash碰撞

  9. 什么是hash桶

    Hash数组中的Entry,因为Entry中有个叫做next的属性,Entry就是一个单向列表

  10. HashMap存放null键的位置在哪?

    在数组中下标为0的位置

  11. HashMap扩容出现的问题/Hash回环/Hash死循环

    在扩容的情况下,Hash桶中Entry的next指向下一个Entry的地址出现重复

    一般Hash回环出现在多线程扩容下,出现这个问题都是程序员的责任,因为HashMap明确说明该集合是线程不安全,在多线程的情况下应该使用ConcurrentHashMap

  12. JDK1.8版本对于HashMap进行了哪些改动?

hash()进行了改进:将hashCode值认为有高16位和低16位,并且将高16位 ^ 低16位,让hash值分布更加均匀

JDK1.7:数组+链表的形式,头插法

JDK1.8:数组+链表+红黑树,尾插法

  1. JDK1.8版本HashMap为什么有黑红数的改进?

加入红黑树使得查询更有效率

  1. JDK1.8版本HashMap什么时候从链表转换为红黑树?

数组长度大于64,并且链表长度大于8

  1. JDK1.8版本HashMap为什么链表大于8就转换为红黑树?

因为泊松分布额统计概率学,统计出链表大于8的情况以及是及其微小了

经验:要防止hash碰撞,一定也要重写hashCode()

TreeSet源码

public class TreeSet<E> extends AbstractSet<E> implements NavigableSet<E>{
    
    private transient NavigableMap<E,Object> m;
    
    private static final Object PRESENT = new Object();
    
    public TreeSet() {
        this(new TreeMap<E,Object>());
    }
    
    public TreeSet(Comparator<? super E> comparator) {
        this(new TreeMap<>(comparator));
    }
    
    TreeSet(NavigableMap<E,Object> m) {
        this.m = m;
    }
    
    public boolean add(E e) {
        return m.put(e, PRESENT)==null;
    }
    
}

TreeSet底层由TreeMap实现

TreeMap源码

public class TreeMap<K,V> extends AbstractMap<K,V> implements NavigableMap<K,V>{
    
    private final Comparator<? super K> comparator;
    
    private transient Entry<K,V> root = null;
    
    public TreeMap() {
        comparator = null;
    }
    
    public TreeMap(Comparator<? super K> comparator) {
        this.comparator = comparator;
    }
    
    public V put(K key, V value) {
        //t = 麻生希的Entry
        Entry<K,V> t = root;
        if (t == null) {
            compare(key, key); //目的:1.检查Key是否为null 2.检查该类是否实现内置比较器

            root = new Entry<>(key, value, null);
            size = 1;
            modCount++;
            return null;
        }
        //比较结果
        int cmp;
        Entry<K,V> parent;
        
        Comparator<? super K> cpr = comparator;
        if (cpr != null) {//外置比较器的逻辑
            do {
                parent = t;
                cmp = cpr.compare(key, t.key);
                if (cmp < 0)
                    t = t.left;
                else if (cmp > 0)
                    t = t.right;
                else
                    return t.setValue(value);
            } while (t != null);
        }else {//内置比较器的逻辑
            
            if (key == null)
                throw new NullPointerException();
            Comparable<? super K> k = (Comparable<? super K>) key;
            do {
                //椎名空的Entry
                parent = t;
                //4
                cmp = k.compareTo(t.key);
                if (cmp < 0)
                    t = t.left;
                else if (cmp > 0)
                    t = t.right;
                else
                    return t.setValue(value);
            } while (t != null);
        }
        //北岛玲的Entry
        Entry<K,V> e = new Entry<>(key, value, parent);
        if (cmp < 0)
            parent.left = e;
        else
            parent.right = e;
        //红黑树的制衡
        fixAfterInsertion(e);
        size++;
        modCount++;
        return null;
    }
    
    final int compare(Object k1, Object k2) {
        return comparator==null ? ((Comparable<? super K>)k1).compareTo((K)k2)
            : comparator.compare((K)k1, (K)k2);
    }
    
    static final class Entry<K,V> implements Map.Entry<K,V> {
        K key;
        V value;
        Entry<K,V> left = null;
        Entry<K,V> right = null;
        Entry<K,V> parent;
        boolean color = BLACK;
        
        Entry(K key, V value, Entry<K,V> parent) {
            this.key = key;
            this.value = value;
            this.parent = parent;
        }
    }
    
}

外置比较器优先于 内置比较器

https://www.cs.usfca.edu/~galles/visualization/Algorithms.html

《算法导论》中对于红黑树的定义如下:

  1. 每个结点或是红的,或是黑的
  2. 根节点是黑的
  3. 每个叶结点是黑的
  4. 如果一个结点是红的,则它的两个儿子都是黑的
  5. 对每个结点,从该结点到其子孙节点的所有路径上包含相同数目的黑结点
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Helloword先生

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值