问题引入
在一次解决redis热key问题时,打算通过计算"string".hashCode()%10
对key进行分散,打算放到10个不同的key中,代码如下所示,
public class Main
{
public static void main(String[] args) {
System.out.println("xxxxxxxx_id_0".hashCode()%10);
System.out.println("xxxxxxxx_id_1".hashCode()%10);
System.out.println("xxxxxxxx_id_2".hashCode()%10);
System.out.println("xxxxxxxx_id_3".hashCode()%10);
System.out.println("xxxxxxxx_id_4".hashCode()%10);
System.out.println("xxxxxxxx_id_5".hashCode()%10);
System.out.println("xxxxxxxx_id_6".hashCode()%10);
System.out.println("xxxxxxxx_id_7".hashCode()%10);
System.out.println("xxxxxxxx_id_8".hashCode()%10);
System.out.println("xxxxxxxx_id_9".hashCode()%10);
System.out.println("xxxxxxxx_id_10".hashCode()%10);
System.out.println("xxxxxxxx_id_11".hashCode()%10);
System.out.println("xxxxxxxx_id_12".hashCode()%10);
System.out.println("xxxxxxxx_id_13".hashCode()%10);
}
}
运行结果如下:
标准输出:
-1
0
-9
-8
-7
-6
-5
-4
-3
-2
8
9
0
1
通过观察结果可知,在java中取%时,在被除数为正数时,结果可能为负数,本来要分为10个key,结果分散到19个key中,导致意外的bug
求模与取余的概念
- 取余运算(“Remainder Operation ”),一般使用
rem
表示,求余(rem
)时的商是向0取整(java的%即为向0取整) - 取模运算(“Modulo Operation”),一般使用
mod
表示,求模(mod
)时的商是向下取整(向负无穷方向取整)
求模与取余的结果表格对照
假定被除数为a
,除数为b
求余表达式 | 商 | 求余结果 | 求模表达式 | 商 | 求模结果 | 结论 | |
---|---|---|---|---|---|---|---|
7 rem 4 | 1 | 3 | 7 mod 4 | 1 | 3 | a、b符号相同,求余和求模结果相同 | |
(-7) rem 4 | -1 | -3 | (-7) mod 4 | -2 | 1 | a、b符号不同,求余和求模结果不同 | |
7 rem (-4) | 1 | 3 | 7 mod (-4) | -2 | -1 | a、b符号不同,求余和求模结果不同 | |
(-7) rem (-4) | -1 | -3 | (-7) mod (-4) | -1 | -3 | a、b符号相同,求余和求模结果相同 |
求余操作的结果的符号取决于被除数的符号
在编译器中,两个异号的数取余之后的结果取决于分子的符号。负数%负数,编译器会将分母的负数自动转换为正整数,然后再将分子负数的负号提取出来,将两个正整数取余,最后的结果加上负号就好了。负数%正数,编译器先将分子负数的负号提取出来,将两个正整数取余,最后结果加上负号即可。正数%负数,编译器自动将分母负数转换为正整数,然后两个正整数取余得到就是最终结果
求模操作的结果的符号取决于除数的符号
java代码验证
public class Main
{
public static void main(String[] args)
{
System.out.println("------Remainder Operation------");
System.out.println(7 % 4);
System.out.println((-7) % 4);
System.out.println(7 % (-4));
System.out.println((-7) % (-4));
System.out.println("------Modulo Operation------");
System.out.println(Math.floorMod(7, 4));
System.out.println(Math.floorMod(-7, 4));
System.out.println(Math.floorMod(7, -4));
System.out.println(Math.floorMod(-7, -4));
}
}
输出值:
标准输出:
------Remainder Operation------
3
-3
3
-3
------Modulo Operation------
3
1
-1
-3
总结
对于整型数a,b来说,取模运算或者求余运算的方法都是:
- 求 整数商: c = a/b;
- 计算模或者余数: r = a – c*b.求模运算和求余运算在第一步不同:取余运算在取c的值时,向0 方向舍入(fix()函数);而取模运算在计算c的值时,向负无穷方向舍入(floor()函数)
例如计算:-7 Mod 4那么:a = -7;b = 4;
- 第一步:求整数商c,如进行求模运算c = -2(向负无穷方向舍入),求余c = -1(向0方向舍入);
- 第二步:计算模和余数的公式相同,但因c的值不同,求模时r = 1,求余时r = -3。
- 归纳:当a和b符号一致时,求模运算和求余运算所得的c的值一致,因此结果一致。当符号不一致时,结果不一样。求模运算结果的符号和b一致,求余运算结果的符号和a一致。另外各个环境下%运算符的含义不同,比如c/oc/c++,java 为取余,而python则为取模。
问题解决方法
根据上述可知:求模操作的结果的符号取决于除数的符号,因此我们应该将最开始代码中的%修改为Math.floorMod
public class Main
{
public static void main(String[] args) {
System.out.println(Math.floorMod("xxxxxxxx_id_0".hashCode(),10));
System.out.println(Math.floorMod("xxxxxxxx_id_1".hashCode(),10));
System.out.println(Math.floorMod("xxxxxxxx_id_2".hashCode(),10));
System.out.println(Math.floorMod("xxxxxxxx_id_3".hashCode(),10));
System.out.println(Math.floorMod("xxxxxxxx_id_4".hashCode(),10));
System.out.println(Math.floorMod("xxxxxxxx_id_5".hashCode(),10));
System.out.println(Math.floorMod("xxxxxxxx_id_6".hashCode(),10));
System.out.println(Math.floorMod("xxxxxxxx_id_7".hashCode(),10));
System.out.println(Math.floorMod("xxxxxxxx_id_8".hashCode(),10));
System.out.println(Math.floorMod("xxxxxxxx_id_9".hashCode(),10));
System.out.println(Math.floorMod("xxxxxxxx_id_10".hashCode(),10));
System.out.println(Math.floorMod("xxxxxxxx_id_11".hashCode(),10));
System.out.println(Math.floorMod("xxxxxxxx_id_12".hashCode(),10));
System.out.println(Math.floorMod("xxxxxxxx_id_13".hashCode(),10));
}
}
结果如下:
标准输出:
9
0
1
2
3
4
5
6
7
8
8
9
0
1
参考文档
取余和取模的区别:https://www.runoob.com/w3cnote/remainder-and-the-modulo.html
百度百科:https://baike.baidu.com/item/%E5%8F%96%E6%A8%A1%E8%BF%90%E7%AE%97/10739384?fr=aladdin
Java取模和取余,你真的弄懂了吗:https://www.cnblogs.com/doondo/p/14678204.html
编程语言中的 % 是取模 还是 取余?:https://cloud.tencent.com/developer/news/267970
负数取余问题:https://blog.csdn.net/qq_43152052/article/details/101023628