按位运算可能一开始看起来不太直观,但一旦理解了,你会发现它们非常容易理解。我将尝试解释这段代码在一个以172.16.0.1/23
为例的netspec
字符串中的作用。
第一部分 - CIDR转二进制
目标是从给定的CIDR前缀长度生成子网掩码的二进制表示。CIDR前缀长度只是子网掩码中的1
位数。第一行:
final int bits = 32 - Integer.parseInt(netspec.substring(netspec.indexOf('/')+1));
通过获取斜杠
/
的索引并解析其后的整数(在我的示例中为
23
),找到CIDR前缀长度。将32从该数字中减去,以获得子网掩码中
0
的数量——这些位也称为主机位。
在此示例中,我们知道我们正在处理
/23
前缀,它的子网掩码应如下所示:
n
代表网络(用于类B网络的16位),s
代表子网,h
代表主机。
对于我们来说,网络和子网位在功能上是相同的,但我进行了区分以保持精确。我们只关心主机位(其数量)。
nnnnnnnn nnnnnnnn sssssssh hhhhhhhh
11111111 11111111 11111110 00000000
最简单的方法是使用一个32位二进制数,所有位都是
1
,并在末尾9位处“填充”
0
。这就是第二行的作用:
您可以忽略bits == 32
检查,因为它不是那么相关,很可能只是一种优化。
//final int mask = (bits == 32) ? 0 : 0xFFFFFFFF - ((1 << bits)-1);
final int mask = 0xFFFFFFFF - ((1 << 9)-1);
0xFFFFFFFF
会给你一个32位的二进制数,所有位都是1
。1
左移9位(1 << bits
)将会得到512,512 - 1
的二进制为111111111
:
1 << 9 10 00000000
- 1 1
1 11111111
当你减去这些值时,你会得到二进制的子网掩码:
0xFFFFFFFF = 11111111 11111111 11111111 11111111
- (1 << 9)-1 = 1 11111111
--------------------------------------------------
11111111 11111111 11111110 00000000
这正是我们想要的网络掩码。
注意:这可能不是最直观的计算二进制值的方法。我喜欢从一个全为1的二进制数开始,该数字在有符号整数中具有十进制值-1
。然后我只需将其向左移动主机位数即可。(另外,如果您处理的整数大于32位,则可以使用0xFFFFFFFF进行掩码操作):
(-1 << 9) & 0xFFFFFFFF
第二部分 - 二进制转点分十进制
剩下的代码将二进制值转换为点分十进制表示法 —— 255.255.254.0。
return netspec.substring(0, netspec.indexOf('/') + 1) + // part of the netspec string before '/' -> IP address
Integer.toString(mask >> 24 & 0xFF, 10) + "." + // 11111111 & 0xFF = 0xFF
Integer.toString(mask >> 16 & 0xFF, 10) + "." + // 1111111111111111 & 0xFF = 0xFF
Integer.toString(mask >> 8 & 0xFF, 10) + "." + // 111111111111111111111110 & 0xFF = 0xFE
Integer.toString(mask >> 0 & 0xFF, 10); // 11111111111111111111111000000000 & 0xFF = 0x00
返回结果如下:
返回语句由多个字符串组成,以IP地址开头,后跟每个八位字节的十进制表示。二进制掩码向右移动(4-n)*8
位(其中n
是八位字节的编号),并使用二进制AND运算符与0xFF进行运算,仅获取最后8位,然后通过Integer.toString
进行解析。
结果为172.16.0.1/255.255.254.0
。