1.Hash扰动函数解读
1.1 计算hash扰动函数值
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
如上HashMap中的hash扰动函数源码,首先,通过hashcode算出key的哈希值h,然后再将算出的h右移16位(int 为32bits,因此此步骤只是将高16移至低16位,高16位补0),最后将h与右移的值进行异或计算(值相同结果为0,不同结果为1)。过程如下图3-1
为什么要向右移动16位,在正常情况下,我们的集合大小都不会很大。因此,我们一般只会使用到低16位((数组长度-1)&hash)。因此,右移16位保留了低16位的信息,也尽量保留了高16位的信息。最后通过(h = key.hashCode()) ^ (h >>> 16),原始的信息都尽量保存在了低16位。
1.2 计算hash在数组中的映射
(first = tab[(n - 1) & hash]) != null) {
如上hashmap源码,最后通过hash的值与数组长度-1求出数组下标。
为什么要与上数组长度减一?1.因为hash值为32位(-2^32 - 2^31),所以值范围很大。只有与上数组长度减一才能更好的映射到数组中。
为什么源码中数组长度为2得n次幂?首先,因为0&任何值都是0,所以当某位为0时,0与上任何值不会改变结果,会导致大量碰撞(1110&1111 == 1110 &1110,15与16产生了碰撞),因此希望(数组长度-1)每一位尽量为一。
2. 项目中实际运用
int idx = (tableSum - 1) & (dbKeyAttr.hashCode() ^ (dbKeyAttr.hashCode() >>> 16));
// 3.根据下/每个数据库的表数,求出第几个数据库
int dbIdx = idx / dbRouterProperties.getTbCount() + 1;
// 3.根据第几个数据库,再算出第一个表
int tbIdx = idx % dbRouterProperties.getTbCount();
// 4.通过上下文保存数据的信息
// 设置库表信息到上下文,String.format("%02d", dbIdx),数据不为两位的话则在前面补0,这里的策略主要和设置的库表名称有关
// 例如: 库名称为test_01 那就写%02d。表名称user_001 对应%03d
DBContext.setDBKey(String.format("%02d", dbIdx));
DBContext.setTBKey(String.format("%03d", tbIdx));
项目源码解读:
idx / dbRouterProperties.getTbCount() + 1;分库数据库是从1开始编号,所以要加一。
int tbIdx = idx % dbRouterProperties.getTbCount();这里应该要加一,因为表也是从1开始。DBContext.setDBKey(String.format("%02d", dbIdx));通过ThreadLocal保存,方便上下文调用。