hashcode返回值可能为负数
公司内部做服务优化,线上单机部署多个redis实例,路由到同一台机器上的用户,id号段假设为1000000~9999999,同一个的用户信息肯定是要固定到某个redis实例的,所以需要一个算法,保证每次选择的redis实例都是一样的。最容易想到的就是用id对redis实例个数取余,但如果以后id换为字符串呢?这种取余算法就不合适了。
之后想到可以使用hashcode,把id转换为String,对之取hashcode,然后用hashcode对redis实例个数取余,同样的字符串,计算得到的hashcode肯定是一致的,也就解决了一致性选择redis实例的问题。部分代码如下:
//key为String类型
int hash = 0;
if (StringUtils.isNotBlank(key)) {
hash = key.hashCode()
}
//根据basePort做偏移,选取不同的redis实例
jedis = getClientFromPool(host, config.basePort + hashcode % redisServerCount);
考虑到字符串可能为空(程序bug或未知原因),默认hashcode为0,根据不同的hashcode,对basePort做偏移初始化的jedis。本以为代码写的很健壮,运行起来肯定万事大吉,直接手工。
但是,事与愿违,服务运行时总是报奇怪的错误如下,究其原因就是,jedis没有初始化,无法从jedis的连接池中获取链接,本机redis服务监听的端口好好的,redis服务本身没有问题,怎么会这样呢?
java.lang.NullPointerException
at com.xiaomi.xmpush.redis.JedisClient.set(JedisClient.java:490)
at com.xiaomi.xmpush.redis.JedisClient.set(JedisClient.java:484)
redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource from the pool
at redis.clients.util.Pool.getResource(Pool.java:53)
at redis.clients.jedis.JedisPool.getResource(JedisPool.java:226)
at com.xiaomi.xmpush.redis.JedisClient.getClientFromPool(JedisClient.java:69)
那就只可能是代码的问题,无法创建redis实例的链接,那可能是port选的不对?port为什么会不对呢,hashcode取余,保证不会溢出啊!心中很纳闷,怀疑可能是hashcode搞得鬼,果然,增加了一些debug log之后,竟然发现hashcode的值可能为负数。比如这货
long userId = 56800004874L;
System.out.println(String.valueOf(userId).hashCode() );
输出:-2082984168
再看hashcode的源码,返回值是int型,确实可能是负数。。
/**
* Returns a hash code for this string. The hash code for a
* {@code String} object is computed as
* <blockquote><pre>
* s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
* </pre></blockquote>
* using {@code int} arithmetic, where {@code s[i]} is the
* <i>i</i>th character of the string, {@code n} is the length of
* the string, and {@code ^} indicates exponentiation.
* (The hash value of the empty string is zero.)
*
* @return a hash code value for this object.
*/
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
大家都知道,int型的值取值范围为Integer.MIN_VALUE(-2147483648)~Integer.MAX_VALUE(2147483647),那怎么修改呢?可以想到取绝对值,那Integer.MIN_VALUE的绝对值是多少呢?正数发生了溢出啊?
Math.abs(Integer.MIN_VALUE) = Integer.MIN_VALUE
看来这样行不通,如果对hash做数据偏移,统一加2147483647呢?No no,这样更是错误,因为更会发生数据溢出,会产生负数,有一种牛逼的办法就是使用位与操作:
hash = key.hashCode() & Integer.MAX_VALUE; // caution, make value not minus
对于计算得到的hash值,强制把符号位去掉,保证结果只在正数区间,至此,把hashcode关进笼子了,问题解决。
文章转载自:https://blog.csdn.net/chanllenge/article/details/85673259