Java中byte做&0xff运算的原因及解析

Java中byte做&0xff运算的原因及解析

一个行走的民 2018-03-18 17:23:05 6163 收藏 5
分类专栏: Java 文章标签: java unsigned sigened 无符号数据类型 0xff
版权

Java
专栏收录该内容
23 篇文章0 订阅
订阅专栏
网上瞎写的一大堆,实在是无语。把自己理解的整理分享给大家。
首先要知道原码、反码、补码是什么。可以参考:
http://www.cnblogs.com/zhangziqiu/archive/2011/03/30/ComputerCode.html

第一种情况:signed/unsigned之间的转化
java中,除了char是unsigned 两个字节,用来表达UTF-16,此外byte/short/int/long都是signed的。
取值范围:
boolean:一字节
byte:(-128,127) 一字节
char:(0,65535) 两字节
short:(-32768,32767)两字节

那么在Java与别的语言编写的程序通信时,就可能涉及到signed/unsigned之间的转化。比如C写的socket传输unsigned数据

signed到unsigned的转化
//JDK 1.8中Byte.toUnsignedInt(byte x)
public static int toUnsignedInt(byte x) {
return ((int) x) & 0xff;//将高24位全部变成0,低8位保持不变
}

//JDK 1.8中Integer.toUnsignedLong((int x)
public static long toUnsignedLong(int x) {
return ((long) x) & 0xffffffffL;//将高32位全部变成0,低32位保持不变
}
1
2
3
4
5
6
7
8
9
比较典型,经典的运用则是下面的:

//JDK1.8 ByteArrayInputStream read函数源码

protected byte buf[];//用于缓存数据的数组

//调用该函数会返回一个(0,255)的int类型数字,如果读到stream的end,则返回-1
public synchronized int read() {
return (pos < count) ? (buf[pos++] & 0xff) : -1;//这里跟调用toUnsignedInt(byte x)的效果是一样的
}
1
2
3
4
5
6
7
8
上面的源码可能有的人会犯迷糊
0xff的二进制是1111 1111 (8bit),一个byte也是8bit,
上面的操作buf[pos++] & 0xff是将一个byte类型的数字&0xff,那么得到的结果应该是还是这个byte类型数字本身呀?

可能你忽略了一个问题了:Java二元运算符的类型自动提升
也就是说buf[pos++] & 0xff这个运算中,buf[pos++]已经被自动提升为int了。

byte b = -20;
System.out.println(b & 0xff);// 236

final byte b2 = -20;
final byte mask = (byte) 0xff;
System.out.println(b2 & mask); // -20
1
2
3
4
5
6
第二种情况: byte转化为16进制String
在如下一段代码中有int v = src[i] & 0xFF这样的代码。

public static String bytesToHexString(byte[] src){
StringBuilder stringBuilder = new StringBuilder("");
if (src == null || src.length <= 0) {
return null;
}
for (int i = 0; i < src.length; i++) {
int v = src[i] & 0xFF;
String hv = Integer.toHexString(v);
if (hv.length() < 2) {
stringBuilder.append(0);
}
stringBuilder.append(hv);
}
return stringBuilder.toString();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
这里做&0xff的原因是:
1.byte是1byte(8位),int是4byte(32位)表示的。
2.Java中是使用了补码的形式进行数据存储的。
3.java中byte数据转化为int数据时会自动补位,如果最高位(符号位)是0,则高24位全部补0,若是1,则高24位全部补1。
4.知道了上面的三点,就有:
byte -1的二进制为(补码):1111 1111 -->对应的16进制为0xFF
int -1的二进制为(补码):1111 1111 1111 1111 1111 1111 1111 1111 -->对应的16进制为0xFFFFFFFF
5.Integer.toHexString(int i)函数内部是通过传进来数字的二进制(补码形式)来进行转换的,因此如果不进行int v = src[i] & 0xFF;
则得到的结果就是0xFFFFFFFF。而&0xff只后,传入的参数的二进制就变为0000 0000 0000 0000 0000 0000 1111 1111(虽然这个数的值以不再是-1,但是将他进行转换得到的0xff才是我们需要的)
源码如下:

//JDK1.8
static int formatUnsignedInt(int val, int shift, char[] buf, int offset, int len) {
int charPos = len;
int radix = 1 << shift;
int mask = radix - 1;
do {
buf[offset + --charPos] = Integer.digits[val & mask];//每次do-while循环都会取4位(从高位到低位),Integer.digits[]数组是十六进制的字符集
val >>>= shift;
} while (val != 0 && charPos > 0);

return charPos;
}

final static char[] digits = {
'0' , '1' , '2' , '3' , '4' , '5' ,
'6' , '7' , '8' , '9' , 'a' , 'b' ,
'c' , 'd' , 'e' , 'f' , 'g' , 'h' ,
'i' , 'j' , 'k' , 'l' , 'm' , 'n' ,
'o' , 'p' , 'q' , 'r' , 's' , 't' ,
'u' , 'v' , 'w' , 'x' , 'y' , 'z'
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
6.以上五点是全部原因,end
————————————————
版权声明:本文为CSDN博主「一个行走的民」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/zhaominpro/article/details/79602381


今天看到一个问题是关于0xff的,深入看进base64的源码,加上看了一些资料,明白的它的用意。
/**
* Encodes all remaining bytes from the specified byte buffer into
* a newly-allocated ByteBuffer using the {@link Base64} encoding
* scheme.
*
* Upon return, the source buffer's position will be updated to
* its limit; its limit will not have been changed. The returned
* output buffer's position will be zero and its limit will be the
* number of resulting encoded bytes.
*
* @param buffer
* the source ByteBuffer to encode
* @return A newly-allocated byte buffer containing the encoded bytes.
*/
public ByteBuffer encode(ByteBuffer buffer) {
int len = outLength(buffer.remaining());
byte[] dst = new byte[len];
int ret = 0;
if (buffer.hasArray()) {
ret = encode0(buffer.array(),
buffer.arrayOffset() + buffer.position(),
buffer.arrayOffset() + buffer.limit(),
dst);
buffer.position(buffer.limit());
} else {
byte[] src = new byte[buffer.remaining()];
buffer.get(src);
ret = encode0(src, 0, src.length, dst);
}
if (ret != dst.length)
dst = Arrays.copyOf(dst, ret);
return ByteBuffer.wrap(dst);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
进入encode0方法中可以看到下面的代码块

private int encode0(byte[] src, int off, int end, byte[] dst) {
char[] base64 = isURL ? toBase64URL : toBase64;
int sp = off;
int slen = (end - off) / 3 * 3;
int sl = off + slen;
if (linemax > 0 && slen > linemax / 4 * 3)
slen = linemax / 4 * 3;
int dp = 0;
while (sp < sl) {
int sl0 = Math.min(sp + slen, sl);
for (int sp0 = sp, dp0 = dp ; sp0 < sl0; ) {
int bits = (src[sp0++] & 0xff) << 16 |
(src[sp0++] & 0xff) << 8 |
(src[sp0++] & 0xff);
dst[dp0++] = (byte)base64[(bits >>> 18) & 0x3f];
dst[dp0++] = (byte)base64[(bits >>> 12) & 0x3f];
dst[dp0++] = (byte)base64[(bits >>> 6) & 0x3f];
dst[dp0++] = (byte)base64[bits & 0x3f];
}
int dlen = (sl0 - sp) / 3 * 4;
dp += dlen;
sp = sl0;
if (dlen == linemax && sp < end) {
for (byte b : newline){
dst[dp++] = b;
}
}
}
if (sp < end) { // 1 or 2 leftover bytes
int b0 = src[sp++] & 0xff;
dst[dp++] = (byte)base64[b0 >> 2];
if (sp == end) {
dst[dp++] = (byte)base64[(b0 << 4) & 0x3f];
if (doPadding) {
dst[dp++] = '=';
dst[dp++] = '=';
}
} else {
int b1 = src[sp++] & 0xff;
dst[dp++] = (byte)base64[(b0 << 4) & 0x3f | (b1 >> 4)];
dst[dp++] = (byte)base64[(b1 << 2) & 0x3f];
if (doPadding) {
dst[dp++] = '=';
}
}
}
return dp;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
src[sp0++] & 0xff src 是一个 byte数组 哎
网上有很多说这个用法的含义的,我就不强行解释了
总结为一句话:byte类型的数字要&0xff再赋值给int类型,其本质原因就是想保持二进制补码的一致性。当byte要转化为int的时候,高的24位必然会补1,这样,其二进制补码其实已经不一致了,&0xff可以将高的24位置为0,低8位保持原样。这样做的目的就是为了保证二进制数据的一致性。
感谢学习大佬的文章[跳转地址](https://blog.csdn.net/zhaowei5210/article/details/70920711)
————————————————
版权声明:本文为CSDN博主「秋天的铁工匠」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_37970049/article/details/103762790


关于原码反码和补码以及byte&0xff

 

1. 问题由来

本笔记是由byte&0xff引申出来的。在看某一段代码的时候发现有这么一个逻辑:

 

该方法的功能是把四个元素的byte数组转换成ip4地址,从debug的中间过程可以看出来src的第二个元素为负数-100,但它确实是表示ip地址的第二个字节,且src[1] & 0xff之后又变为了正数156,这其中的现象如何解释?要想知道这里面的原因,首先需要知道原码、反码和补码的概念。

2. 原码、反码和补码

数字是可以二进制来表示的,比如8的二进制表示为00001000,-8的二进制为10001000,其中最高位为符号位。对于正数来说,其二进制原码,反码,补码均相同。而对于负数来说,反码等于符号位位不变,其余各位取反;补码等于其反码加1。

比如问题中的156,其二进制表示为10011100,其反码和补码也是10011100。而-100的二进制表示为11100100,其反码为10011011,补码为1011100。这时候会发现156的原码和-100的补码是一致的。

3. byte & 0xff的细节

我们知道byte是java的一种基本类型,其大小为一个字节,表示的整数范围是-128~127。而当其最高为不解释为符号位时,其最大可以表示的数为255。因此例子中字节数组的第二个元素debug时被解释为-100的原因是,156的二进制表示正好可以解释为带符号byte的-100,且我们可以发现byte中存储的内容其实是补码。

ok,第一个问题清楚了,-100的补码与无符号的156的二进制表示一致,且java中的byte存储的是补码。那么,为什么src[1] & 0xff之后就变回了正数呢?讲道理,0xff可以表示为11111111,我们知道和1与运算之后还是其本身,乍一看,这个运算是没有意义的。

0xff究竟是怎么一回事呢。可以考虑一下下面这段代码中c的值会是什么:

int a = 255;
int b = 0xff;
boolean c = a == b;

c的值为true。其实0xff与255没有本质的区别,一个是十进制一个是十六进制,都是用来表示一个int型正数。回道上面的问题,src[1] & 0xff也就是src[1]与一个整数相与,那么src[1]首先就要先转换成一个四字节的整数:

11111111 11111111 11111111 10011100,而0xff为一个整数,其四字节表示为00000000 00000000 00000000 1111111,两者相与的结果为00000000 00000000 00000000 10011100,也即是解释为整数156,这样也就达到了想要获取byte中实际装入的无符号156的目的。

南来地,北往的,上班的,下岗的,走过路过不要错过!

======================个性签名=====================

之前认为Apple 的iOS 设计的要比 Android 稳定,我错了吗?

下载的许多客户端程序/游戏程序,经常会Crash,是程序写的不好(内存泄漏?刚启动也会吗?)还是iOS本身的不稳定!!!

如果在Android手机中可以简单联接到ddms,就可以查看系统log,很容易看到程序为什么出错,在iPhone中如何得知呢?试试Organizer吧,分析一下Device logs,也许有用.

我的开发工具

对于博客园里的网友,不敢称为叫"程序员"的人,你们攻击性太强,看来你们是不会想到我的用意的.园子里有不少人都非常喜欢Jeffrey,是因为它的第一版 框架设计 CLR via C#.
可是从第一版到现在的第三版,没有看到真正底层的东西,内容仅仅是比MSDN文档更丰富一些,可能是我的要求太高了吧.
也就是因为它很多时候会接触到微软开发人员,会经常聊聊某些问题而已,而它又将这些问题反应到书中.也许它就像一个小记者.
它的年龄大我们不多,我的孩子与它小儿子一般大,如果我能向它那样出入微软与它们开发人员长时间交流,不仅仅会牛成它这样.....
可是微软的开发人员不会扔太多时间在它这儿的.所以它会整天追着这个,赶它那个..屁颠个不停吧...
而它的另一版被称为好书的 Windows核心编程,更是没有什么深度可言,仅仅是将windows提供的api,以及内核功能再重申了一遍.
这些书对晋及编程知识是有些贡献的,再说一遍我不是在匾低谁,说说想法而已.

原文地址:https://www.cnblogs.com/ioriwellings/p/15131131.html