谈谈java中& 0xFF

上篇文章写了在java中常用的操作运算符,其内容很基础,大多数开发的时候可能只会用到移位算法,而与或非操作则通常会有其他使用方式,比如我在看《HowTomcatWorks》解析http请求头文件时使用了 &0xFF,其实我是有些懵b的,我不能直观的一下感受到这样的操作结果是要做什么,于是便有了上篇文章做基础,这篇文章来写 & 0xFF是怎么回事。

在java的计算领域中,使用三种进制来存储和处理数值,分别是二进制、十进制、十六进制,其中二进制是隐式方式处理,没有哪种数据类型是使用bit二进制来直接表示的,也没有一种数据类型可以直观的记录一个二进制,所以我自己将其成为隐式的。十进制是我们日常生活用常用的数字表达形式,常用int、long 等类型表示整型,float、double表示浮点数等,接下来说说十六进制

十六进制是平时开发不太常用的一种进制形式,使用0x为开头表示,比如Integer的最大值是使用

public static final int MAX_VALUE = 0x7fffffff;

这种形式来表示,而不是MAX_VALUE = 2147483647,虽然长度看起来差不多。。。但如果熟悉了十六进制就可以直观的看出这一段表示的意思

现在来说说进制到底是怎么处理的

二进制:满2进1,当前位置0,比如0+1等于1不够2,则还是显示1,如果再加1得2那么应该显示为进1,就是高位加1,当前位置0,就是{1 0}=十进制2

十进制:满10进1,当前位置0,与上面差不多,1+1是2不够10则不进位,当加到9时,再加1构成十,高位加(进)1,当前置0就变成了10,达到19的时候再加1时,高位加(进)1,当前位置0就变成了20

十六进制:满16进1,当前位置0,同样1+1是2,不够16不进位,当达到15时再加1时是16,满16就需要进位了,高位加1,当前位置0,就是10。。。那么问题是 9+1的10 和15+1的10没法区分,然后科学家们就想了办法,用几个英文字母替代10及之后的数值,直到15,下面是十六进制与10进制对照

十进制 : 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

十六进制:0 1 2 3 4 5 6 7 8 9 A B C D E F

继续深入对比

16 18 32 100 127 190 255

(1*16+0)10 (1*16+2)12 (2*16+0)20 (6*16+4)64 (7*16+15)7F (11*16+14)BE (15*16+15)FF

在java 代码中,写16进制时需使用0x作为前缀,比如int i = 0xF(15)、int i=0x11(17)、

int i=0xFF(255)。

本篇的主角出现了,其中一个 0xFF。另一个主角是&(与运算符)。上篇有介绍&是位运算符,属于二进制的运算符, i(int) & 0xFF,这就有意思了 i(10进制) &(二进制运算符) 0xFF(十六进制),实际到了计算层面的时候无论十进制、十六进制都会转为2进制进行计算,现在我们来看十六进制与二进制的对比

2的n次方 2^1 ... 2^3-1 2^3 2^4-1 2^4 2^5-1 2^5 2^6-1 2^6

十进制 : 2 7 8 15 16 31 32 63 64

十六进制 : 2 7 8 F 10 1F 20 3F 40

二进制 : {10} {111} {1000} {1111} {1 0000} {1 1111} {10 0000} {11 1111} {100 0000}

2^7-1 2^7 2^8-1 2^8 ...... 2^31-1 2^31

127 128 255 256 2147483647 -2147483648

7F 80 FF 100 7FFFFFFF 80000000

{111 1111} {1000 0000} {1111 1111} {1 0000 0000} {0 后边31个1} {1后边31个1}

最后写的2的31次方为何为负数,是因为本段对比均以java int值作为运算范围,是有运算符号的,最大范围为32位,最左边的称为高位,是运算符号。

0xFF可以直观的看到对应的二进制是{1111 1111},因为int类型是32位,在进行&运算时,需要补全0才可以运算,根据上篇的(与运算口诀,一 一得一,其他为0)来试一下999& 0xFF

0000 0000 0000 0000 0000 0011 1110 0111 = 999

&

0000 0000 0000 0000 0000 0000 1111 1111 = 0xFF = 255

---------------------------------------------------------------------

0000 0000 0000 0000 0000 0000 1110 0111 = 231

再算一个66 & 0xFF

0000 0000 0000 0000 0000 0000 0100 0010 = 66

&

0000 0000 0000 0000 0000 0000 1111 1111 = 0xFF = 255

---------------------------------------------------------------------

0000 0000 0000 0000 0000 0000 0100 0010 = 66

再来一个负数的,-1024 & 0xFF

1111 1111 1111 1111 1111 1100 0000 0000 =-1024

&

0000 0000 0000 0000 0000 0000 1111 1111 = 0xFF = 255

---------------------------------------------------------------------

0000 0000 0000 0000 0000 0000 00000000 = 0

再来一个负数的,-200 & 0xFF

1111 1111 1111 1111 1111 1111 0011 1000 =-200

&

0000 0000 0000 0000 0000 0000 1111 1111 = 0xFF = 255

---------------------------------------------------------------------

0000 0000 0000 0000 0000 0000 0011 1000 = 56

从以上几个例子可以看出 & 0xFF就像一把剪刀,首先把符号位变成了正符号0,然后剪掉 3个8位留最后一段8位,用行业术语叫高位、低位,使用-200举例高位低位

1111 1111 1111 1111 1111 1111 0011 1000 =-200

\_________高位________/ \_________低位_______/

再啰嗦几句,最左边1位表示符号位时,从左向右是从高位向低位,这种叫做大端模式,有大端就有小端模式,深入了解大小端可去百科传送门

既然像一把剪刀,这把剪刀能干什么,我们来实战演练一下

int类型数值,-3434343,然后进行向右移位操作,试试看-3434343 >> 24

1111 1111 1100 1011 1001 1000 1001 1001

[{1111 1111 1111 1111 1111 1111} 1111 1111] 1100 1011 1001 1000 1001 1001

上篇文章我们有说,带符号右移,负数右移移动多少位就在左边补多少个1,操作后我们最后得到的值是[]内的,右边的都向右溢出舍去,我们将结果再进行 & 0xFF操作,

1111 1111 1111 1111 1111 1111 1111 1111

&

0000 0000 0000 0000 0000 0000 1111 1111 = 0xFF = 255

---------------------------------------------------------------------

0000 0000 0000 0000 0000 0000 1111 1111 = 255

同样的方式我们右移16位 8位 和直接& 0xFF看到什么结果

1111 1111 1100 1011 1001 1000 1001 1001

[{1111 1111 1111 1111} 1111 1111 1100 1011] 1001 1000 1001 1001(红色部分补位)

1111 1111 1111 1111 1111 1111 1100 1011

&

0000 0000 0000 0000 0000 0000 1100 1011 = 0xFF = 255

---------------------------------------------------------------------

0000 0000 0000 0000 0000 0000 1100 1011 = 203

..........以下雷同结果分别是

0000 0000 0000 0000 0000 0000 1001 1000 = 152

0000 0000 0000 0000 0000 0000 1001 1001 = 153

结果组装

int 255 = (byte) -1

int 203 = (byte) -53

int 152 = (byte) -104

int 153 = (byte) -103

那么 -3434343 转为byte数组则是 byte [] result = { -1, -53, -104, -103 }

容我再bb两句: -3434343和他的二进制小伙伴(还是上面灰色背景那段)

1111 1111 1100 1011 1001 1000 1001 1001 = -3434343

\___-1(255)____/ \_-53(203)___/ \__-104(152)___/ \___-103(153)____/

是不是瞬间觉得nb了,这4段指的就是java中int占4byte(字节),想取出哪段的值就把他移动到最右边8位的位置,然后用 & 0xFF 剪掉前面0,留下最后8位的值,就得到想取出段的int值了(谁tm发明的,太nb了),(天空中传来小伙伴说为什么不substring ,靠!这个二进制,没提供那功能)

在网上可以找到很多int转byte的代码,但是自己观察好他的最后byte顺序,有的是反的,我不敢保证我的顺序正确,但我敢保证我处理结果与java内置的处理结果(代码在下边)相同,伙伴自己仔细甄别

ByteArrayOutputStream boutput = new ByteArrayOutputStream();
DataOutputStream doutput = new DataOutputStream(boutput);
try {
    doutput.writeInt(i);
} catch (IOException e) {
    e.printStackTrace();
}
byte[] buf = boutput.toByteArray();