amp@java

amplifier's java blog

  BlogJava :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理 ::
  98 随笔 :: 0 文章 :: 228 评论 :: 0 Trackbacks
当读写二进制文件,或者要把非标准长度的整数与标准长度的整数互相转换时,就要用到大量的位操作,虽然看起来很简单,实际上里面却有很多细节很容易出错。

首先,Java有些标准跟C/C++是不同的:

1、Java采用高字节在前的方式读写数据,例如要把一个4字节的int数值写入文件时,它是按照从高字节到低字节的顺序写入的,读取的时候也是这样读出来。
而C/C++则采用平台相关的方式,在Windows平台采用低字节在前的方式,在Linux/Unix平台则采用高字节在前的方式。
如果Java要读取C/C++创建的二进制文件,就要注意这个问题,最好先搞清楚原来的文件是采用哪种方式创建的。网络通信也要注意。

2、Java没有无符号数,无论byte,short,int,long都是有符号整数,而C/C++有个unsigned关键字可以设置一个数值为无符号数。

3、Java的整数基本数据类型就是byte,short,int,long这几个,长度分别为1,2,4,8字节,C/C++可以用typedef定义各种数据类型。

第二,Java是采用补码来存放整数的。
有时候觉得补码的定义有些奇怪,实际上可以这样理解:

把一个整数从0一直往上加1,加到溢出就变成了负数的最小值,然后再继续加1,最后又能回到0,实际上就是一个轮回。
例如一个byte类型的整数,一共有8位,能表示256个数值,采用补码的话数值范围就是-128~127,表示方法如下:
0        0000 0000
1        0000 0001
.
.
126    0111 1110
127    0111 1111
-128   1000 0000
-127   1000 0001
.
.
-1       1111 1111
0         0000 0000

第三、不同长度的整数转换。
如果是从较短的数转成较长的数,很简单,如果是正数就在高字节补0,如果是负数就在高字节补1。
例如byte的127转为short的127:
byte:0111 1111
short:0000 0000 0111 0111
byte的-127转为short的-127
byte:1000 0001
short:1111 1111 1000 0001
如果是从较长的数转成较短的数,实际上就是把高位都截断了,所以转出来的数值可能完全不是一回事了。
例如short的256转为byte:
short:0000 0001 0000 0000
byte: 0000 0000
把256变成了0
short的-255转成byte:
short:1111 1111 0000 0001
byte:0000 0001
把-255变成了1

第四、位运算操作符及它们的优先级
Java的位运算操作符包括:~非,|按位或,&按位与,^按位异或,<<左移,>>右移,>>>右移左侧补0
各种运算符的优先级如下表所示:
优先级
运算符
结合性
1
() [] .
从左到右
2
! +(正) -(负) ~ ++ --
从右向左
3
* / %
从左向右
4
+(加) -(减)
从左向右
5
<< >> >>>
从左向右
6
< <= > >= instanceof
从左向右
7
== !=
从左向右
8
&(按位与)
从左向右
9
^
从左向右
10
|
从左向右
11
&&
从左向右
12
||
从左向右
13
?:
从右向左
14
= += -= *= /= %= &= |= ^= ~= <<= >>= >>>=
从右向左
根据该表可以看到,位运算操作符的优先级各有不同,分别为:
1、~
2、>> << >>>
3、&
4、^
5、|
另外需要特别注意的是,除了~,其他位运算操作的优先级都低于加减,所以要记得以下语句是返回32而不是7!
1<<2+3
还有就是&、^、|的优先级都是低于逻辑操作符的,因此下面的语句会编译出错,幸好Java不像C那样对所有大于1的值都认为是真,否则下面的语句也能编译通过,但可能与你的意图不太一样,可能调试半天才发现。
if(3&1>0)
如果记不清楚,还是按照你的意图加上括号最保险。

第五、字节数组与整数之间的转换
为了把一个整数存入文件,或者从文件中读取一个整数,需要经常在字节数组和整数之间转换,这个过程要用到大量的位运算。
首先需要记住的是,在参与所有运算前,Java都会把byte、short类型的值都转换成int,然后再对转换后的int进行操作。例如下面的语句会编译出错:
byte a=10,b=20,c;
c=a+b;

因为a和b在相加前都被转成了int,最后得到的结果是个int类型的值,如果要赋给byte类型的c,必须显式地进行类型转换,即把第二句改为:
c=(byte)(a+b)

这一点很关键,因为对于一个最高位为1的byte类型的整数(负数),在运算之前它会被强制转换成int类型,根据上面所说的第三点,其实就是往前面的三个高字节补上1,这样一来,它在参与位运算的过程中,就不仅仅是它本身的8个bit参与了,实际上连前3个字节的24个bit(均为1)也参与了。例如有一个整数i=1082163328,它的二进制表示为:
01000000 10000000 10000000 10000000
分为4个字节存储,除了第一个字节是正数外,其余3个字节均为负数。假如用a代表最高字节的值,用b代表其他三个字节的值,如果按照通常的理解,你可能会这样得到i的值:
i=(a<<24)+(b<<16)+(b<<8)+b

如果a和b都是正数,上面的等式是成立的,但是在这个例子里,却是错的,因为上式中的a和b都已经被强制转换成了int类型再参加运算,实际上
a=00000000 00000000 00000000 01000000
b=11111111 11111111 11111111 10000000
i=01000000 00000000 00000000 00000000+11111111 10000000 00000000 00000000+11111111 11111111 10000000 00000000+11111111 11111111 11111111 10000000
最后得到的结果是1065320320,不是原来的值了。
为了不让byte在强制转换成int的过程加入了我们不想要的高位1,我们需要把它跟0xff进行与操作,i的值应该这样运算:
= ( ( a& 0xff ) << 24 ) +( ( b & 0xff ) << 16 ) + ( ( b & 0xff ) << 8 ) + ( b & 0xff )

注意,因为&和<<的优先级都低于+,所以上面的括号是不能少的。不过由于跟0xff与操作之后,其余24位都变成了0,因此可以把+改为|操作,因为任何值与0进行或操作都得到本身:
= ( a & 0xff ) << 24 | ( b & 0xff ) << 16 | ( b & 0xff ) << 8 | ( b & 0xff )

由于<<的优先级高于|,所以省了一些括号。最高字节可以不与0xff进行与操作,因为它转换成int后左边增加的3个字节都在左移24位时被去掉了:
= a << 24 | ( b & 0xff ) << 16 | ( b & 0xff ) << 8 | ( b & 0xff )


把int转为字节数组的时候比较简单,直接右移截断即可:
byte[] b = new byte[4];
b[0= (byte) (i >> 24);
b[1= (byte) (i >> 16);
b[2= (byte) (i >> 8);
b[3= (byte) i;


第六、非标准长度整数的存储和读取
假如有两个变量,他们的值可以用12个bit来表示,如果我们用16bit的short类型来表示一个变量,那么两个变量就需要4个字节,而实际上它们只需要3个字节就能表示出来,如果存储空间比较有限,写入文件时可以把它们存放在3个字节里面,但是读写过程就需要进行转换。
在内存里,它们都是标准的数据类型:
short a,b;

写入文件时,我们用第一个字节和第二个字节的前半部分来表示a,把第二个字节的后半部分和第三个字节来表示b,即:
1:xxxx xxxx
2:xxxx yyyy
3:yyyy yyyy
x和y都表示一个bit,分别用来存放a和b。写入时先把a和b转为字节数组:
byte[] out = new byte[3];
out[
0= (byte) ( a >> 4 );//把a的高8位放在第一个字节
out[1= (byte) ( a << 4 );//先把a左移四位,在右边补上4个0,第二个字节的高4位就是a的低4位了,第二个字节的高4位已经生成,低4位还是0
out[1|= (byte) ( b >> 8 & 0x0f );//b右移8位,并与0x0f进行与操作,实际上就只保留了b的高4位,并且是在字节的低4位上,跟第二步得到的字节进行或操作,就生成了第二个字节
out[2= (byte) b;//把b的高4位截断就得到了低8位
然后再把这个字节数组写入文件,就可以用3个字节表示两个整数了。
读取:
=(short)( (out[0& 0xff<< 4 | ( out[1& 0xf0 )>>4);
= (short)((out[1& 0x0f<< 8 | ( out[2& 0xff));


posted on 2012-04-08 16:56 amp@java 阅读(1631) 评论(2)  编辑  收藏 所属分类: Java common

评论

# re: Java的位操作 2012-04-09 14:53 五月花
二进制就是二进制,还要弄清是什么语言创建的?怎么创建的?  回复  更多评论
  

#  内容请不要发表任何与政治相关的内容 2012-04-10 16:36 内容请不要发表任何与政治相关的内容
内容请不要发表任何与政治相关的内容  回复  更多评论
  


只有注册用户登录后才能发表评论。


网站导航: