最近在把C代码移植到Java(Android)中,C中一般的数据都会使用byte[](unsigned char)来存储,且位操作特别频繁。我要把这些数据转换成在Java存储,或把byte转换成本语言的其他类型,掉进不少坑。这里,总结下这段时间我遇到的转换问题。
一、C与Java的变量类型区别
1、变量类型及所占字节数
变量类型 | C中字节数 | Java中字节数 |
---|---|---|
char | 1 | 2 |
byte | 【无】 | 1 |
short | 2 | 2 |
int | 4 | 4 |
long | 4 | 8 |
C在不同的机器所占字节数不同,但一般的机器都是这样,不同的可用sizeof(int)来获取。
C中没有byte类型,但一般都会用typedef来定义一个:typedef unsigned char BYTE;
2、变量类型有无符号
C中可以定义有符号的变量和无符号的变量类型,而Java中只有带符号的变量。无符号表示的正数范围要大一个等级。
如:signed char范围是-128~127,unsigned char的范围是0~255
二、Java中注意事项
以下基本都是按照C的操作习惯类:所有的类型都是跟C一样,且当做无符号处理;常用十六进制表示数据。
1、byte、short的位操作都是先转换成int类型,再进行位操作。
因为Java中没有unsigned,所以正负数转换完全不一样。如
byte a = 0x12;
byte b = (byte)0x81;
...位操作...
a的最高位是0,所以是正数,直接转换成十进制即可,为18。
b的最高位是1,所以是负数。用常量值赋值,常量默认是int型,若没超过-128~127(0x7F)的范围,可直接赋值,否则必须强转。
计算机中都是用补码来存储数据,根据正数补码为原码,负数补码为去符号位后,原码取反加1。已知补码,正数的原码就是去符号位后,补码减1再取反。
补码 1000 0001
去符号 -000 0001
减1 -000 0000
取反 -111 1111
----------------------
十进制 -127
so,
a转换成int后是:0x00000012
b转成成int后是:0xFFFFFF81
转换后,如果把前面的0去掉后,正数表示还是一样,但负数前面的不再是0了,而全是F(1111)了。
我要说明的就是这点:在位操作时,首先会先转成int类型,正数被转换后前面的字节全填0,负数则填1。
谨记这点,在操作时就知道如何操作了
2、byte与int的互转
byte转int,由上面可知,如果是负数,则会出现问题。所以应该把需要的最低字节提取出来。
int转byte,直接强转即可,强转的操作相当于直接截取最低字节。这没什么意义,主要用来为下面的int转byte[]打基础。
private static int byte2Int(byte b) {
return b & 0xFF;
}
public static byte int2Byte(int num) {
return (byte) num;
}
3、带符号右移>>和无符号右移>>>
对于正数,两种位操作都是一样。
但对于负数来说就不一样了,带符号右移>>后最高位补1,无符号右移>>>最高位补0。
目前我用的最多的地方就是取位和整除2^n。
【1】取位
把变量上固定位的值取出。如取出a和b的高4位:
private static void testGetHighBits() {
byte a = 0x12;
byte b = (byte) 0x81;
byte highA, highB;
highA = (byte) (a >> 4);
highB = (byte) (b >> 4);
System.out.printf("highA=%X, highB=%X", highA, highB);
}
打印的结果是:highA=1, highB=F8
我们想要的结果应该是:highA=1, highB=8
肿么办?就用我们的>>>吧:highB = (byte) (b >>> 4);
结果还是一样。。。
想想上面第一点说的。highB = (byte) (b >>> 4);
的执行流程是这样的:
b转换为int: 1111 1111 1111 1111 1111 1111 1000 0001
>>>4: 0000 1111 1111 1111 1111 1111 1111 1000
强转成byte: 1111 1000
所以最后结果是F8。
取位,我们应该按需取:把值和需要的位相与,再移位,或先移位再与。
把需要的位取出,就是与1相与,不是吗?如
取低4位,直接&0x0F。
取高4位,&0xF0,再>>4,或先>>4,再&0x0F
取中间4位,&0x3C,再>>4,或先>>4,再&0x0F
上面的代码应该改为:
private static void testGetHighBits() {
byte a = 0x12;
byte b = (byte) 0x81;
byte highA, highB;
highA = (byte) ((a & 0xF0) >> 4);
highB = (byte) ((b & 0xF0) >> 4);
System.out.printf("highA=%X, highB=%X", highA, highB);
}
或
highA = (byte) ((a >> 4) & 0x0F);
highB = (byte) ((b >> 4) & 0x0F);
把>>替换成>>>也都可以的。注意运算符的优先级(口诀:单目乘除为关系,逻辑三目后赋值。参考http://lasombra.iteye.com/blog/991662)
【2】整除2^n
我们知道<<n,相当于乘2^n;>>n相当于除2^n。(<<与MarkDown语法冲突,只能嵌套在这代码块中)
这>>n也可以用>>>n代替,那>>与>>>到底在什么时候要区分用?
目前我用到的地方就是在无符号int中。如,用int类型的变量代表0~2^32-1,并求整除4后的结果。
private static void testBitsRightMove() {
int a = 0x12C; // 实际值:300,也是代表值
int b = 0x80000089; // 实际值:-2147483511,但此代表:2147483785
int tmpA, tmpB;
System.out.printf("a=%d, 0x%X; \t b=%d, 0x%X\n", a, a, b&0xFFFFFFFFL, b);
tmpA = a >> 2;
tmpB = b >>> 2;
System.out.printf("tmpA=%d; \t tmpB=%d", tmpA, tmpB);
}
打印结果:
a=300, 0x12C; b=2147483785, 0x80000089
tmpA=75; tmpB=536870946
上面的>>都可以替换成>>>,但>>>就不能被>>替换。在无符号的操作中,如果实际值是负数,必须使用>>>,为了保险起见,建议都用>>>。
4、如何用byte表示无符号,0~255的范围?
上面已经讲了int表示无符号的,同理的,byte也可以表示无符号的。但这仅是存储用,实际操作时必须放大范围来操作。
如有一个大小是256的byte类型数组,现在给数组赋值,元素的值为其对应的数组脚标值,并求偶数和。
private static void testUnsignedByte() {
byte[] arr = new byte[256];
int i;
int sum = 0;
// 赋值
for (i = 0; i < 256; i++) {
arr[i] = (byte) i;
}
// 打印元素,并求和
for (i = 0; i < 256; i++) {
int e = arr[i] & 0xFF;
System.out.printf("%d\t", e);
if ((e & 0x01) == 0) {
sum += e;
}
}
System.out.printf("\n sum=%d", sum);
}
打印结果:
0 1 2 3 4 5 ...[省略]... 251 252 253 254 255
sum=16256
而C中一般都是无符号操作。在转成Java时,个人建议:
int转byte:直接强转
byte转int:用取位方法(&0xFF)
5、使用赋值运算符
使用位运算的赋值运算符,是位与位的操作,可以直接使用。
但如果是算术运算符,如+=, -=, *=, \=,那就要注意了。如byte的129(实际值为-127),
private static void test() {
byte b = (byte) 0x81; // 实际-127,表示129
b /= 3;
System.out.printf("b=%d", b);
}
打印结果是:-42
但我们需要的结果是:43
因为我们使用了实际值进行运算,所以要达到想要的结果,必须先转换成大范围的数据类型,再运算: b = (byte) ((b & 0xFF) / 3);
三、实例操作
1、byte[]转int
private static int bytes2Int(byte[] bs) {
int retVal = 0;
int len = bs.length < 4 ? bs.length : 4;
for (int i = 0; i < len; i++) {
retVal |= (bs[i] & 0xFF) << ((i & 0x03) << 3);
}
return retVal;
// 如果确定足4位,可直接返回值
// return (bs[0]&0xFF) | ((bs[1] & 0xFF)<<8) | ((bs[2] & 0xFF)<<16) | ((bs[3] & 0xFF)<<24);
}
2、int转byte[]
private static byte[] int2Bytes(int n) {
byte[] bs = new byte[4];
bs[0] = (byte) n;
bs[1] = (byte) (n >> 8);
bs[2] = (byte) (n >> 16);
bs[3] = (byte) (n >> 24);
return bs;
}
3、byte[]转int[]
private static int[] bytes2Ints(byte[] bs) {
int len = (bs.length & 0x03) == 0 ? (bs.length >> 2) : (bs.length >> 2) + 1;
int[] is = new int[len];
// 进行转换前必须保证is[]所有元素的值都是0,这里创建时默认都是0,所以不用初始化赋值
for (int i = 0; i < bs.length; ++i) {
is[i >> 2] |= (bs[i] & 0xFF) << ((i & 0x03) << 3);
}
return is;
}
或
private static int[] bytes2Ints(byte[] bs) {
int len = (bs.length & 0x03) == 0 ? (bs.length >> 2) : (bs.length >> 2) + 1;
int[] is = new int[len];
int offset = 0;
for (int i = 0; i < len; i++, offset +=4) {
int rest = 4;
rest = offset + rest > bs.length ? bs.length - offset : rest;
is[i] = 0;
switch (rest) {
case 4:
is[i] |= (bs[offset + 3] & 0xFF) << 24;
case 3:
is[i] |= (bs[offset + 2] & 0xFF) << 16;
case 2:
is[i] |= (bs[offset + 1] & 0xFF) << 8;
case 1:
is[i] |= bs[offset] & 0xFF;
}
}
return is;
}
4、int[]转byte[]
private static byte[] ints2Bytes(int[] is) {
byte[] bs = new byte[is.length << 2];
int offset;
for (int i = 0; i < is.length; i++) {
offset = i<<2;
bs[offset] = (byte) (is[i] & 0xFF);
bs[offset + 1] = (byte) ((is[i] >> 8) & 0xFF);
bs[offset + 2] = (byte) ((is[i] >> 16) & 0xFF);
bs[offset + 3] = (byte) ((is[i] >> 24) & 0xFF);
}
return bs;
}
有时会使用short,其与byte的互换,与上面同理。