Java NIO中的“码”与字节序问题

1,150 阅读3分钟

原码、补码、反码三者的关系

       计算机中的码可以有三种表示,分别是原码,补码和反码。原码自然就是直接将十进制码转换成的二进制码,最高位表示符号。原码的优点是直观易懂,机器数和真数之间转换方便,实现乘除比较容易,加减比较困难。而补码就是弥补原码的加减比较困难的缺点。补码的设想是:使符号位也参与运算,从而实现加减法的运算规则;把减法运算转换成加法运算,从而减化运算电路的复杂度。
        反码是一种中间码。它既不是程序员所看到码,也不是计算机最终所存储的补码。反码解决负数加法运算问题,将减法运算转换为加法运算,从而简化运算规则,是一种在运算过程中间所产生的中间码。
总之,计算机存储的是补码;反码只不过是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模式。有关字节序的问题,可以参考字节序