1. == 和 equals 的区别

对于八大基本数据类型, == 比较的是值,对于引用类型, 比较的是对象的地址
equals是Object类的方法,默认实现是==,但在String等类中复写,比较对象的值相等与否,重写 equals 需要同步重写 hashcode 方法
==操作符对于String和包装类表现得异同

2. StringBuffer与StringBuilder

两者都是继承AbstractStringBuilder,区别在于StringBuffer把所有方法包括Override的方法都加上了synchronized关键字

3. final 修饰什么

final修饰类,则该类不能被继承
final修饰方法,则该方法不能被重写(Override)
final修饰变量,则该变量必须赋初始值,且不能被赋值第二次

4. static 修饰什么?修饰内部类和外部类的区别

static修饰类时不能修饰外部类,只能修饰内部类,修饰内部类时,该内部类和普通类一样
static也可以修饰变量,该变量属于类,在进程中内存独享

5. GC

6. HashMap

7. 反射

8. HashMap

HasnMap 的初始负载因子是 0.75f,
hash值计算方法:

  1. return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);

默认容量是 1<<4 即 16
最大容量是小于1 << 30 即小于 230 的最大2的幂

在 put 方法中,先判断 table[(n - 1) & hash] 存不存在,其中 n 是 table 的 length,这里的 (n - 1) & hash 很有说法,举个例子说明:

假设计算出的 hash = 0100_1110_0100_1100_0101_1100_0110_1010 这是一个 int 32 位的二进制形式 一个长度为 8 的HashMap,n - 1 = 7 即 0000_0000_0000_0000_0000_0000_0000_0111 这两者按位与&操作后,是:0000_0000_0000_0000_0000_0000_0000_0010 这其实是把 hash 值限定到了 table 长度之内,这样就可以把很大的 hash 值经过转换当做 table 的 index 同时,为什么 HashMap 的长度要是 2 的幂呢?,因为 2 的幂的二进制有特点: 2 = 0010 4 = 0100 8 = 1000 2 - 1 = 0001 4 - 1 = 0011 8 - 1 = 0111 所以为了配合 hash 值取下标,HashMap强行限制长度为 2 的幂

回到 put 方法,如果 (n - 1) & hash 作为 index 对应 table 中没有元素,则直接新建 Node 更新 value;
如果有元素且 hash 和 value 相等,则更新 value;
else if treeNode,更新 value
else 循环链表更新
但是如果过程中发现链表在插入后 == 8,则转换成红黑树,在转换成红黑树过程中,如果当前 table 长度小于 64,则用拓展 table 来代替转换成树

说下 HashMap 的扩容,
table 长度 * 2 就是新长度,然后遍历旧 table,如果是单节点,则 newTab[e.hash & (newCap - 1)] = e;
如果是树,执行树的相关方法
如果是该元素是链表头,则遍历该链表:
首先计算 (e.hash & oldCap) 这个值是 0 还是 oldCap(只能是这两个值)注意 oldCap 和 oldCap - 1 的二进制区别:

8 = 1000 8 - 1 = 0111 如果 e.hash = xxxx_1010 那么 e.hash & oldCap = xxxx_1010 & 0000_1000 = 0000_1000 = oldCap

遍历链表过程中,把 (e.hash & oldCap) 值为 0 的拿出来,放到新 table 的原位置,
把 (e.hash & oldCap) 值为 oldCap 的拿出来,放到 原位置 + oldCap 的地方去
那为什么不把这个链表放在新数组的同位置呢?为什么要多此一举?
我们考虑下,数组下标 = (容量 - 1)&hash,那么扩容后,容量变为2倍,那么数组下标就不再是定值,注意下链表中的元素经过 (oldCap - 1)&hash 计算后是相等的,但是容量*2后,相当于(oldCap -1)这个bit mask增加了一位(不理解的想想 8-1 = 7 和 16-1 = 15的二进制区别),所以多出来的这一位会造成链表中的元素不再是同一个index了,我们看下 HashMap 的具体算法
首先 HashMap 分了两个条件:
对于 (e.hash & oldCap) == 0 :(x代表未知数0或者1,每个x都是独立的随机0或1)

oldCap : 0000….0100….0000 hash : xxxx ….x0xx ….xxxx e.hash & oldCap : 0000….0000….0000 oldCap - 1 : 0000….0011….1111 (oldCap - 1)&hash : 0000….00xx….xxxx newCap - 1 : 0000….0111….1111 (newCap - 1)&hash : 0000….00xx….xxxx 可以看出(oldCap - 1)&hash == (newCap - 1)&hash ,也就是扩容后该元素数组下标不变

对于 (e.hash & oldCap) == oldCap:

oldCap : 0000….0100….0000 hash : xxxx ….x1xx ….xxxx e.hash & oldCap : 0000….0100….0000 oldCap - 1 : 0000….0011….1111 (oldCap - 1)&hash : 0000….00xx….xxxx newCap - 1 : 0000….0111….1111 (newCap - 1)&hash : 0000….01xx….xxxx 可以看出(newCap - 1)&hash = (oldCap - 1)&hash + oldCap,也就是扩容后该元素数组下标 + 旧数组容量

再说一下 tableSizeFor(int cap) 这个方法,这个方法的用处是返回大于 cap 的最小的2的幂

  1. static final int tableSizeFor(int cap) {
  2. int n = cap - 1;
  3. n |= n >>> 1;
  4. n |= n >>> 2;
  5. n |= n >>> 4;
  6. n |= n >>> 8;
  7. n |= n >>> 16;
  8. return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
  9. }

我们考虑任意一个数

000101101001

不小于他的2的幂应该是

000111111111 + 1

所以我们只要把一个数最高位1的后面全部是1 再加 1 就ok
那么第一次 n |= n >>> 1; 后最高位的1往后拓展了一位,变成了2位,第二次 n |= n >>> 2; 则让2位1变成4位,因为int是32位,所以 n |= n >>> 16; 后,全部 32 位都可以计算到
那为什么刚开始要 n = cap -1 呢?因为如果 cap 正好是2的幂,比如0100 = 4,那么返回 0111 + 0001 = 1000 = 8,这就不对了,所以减1防止这种情况发生