zoukankan      html  css  js  c++  java
  • hashcode返回值可能为负数

    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

  • 相关阅读:
    《华东交通大学2018年ACM“双基”程序设计竞赛*补》
    《多校补题》
    《HDU多校第五场》
    前端开发框架
    Myeclipse Weblogic Launches下的classpath配置文件目录
    正则表达式:元字符 简
    Freemarker
    SSM整合
    MySQL基础
    Redis与Spring Data Redis
  • 原文地址:https://www.cnblogs.com/k-class/p/13773161.html
Copyright © 2011-2022 走看看