原码、补码、反码三者的关系
计算机中的码可以有三种表示,分别是原码,补码和反码。原码自然就是直接将十进制码转换成的二进制码,最高位表示符号。原码的优点是直观易懂,机器数和真数之间转换方便,实现乘除比较容易,加减比较困难。而补码就是弥补原码的加减比较困难的缺点。补码的设想是:使符号位也参与运算,从而实现加减法的运算规则;把减法运算转换成加法运算,从而减化运算电路的复杂度。
反码是一种中间码。它既不是程序员所看到码,也不是计算机最终所存储的补码。反码解决负数加法运算问题,将减法运算转换为加法运算,从而简化运算规则,是一种在运算过程中间所产生的中间码。
总之,计算机存储的是补码;反码只不过是CPU内部运算的产生的中间产物。
java 中的码
Java中的基本类型都是有符号的类型,也就是最高位是符号位。一个short类型在java中是两个字节。最高位符号位,那么只有15位来表示实际的数值。那么short类型的最大值应该是2^15-1。即32767。如果将short类型的值赋值的比32767大,那么IDE将会报错,出现一个程序员喜闻乐见的错误提醒。
reverseBytes:以8位为一个整体反转二进制补码所得到的值。
public class CA {
public static void main(String[] args) {
short a = 9; // 二进制补码表示: 00000000 00001001
short b = Short.reverseBytes(a);// 00001001 00000000
System.out.println(b);
toBinary(b);
}
public static void toBinary(int num) {
if (num / 2 == 0) {// 此时已经计算到了结束
System.out.print(num % 2);
} else {
toBinary(num / 2);// 向下继续计算
System.out.print(num % 2);
}
}
}从源码即可看出是以8位为一个整体来反转。
public static short reverseBytes(short i) {
return (short) (((i & 0xFF00) >> 8) | (i << 8));
}reverseBytes只有一个地方用到。那就是NIO中的Buffer。都知道Buffer是NIO中的缓冲区,数据从通道读入缓冲区,又从缓冲区写入到通道中的。其中有两个方法很重要:put方法与get方法。put方法是将要缓存的数据直接放进内存,get方法是从内存中直接取出数据。JAVA最终的存储是在调用native的C/C++方法。
我们以DirectShortBufferS为例,里面有两个方法:
public short get(int i) {
return (Bits.swap(unsafe.getShort(ix(checkIndex(i)))));
}
public ShortBuffer put(int i, short x) {
unsafe.putShort(ix(checkIndex(i)), Bits.swap((x)));
return this;
}在设置值和put值的时候都通过了swap方法。Bits.swap方法只是reverseBytes的一个封装:
class Bits {// package-private
// -- Swapping --
static short swap(short x) {
return Short.reverseBytes(x);
} 为什么在put的时候,将值swap一遍再放进内存,然后在get的时候又swap一遍才返回呢?这不是相当于什么都没有做吗?
原来之所以要这么做,是因为java中的字节序与依赖平台的C/C++的字节存储顺序不一样导致的。
字节存储顺序又叫字节序,顾名思义就是字节的存储顺序,再多说两句就是大于一个字节类型的数据在内存中的存放顺序(一个字节的数据当然就无需谈顺序的问题了)。一般有两种存储方式:
1.从低位字节存放起始数,地址越高值越往后。就是所谓的 Little endian。
2.从高位字节开始存储,地址越低越往后,就是所谓的 Big endian。
由于Java 是Big endian,而C/C++在很多情况下(与操作系统,CPU有关)是Little endian。所以,java直接存储值到内存中,需要先转换成Little endian模式。有关字节序的问题,可以参考字节序