byte为什么要与上0xff?

无意间翻看之间的代码,发现了一段难以理解的代码。

byte[] bs = digest.digest(origin.getBytes(Charset.forName(charsetName))) ;

for (int i = 0; i < bs.length; i++) {
    int c = bs[i] & 0xFF ;
    if(c < 16){
        sb.append("0");
    }
    sb.append(Integer.toHexString(c)) ;
}
return sb.toString() ;

bs是由一段字符串经过MD5加密后,输出的byte数组。我起初难以理解为什么在接下来的循环中要将bs[i]&oxFF再复制给int类型呢?

bs[i]是8位二进制,0xFF转化成8位二进制就是11111111,那么bs[i]&0xFF不是还是bs[i]本身吗?有意思吗?

 

后来我又写了一个demo

package jvmProject;

public class Test {

public static void main(String[] args) {
    byte[] a = new byte[10];
    a[0]= -127;
    System.out.println(a[0]);
    int c = a[0]&0xff;
    System.out.println(c);
}
}

我先打印a[0],在打印a[0]&0xff后的值,本来我想结果应该都是-127.

但是结果真的是出人意料啊!

-127

129

到底是为什么呢?&0xff反而不对了。

 

楼主真的是不懂啊,后来往补码那个方向想了想。

记得在学计算机原理的时候,了解到计算机内的存储都是利用二进制的补码进行存储的。

复习一下,原码反码补码这三个概念

对于正数(00000001)原码来说,首位表示符号位,反码 补码都是本身

对于负数(100000001)原码来说,反码是对原码除了符号位之外作取反运算即(111111110),补码是对反码作+1运算即(111111111)

概念就这么简单。

 

当将-127赋值给a[0]时候,a[0]作为一个byte类型,其计算机存储的补码是10000001(8位)。

将a[0] 作为int类型向控制台输出的时候,jvm作了一个补位的处理,因为int类型是32位所以补位后的补码就是1111111111111111111111111 10000001(32位),这个32位二进制补码表示的也是-127.

发现没有,虽然byte->int计算机背后存储的二进制补码由10000001(8位)转化成了1111111111111111111111111 10000001(32位)很显然这两个补码表示的十进制数字依然是相同的。

 

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

不一定吧?好比我们拿到的文件流转成byte数组,难道我们关心的是byte数组的十进制的值是多少吗?我们关心的是其背后二进制存储的补码吧

所以大家应该能猜到为什么byte类型的数字要&0xff再赋值给int类型,其本质原因就是想保持二进制补码的一致性。

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

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

 

象例2中,int c = a[0]&0xff;  a[0]&0xff=1111111111111111111111111 10000001&11111111=000000000000000000000000 10000001 ,这个值算一下就是129,

所以c的输出的值就是129。有人问为什么上面的式子中a[0]不是8位而是32位,因为当系统检测到byte可能会转化成int或者说byte与int类型进行运算的时候,就会将byte的内存空间高位补1(也就是按符号位补位)扩充到32位,再参与运算。上面的0xff其实是int类型的字面量值,所以可以说byte与int进行运算。

评论列表

  

#1楼 2016-05-25 22:58 Anthony-黄亮  

其实是从数字类型扩展到较宽的类型时,补零扩展还是补符号位扩展。
这是因为Java中只有有符号数,当byte扩展到short, int时,即正数都一样,因为为符号位是0,所以无论如何都是补零扩展;但负数补零扩展和按符号位扩展结果完全不同。
补符号数,原数值不变。
补零时,相当于把有符号数看成无符号数,比如-127 = 0x81,看成无符号数就是129, 256 + (- 127)
对于有符号数,从小扩展大时,需要用&0xff这样方式来确保是按补零扩展。
而从大向小处理,符号位自动无效,所以不用处理。

支持(10)反对(0)

  

#2楼[楼主] 2016-05-25 23:26 陈其苗  

@ Anthony-黄亮
感谢!
让我理解更深了,也就是说在byte向int扩展的时候,自动转型是按符号位扩展的,这样子能保证十进制的数值不会变化,而&0xff是补0扩展的,这样子能保证二进制存储的一致性,但是十进制数值已经发生变化了。也就是说按符号位扩展能保证十进制数值不变,补0扩展能保证二进制存储不会变。而正数可以说是既按符号位扩展,又是补0扩展,所以在二进制存储和十进制数值上都能保证一致。

支持(1)反对(0)

  

#3楼 2016-05-26 10:40 ahdung  

java这么操蛋的吗,byte还能有符号

支持(2)反对(4)

  

#4楼 2016-05-26 10:48 五星  

C#里好像不需要吧,我记得byte本就是无符号的

支持(5)反对(0)

  

#5楼[楼主] 2016-05-26 10:56 陈其苗  

@ 五星
c#没试过,java上byte是有符号位的

支持(0)反对(0)

  

#6楼 2016-05-26 11:33 jan4984  

总之就是Java垃圾

支持(8)反对(3)

  

#7楼 2016-05-26 13:30 旋剑  

c++ 程序员路过
简单的查了一下,发现java原来天生没有 unsigned
楼主可以简单的注释一下:
int c = bs[i] & 0xFF ; // 等同于 unsigned int c = bs[i];

不过如果只学过java,估计也看不懂注释

支持(3)反对(0)

  

#8楼 2016-05-26 16:19 jerry_0824  

~感谢分享~

支持(0)反对(0)

  

#9楼 2016-05-26 17:27 InkFx  

C# 中:
byte 类型: 0~255
sbyte 类型: -128 ~ 127

Java 中:
byte 类型: -128 ~ 127


——————————————————————
补充一下:


—— 之所以 Java 要用 &0xFF ,是因为 Java 语言,没有 无符号 byte。
——————————————————————
纯属猜测 —— 都别较真。

支持(0)反对(2)

  

#10楼[楼主] 2016-05-26 17:36 陈其苗  

@ InkFx
sbyte a = -127
如果你这样理解的话,你觉得下面两句话有区别吗?
byte c = (byte)a;

byte c = (byte)(a&0xff)

支持(0)反对(1)

  

#11楼 2016-05-26 17:41 InkFx  @ 路上有多远

引用@InkFx
sbyte a = -127
如果你这样理解的话,你觉得下面两句话有区别吗?
byte c = (byte)a;

byte c = (byte)(a&0xff)



亲自 实践:没区别 —— C#是强大的,Java是欠缺的。


Java 之所以 &0xFF 只有一个原因:Java 没有 无符号 byte。??

支持(0)反对(1)

  

#12楼[楼主] 2016-05-26 17:42 陈其苗  

@ InkFx
。。。。这不是c#的智能问题吧!
在c#中无论是sbyte还是byte在存储的时候都是8位,所以sbyte和byte之间的转化 ,不会产生补位的情况,&0xff是没有任何意义的。

支持(0)反对(1)

  

#13楼[楼主] 2016-05-26 17:49 陈其苗  @ InkFx

引用@路上有多远

引用引用@InkFx
sbyte a = -127
如果你这样理解的话,你觉得下面两句话有区别吗?
byte c = (byte)a;

byte c = (byte)(a&0xff)



亲自 实践:没区别 —— C#是强大的,Java是残疾的。


Java 之所以 &0xFF 只有一个原因:Java 没有 无符号 byte。




我个人觉得吧 ,&0xff只有在同为符号数存储,并且在窄类型向宽类型拓展的时候,为了保证不丢失二进制的存储,即在二进制数据保持一致的时候才会用到。

支持(0)反对(0)

  

#14楼 2016-05-26 19:13 暗海风  

0xff是一个int类型的数据, 实际上是0x000000ff。
int c = a[0]&0xff;
实际上是 int c = (int)a[0]&0x000000ff;
c 实际上取的是 (int)a[0]的低8比特数据而已,当然不一样啦。
(以上解释仅针对java)

我觉得这篇博客的题目取得不太合适,byte不需要要与上0xff,而是byte类型转换为int类型,希望保证低8比特数据一致,前24比特为0时要与上0xff。

支持(3)反对(0)

  

#15楼[楼主] 2016-05-26 20:30 陈其苗  

@ 暗海风
是我疏忽了~

支持(0)反对(0)

  

#16楼 2016-05-26 21:03 KOFIP  

标题加上 JAVA 更合适,吓我一跳,

用了这么多年C#,从来没遇到这种问题。

支持(1)反对(0)

  

#17楼 2016-05-27 08:41 说书的猫  

@ 五星
c#里有符号的是sbyte ,无符号的用btye表示
其实区别不大

支持(0)反对(0)

  

#18楼 2016-06-27 16:08 Anthony-黄亮  

C#也有这个问题,不是byte到int,而是sbyte到int。
所有有符号数都需要这个操作,默认的行为(按符号位扩展)不符合此处的要求。
对于有符号数,从小扩展大时,需要用&0xff这样方式来确保是按补零扩展。

支持(1)反对(0)

  

#19楼 2018-08-10 08:42 要不要&0xff  

谢谢你的博文
为什么要&0xff,因为用到了Integer.toHexStirng(int)
Java只有有符号表示
byte是8位的,0xff为了在转化为int时只保留低八位,
对于-127,否则转为int自动符号位拓展0xff ff ff 81 我们的目的是0x81
byte b=-127;
System.out.println("不&:"+Integer.toHexString(b));
System.out.println("要&:"+Integer.toHexString(b&0xff));

支持(0)反对(0)

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值