有趣的byte与0xff

背景:   最近看到这样一段代码,字节数据转字符串,起初没有看懂,为啥Integer.toHexString(0xFF & bArray[i])    这个方法里要写成0xFF & bArray[i]

这句代码的最终目的是把byte[]转换为16进制字符串,toHexString()是把一个int转换为十六进制String ,&0xFF是为了保证byte类型转int后其二进制的一致,即补零扩展...

 public String bytesToHexString(byte[] bArray) {
        StringBuffer sb = new StringBuffer(bArray.length);
        String sTemp;
        for (int i = 0; i < bArray.length; i++) {
            sTemp = Integer.toHexString(0xFF & bArray[i]);
            if (sTemp.length() < 2) {
                sb.append(0);
            }
            sb.append(sTemp.toUpperCase());
        }
        return sb.toString();
  }

看完这段解释,好像懂了又好像没懂,为啥要补零扩展?还有什么扩展呢?

一、了解基本概念原码、反码、补码

  •   原码,就是符号位加上真值的绝对值, 即用第一位表示符号, 其余位表示值,正数的符号位为 0、负数的符号位为 1
  •  反码,负数的反码是在其原码的基础上, 符号位不变,其余各个位取反,正数的反码是其本身
  •  补码,负数的补码是在其原码的基础上, 符号位不变, 其余各位取反, 最后+1. (即在反码的基础上+1),正数的补码就是其本身

反码是原码和补码相互转换时的临时过渡,用处不大

二、为什么计算机中要以补码形式存储数据?

      在计算机系统中数值一律用补码的形式来表示和存储。原因是:使用补码时,可以将符号位和数值位统一处理;同时加法和减法也可以统一处理(CPU只有加法器)此外,补码与原码相互转换,其运算结果是相同的,不需要额外的硬件电路。其次存放补码可以保证计算的准确性。因为CPU只有加法,所以要将1-1转换成1+(-1)来计算。因为1是正数,所以1的反码补码都是其本身。以int类型数据为例子:

1的原/反/补码   0000 0000 0000 0000 0000 0000 0000 0001

-1的       原码   1000 0000 0000 0000 0000 0000 0000 0001

-1的       反码    1111 1111 1111   1111  1111  1111 1111  1110

-1的       补码    1111 1111 1111   1111  1111  1111 1111   1111 

将1的补码和-1的补码相加计算, 

  0000 0000 0000 0000 0000 0000 0000 0001

 1111 1111   1111   1111  1111 1111  1111  1111 

结果是

1 0000 0000 0000 0000 0000 0000 0000 0000

转换为int 类型,这时候你会发现最高位1其实丢了,因为int 类型占4个字节即32bit,所以0000 0000 0000 0000 0000 0000 0000 0000 再次转为原码还是0000 0000 0000 0000 0000 0000 0000 0000,即得到运算结果是十进制的0

三、补零扩展和补符号扩展

byte占8位,int占32位等。正因如此,当把一个低精度的数据类型转成一个高精度的数据类型时,必然会涉及到如何扩展位数的问题。这里有两种解决方案: 

  • 补零扩展:填充一定位数的0。 
  • 补符号位扩展:填充一定位数的符号位(非负数填充0,负数填充1)

举例说明,

-127的原码是 1111 1111 (注意1111 1111 表示无符号是就是255 ,有符号时就是-127 ,有符号时+127 是 0111 111)

-127的反码是 1000 0000

-127的补码是 1000 0001 计算机中存储的就是这个补码,

使用补零扩展得到结果是        0000 0000 0000 0000  0000 0000 1000 0001 正数的补码就是其本身,所以转换为10进制是129 

使用补符号扩展得到的结果是 1111  1111  1111 1111  1111  1111  1000 0001 

求这个补码的补码即原码 1000 0000 0000 0000 00000 0000 0111 1111所以转换为10进制是-127 

由此可见 对与-127,在计算机存储的是补码1000 0001,补零扩展后,为0000 0000 0000 0000  0000 0000 1000 0001,使用补零扩展能够保证二进制存储的一致性,但不能保证十进制值不变,补符号扩展,虽然得到的十进制值没有改变,但是二进制的值却变了,变成了1111  1111  1111 1111  1111  1111  1000 0001
 

四 、我们到底想要什么?

情况一,我们需要 十进制的一致性

public class Test{
    public static void main(String[] args) {
        Byte a=-127;
        int b=a;
        System.out.println(b);
    }
}
// 输出结果是-127

这是因为Java中只有有符号数,当byte扩展到short, int时,Java中的扩展方式是补符号位扩展

但是我做byte->int的转化 所有时候都只是为了保持 十进制的一致性吗?

不一定吧?好比我们拿到的文件流转成byte数组,难道我们关心的是byte数组的十进制的值是多少吗?我们关心的是其背后二进制存储的补码吧。所以大家应该能猜到为什么byte类型的数字要&0xff再赋值给int类型,其本质原因就是想保持二进制补码的一致性。

情况二,我们需要保持二进制补码的一致性

当byte要转化为int的时候,高的24位必然会补1,这样,其二进制补码其实已经不一致了,&0xff可以将高的24位置为0,低8位保持原样。这样做的目的就是为了保证二进制数据的一致性。

因为 0xFF = 1111 1111  低8位为1,高位都为0

对应的二进制为 0000 0000 0000 0000 0000 0000 1111 1111,位运算符&的特点两边都是1才是1,否则是0,故 &0xFF 可将数字的高位都置为0,低8位不变

当然拉,保证了二进制数据性的同时,如果二进制被当作byte和int来解读,其10进制的值必然是不同的,因为符号位位置已经发生了变化

### 按位取反后为什么要0xff进行按位运算? 在对二进制数进行按位取反操作时,特别是在处理有符号整数(如`byte`类型)的情况下,通常会看到后续的按位操作使用了`0xFF`。这一操作的主要目的是**保留原始数据的低8位信息,并屏蔽掉高位扩展带来的符号影响**。 在大多数编程语言中(如Java),当对一个`byte`类型的值执行按位取反操作时,系统会自动将其提升为`int`类型进行计算。这意味着原本的8位数据会被填充至32位。如果原始`byte`值的最高位是1(即表示负数),那么在提升为`int`时,高位将被补1以保持数值的符号不变。这种行为称为**符号扩展**。例如,一个值为`-1`的`byte`(其二进制补码形式为`0xFF`),在转换为`int`时会被扩展为`0xFFFFFFFF`[^2]。 在这种情况下,直接对`byte`执行按位取反操作可能会导致结果超出预期范围。为了防止这种情况,通常会在按位取反之后紧接着使用`& 0xFF`操作。`0xFF`是一个十六进制常量,对应的二进制形式为`00000000 00000000 00000000 11111111`。通过`0xFF`进行按位操作,可以**只保留最低的8位数据,而忽略掉高位的符号扩展部分**。这确保了即使原始数据是有符号的,也能正确地获取到其8位表示形式。 例如,在Java中,以下代码展示了如何正确处理一个`byte`类型的按位取反: ```java byte b = (byte) 0xFF; // -1 in two's complement int result = ~b; // This will be 0x00000000 (which is 0) System.out.println(result); // Output: 0 // To get the correct 8-bit NOT result, mask with 0xFF int maskedResult = ~b & 0xFF; System.out.println(maskedResult); // Output: 255 ``` 上述示例中,如果不使用`& 0xFF`,则结果会因为符号扩展而变为0;而使用了`& 0xFF`后,实际得到的是`255`,这正是`0xFF`按位取反后的8位结果。 类似的机制也适用于其他需要处理固定长度字节数据的场景,比如网络通信、文件解析等,其中确保数据仅限于特定的位宽是非常重要的。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一棵小白菜#

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值