zoukankan      html  css  js  c++  java
  • 字节的游戏

    业务处理上,有时会直接对字节进行操作。例如实现私有协议,对校验位进行检测,敏感数据加密等。博主查了 一下网上的资料,发现有不少都是错误的。甚至连《Thinking in Java》的解释都很令人困惑,以下是从书中摘录的原文:

    如果对char、byte或者short类型的数值惊醒移位处理,那么在移位之前,他们会被转换为int类型,并且得到的结果也是一个int类型。只有数值右端的低5位才有用。

    当时读到这一句的时候,我理解了很久,至今没有明白“只有数值右端的低5位才有用”的含义。理解字节处理的基本方法就是动手操作,下面我会结合用例进行解释。

    首先,我们需要理解几个基础概念。一般来说,字节是我们可以用语言处理的最小对象,无论是C/C++还是Java都没有直接提供bit类型。1 byte = 8 bit,除去最左侧的符号位1byte可以描述的范围是:-128 ~ 127。但是在大多数的业务处理中,我们通常会采用无符号位,即用1byte表示:0 ~ 255。其次,常见的移位操作符有左移(<<) 和右移 (>>),比较容易忽视的是右移操作,如果最左侧的符号位为1则右移是在高位插入的是——1。因此Java中增加了一种“无符号”右位移操作符>>>,通常用不上了解即可。最后,如果我们采用byte[]来表示一种数据类型,数组下标从小到大即内存地址的从低位到高位。记住这个概念非常重要,后面我会引入大端模式与小端模式。

    为了让大家理解以上概念,下面看两个例子:

    1. 假设byte x = 127,对它执行左移1位的操作 x = ?

    byte x = 127;
    x <<= 1;
    System.out.println(Integer.toHexString(x));

    在代码执行之前我们先使用计算器计算一下:BIN(1111 1110) HEX(FE),代码的执行结果为:FFFFFFFE。原因是对x左移1位超出了byte的表示范围,Java自动在左侧补位,由于最高位是1,因此我们获得了一个怪异的结果。那么有什么办法得到一个正确的结果呢?

    byte x = 127;
    x <<= 1;
    System.out.println(Integer.toHexString(x & 0xFF));

    2. 假设byte x = 1,对它执行左移32位的操作 x = ?

    byte x = 1;
    System.out.println(x << 32);

    答案是1。这个结论比较怪异而且确实是一个坑,大家只需要记住:对一个int值来说,左移32位等于它的原始值;对于一个long值来说,左移64位等于它的原始值。

    在理解了这些基本概念以后,我们已经做好了进入字节世界的准备。

    我们如何用4个字节的大端模式表示一个整型变量?

    对大端模式的定义为:数据的高字节保存在内存的低地址中,而数据的低字节保存在内存的高地址中。这个说法很绕而且也不利于理解,对于数字常量来说0x1234,1即为高位,4即为低位。而对于byte[4]来说,bs[0]即为地址低位,bs[3]即为地址高位。这样看来就很清楚了。大端模式符合人们的阅读模式。

    int i = 0x1234;
    byte[] bs = new byte[4];
    bs[3] = (byte) (i & 0xFF);
    bs[2] = (byte) (i >> 8 & 0xFF);
    bs[1] = (byte) (i >> 16 & 0xFF);
    bs[0] = (byte) (i >> 24 & 0xFF);
    
    for(byte b : bs) {
        System.out.println(Integer.toHexString(b));
    }

    更抽象的算法,大家可以在理解了上面的例子以后自己封装。

    反过来我们将以大端模式生成的4个字节还原为一个整型数?

    int x = bs[3] & 0xFF;
    x |= bs[2] & 0xFF << 8;
    x |= bs[1] & 0xFF << 16;
    x |= bs[0] & 0xFF << 24;
    
    System.out.println(Integer.toHexString(x));

    注意:为了得到正确的结果,我们在对byte进行移位前一定要先做位与(&)操作。

    接下来我们需要升级问题,将一个8个字节宽度的符合大端模式的字节数组还原为一个长整型数。

    long x = bs[7] & 0xFF;
    x |= (bs[6] & 0xFF) << 8;
    x |= (bs[5] & 0xFF) << 16;
    x |= (bs[4] & 0xFF) << 24;
    x |= (bs[3] & 0xFF) << 32;
    x |= (bs[2] & 0xFF) << 40;
    x |= (bs[1] & 0xFF) << 48;
    x |= (bs[0] & 0xFF) << 56;
    System.out.println(Long.toHexString(x));

    似乎我们很容易按照整型的转换方式得到以上算法。不幸的是,这样做是错误的。如果这个byte[]表示的数字范围超过整型数的上限,我们将无法获得正确的长整型数。原因是Java默认在对byte进行移位操作前会转换为int类型,还记得上面我们让大家记住“对一个int值来说,左移32位等于它的原始值”吗?正确的做法应该是这样:

    long x = bs[7] & 0xFF;
    x |= ((long)bs[6] & 0xFF) << 8;
    x |= ((long)bs[5] & 0xFF) << 16;
    x |= ((long)bs[4] & 0xFF) << 24;
    x |= ((long)bs[3] & 0xFF) << 32;
    x |= ((long)bs[2] & 0xFF) << 40;
    x |= ((long)bs[1] & 0xFF) << 48;
    x |= ((long)bs[0] & 0xFF) << 56;
    System.out.println(Long.toHexString(x));

    至此我们应该可以很轻松的解决有关字节转换的各种难题了,但是上面的这些算法未免显得太不优美,幸亏Java早就为我们想到了这一点。本着不要重复造轮子的观点,我提供了一套工具。

    /**
     * 任意字节宽度转换为标准整型数
     */
    public static int bytesToInt(byte[] bytes, int byteNum, ByteOrder order) {
        ByteBuffer buffer = ByteBuffer.allocate(4);
        buffer.order(order);
        buffer.put(bytes, 0, bytes.length);
        buffer.put(new byte[buffer.limit() - byteNum], 0, buffer.limit() - byteNum);
        buffer.flip();
        return buffer.getInt();
    }
    
    /**
     * 长整型数转换为指定字节宽度
     */
    public static byte[] longToBytes(long x, int byteNum, ByteOrder order) {
        ByteBuffer buffer = ByteBuffer.allocate(8);
        buffer.order(order);
        buffer.putLong(0, x);
        return Arrays.copyOfRange(buffer.array(), 0, byteNum);
    }
    
    /**
     * 任意字节宽度转换为长整型
     */
    public static long bytesToLong(byte[] bytes, int byteNum, ByteOrder order) {
        ByteBuffer buffer = ByteBuffer.allocate(8);
        buffer.order(order);
        buffer.put(bytes, 0, bytes.length);
        buffer.put(new byte[buffer.limit() - byteNum], 0, buffer.limit() - byteNum);
        buffer.flip();
        return buffer.getLong();
    }
    
    /**
     * 长整型数转换为标准的8字节宽度
     */
    public static byte[] longToBytes(long x, ByteOrder order) {
        ByteBuffer buffer = ByteBuffer.allocate(8);
        buffer.order(order);
        buffer.putLong(0, x);
        return buffer.array();
    }
    
    /**
     * 标准8字节宽度转换为长整型数
     */
    public static long bytesToLong(byte[] bytes, ByteOrder order) {
        ByteBuffer buffer = ByteBuffer.allocate(8);
        buffer.order(order);
        buffer.put(bytes, 0, bytes.length);
        buffer.flip();
        return buffer.getLong();
    }
    
    /**
     * 整型数转换为标准4字节宽度
     */
    public static byte[] intToBytes(int x, ByteOrder order) {
        ByteBuffer buffer = ByteBuffer.allocate(4);
        buffer.order(order);
        buffer.putInt(0, x);
        return buffer.array();
    }
    
    /**
     * 标准4字节宽度转换为整型数
     */
    public static int bytesToInt(byte[] bytes, ByteOrder order) {
        ByteBuffer buffer = ByteBuffer.allocate(4);
        buffer.order(order);
        buffer.put(bytes, 0, bytes.length);
        buffer.flip();
        return buffer.getInt();
    }
  • 相关阅读:
    python+opencv实现图像自适应阈值的均衡化
    ubuntu添加新的分辨率选项(干货)
    python+opencv检测图像清晰度
    python根据列表创建文件夹,拷贝指定文件
    牛客多校Round 4
    牛客多校Round 3
    HDU多校Round 2
    HDU多校Round 1
    牛客多校Round 2
    牛客多校Round 1
  • 原文地址:https://www.cnblogs.com/learnhow/p/10800153.html
Copyright © 2011-2022 走看看