场景

现有场景是两个User对象的List,A中只有id跟用户名,B中有id跟登录时间,现在我们要把他们合成一个完成的User的List

我们直接暴力的的解法就是两个for循环,然后判断id是否相等,相等就给A中复制对应的登录时间。这样这段代码时间复杂度就是O(n^2)

然后一般的推荐做法就是我们将其中一个list转为Map,id为key,对象或者直接登录时间为value,然后只需要一遍for循环然后通过containsKey判断id,然后直接进行赋值,那么这样我们整体的时间复杂度就是O(n)了

探究

这里我们就来探究containsKey的时间复杂度究竟是多少呢?直接贴源码如下:(JDK1.8)

public boolean containsKey(Object key) {
		// 调用getNode方法,及hash方法通过该节点的key计算该节点的一个值
        return getNode(hash(key), key) != null;
    }
static final int hash(Object key) {
        int h;
        // key的hashcode高16位右移16位做一次异或运算得出值
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }
/**
     * Implements Map.get and related methods.
     *
     * @param hash hash for key
     * @param key the key
     * @return the node, or null if none
     */
    final Node<K,V> getNode(int hash, Object key) {
       	// 初始化局部变量,tab为我们map中的节点数组,first跟e为单个节点
       	// n为我们数组的长度,k就是我们key的类型
        Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
        // 进行判断  尤其注意这里:first = tab[(n - 1) & hash]
        // 我们知道我们想Hashmap中put数据的时候,是根据Hashmap中的
        // hash函数计算出key的值与数组长度-1进行与运算得出该节点在
        // 在hashmap底层数组放置的位置,所以这里及后续的判断如果准确
        // 找到了我们值,就直接返回,所以时间复杂度就是O(1)
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (first = tab[(n - 1) & hash]) != null) {
            if (first.hash == hash && // always check first node
                ((k = first.key) == key || (key != null && key.equals(k))))
                return first;
            // 接下来发现上面尽管key计算出来的位置一样,但是value不一
            // 样,所以就要顺着链表去寻找对应的值,因为jdk8里hashmap
            // 链表是到8转变为红黑树的,前面的8准确数字我们在计算时间
            // 复杂度可以看做是O(1),所以这里最坏情况应该就是红黑树的复
            // 杂度O(lgN)    
            if ((e = first.next) != null) {
                if (first instanceof TreeNode)
                    return ((TreeNode<K,V>)first).getTreeNode(hash, key);
                do {
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        return e;
                } while ((e = e.next) != null);
            }
        }
        return null;
    }

要解释的都在上面的源码注释里了,这里还可以扩展一下HashMap中为啥推荐我们初始化map的时候初始容量是2的幂次方,而且1.8后尽管你初始化随便写一个正整数,源码里都会做处理变成向上取一个最接近的2的幂次方的数呢?

就跟我们上面说的,put操作是通过hash函数针对key计算出来的值与数组长度减1进行与运算得出要放的位置的。

我们看看2的幂次方-1的二进制是1,11,111,1111,11111这些,这样根据与运算的性质都为1才是1就充分保证了随机性(因为我们之前就已经通过hash函数高16位与低16位异或了,通过大量的测试发现已经足够随机了),如果最后长度-1之后的二进制最后一个数是0,那么就意味着Hashmap中数组下标为奇数位置的地方永远就放不到节点。造成大量的空间浪费,跟提高hash冲突的概率。

所以我们可以想想,那个啥子网掩码是不是一样的道理…255.255.255.0
用来判断当前的ip是在哪个网段,因为255的二进制就是11111111,然后通过一些逻辑计算啥网络地址,主机地址,广播地址,然后又可以扩展计算机网络的知识了,算了,这部分知识道理,我也还不会,为了保证博客的单一职责原则,就不扩展了,以后再写…