实现自己的数据库驱动——字节位运算细节(八)

前言

前面几篇主要是讲解各个MySQL协议通信过程中涉及到的Packet。后面主要是讲解如何根据Packet的结构,将需要的字节按照一定的顺序写入Packet中。这部分涉及到Java 的字节运算。

低字节转高字节存在的问题

1
2
3
4
5
public static void main(String args[]){
byte x = -126;
System.out.println(Integer.toBinaryString(x & 0xff));
System.out.println(Integer.toBinaryString(x));
}

结果:

10000010
11111111111111111111111110000010

先来看上面的程序。Integer.toBinaryString接受一个int类型的值,并将这个值的二进制码输出。可以看到x在与0xff进行与操作之后的值发生了改变。

先不说为什么,回到byte x = -126来,先想想x存储的二进制应该是什么?Java中存储的是补码(关于什么是补码这里不细讲)。

这个简单,byte总共有八位其中最高位表示符号位,剩下的七位表示值。范围是-128~127。很容易算出来x存储的二进制为下:

10000010

既然byte是八位的二进制,那 和0xff&运算的意义是什么呢?x&0xff难道不还是x吗!

的确是的,但是在做&运算的时候0xff表示的是一个int类型,而x在与int做运算的时候,就需要做byte到int的一个转化。转化的过程为:

  1. 当byte为一个负值,会将8位的byte填充至32位,用1来填充。也就是说byte x = -126 原本是1000 0010,变成1111 1111 .... 1000 0010
  2. 当byte为一个正值,用0来填充,这时候是没有影响的。原本是1000 0010 变成0000 0000... 1000 0010

x = -126.考虑第一种情况,x填充成1111 1111.... 1000 0010再与0xff&运算,最后变成了1000 0010。所以第一个输出就得到了解释。第二个输出因为被填充的1111 1111... 1000 0010没有其他运算,所以按照这个二进制输出。

i & 0xff的意义

上面的输出虽然得到了解释,但是对i & 0xff的意义还未认识到位。首先byte x =-126,在填充成int类型后变成了1111 1111 .... 1000 0010,虽然个二进制表示的十进制还是-126的,但是x在内存中存储的进制已经发生了变化。我希望就算它是被int类型填充,它的二进制还是保持为原来的。即使它的十进制不一样。

很多时候,我们要求的就是二进制唯一性。举个列子,从服务端发送的来了一个Handshake,第四位表示的一个包的序列号,用一个字节来表示。假设服务端的packed number为-5,我们解析这个包的同时,也需要用一个字节来保存,如果这个时候我不用字节来存,而是用一个int来存,是不是就存在上面低字节转高字节的问题。

会导致原本是-5的byte二进制1111 1011,变成了1111 1111.... 1111 1011。所以为了解决二进制不唯一的问题就要利用& 0xff 来避免。

什么时候需要 & 0xff

如上面提到的当存在低位向高位的转化时,就需要考虑补码的问题,比如下列代码

1
2
3
4
5
public static void main(String args[]){
byte x = -34;
System.out.println(Integer.toHexString(x));
System.out.println(Integer.toHexString(x&0xff));
}

结果:

ffffffde
de

首先我们知道x作为字节是以补码的形式存在的,即它在内存是以八位的形式存储1101 1110。那么我调用toHexString(x) 是希望得到de的,但是因为toHexString需要一个int类型,所以在进行转化的时候byte 类型的x就先转成了int类型。转化过程会以1来填充。那么就得到了1111 1111... 1101 1110

那么上述x&0xff就可以保证补码带来的影响了,直接把前面24位的1&0。所以可以得到de

x&0xff虽然已经表示为int类型的222,和原来的值发生了变化,但是在这个场景中,我们需要的是二进制相同,所以当我们在做低位向高位转化,并且涉及到的是进制上的运算,就需要用到&0xff

再举个列子,byte往long的转化,涉及到上述的两个条件,那么就要考虑到是否要使用0xff来保证补码带来的问题

1
2
3
4
5
6
7
8
public long readUB4() {
final byte[] b = this.data;
long l = (long) (b[position++] & 0xff);
l |= (long) (b[position++] & 0xff) << 8;
l |= (long) (b[position++] & 0xff) << 16;
l |= (long) (b[position++] & 0xff) << 24;
return l;
}