zoukankan      html  css  js  c++  java
  • Java中byte与16进制字符串的互换原理

    我们都知道Java中的byte是由8个bit组成的,而16进制即16中状态,它是由4个bit来表示的,因为24=16。所以我们可以把一个byte转换成两个用16进制字符,即把高4位和低4位转换成相应的16进制字符,并组合这两个16进制字符串,从而得到byte的16进制字符串。同理,相反的转换也是将两个16进制字符转换成一个byte。转换的函数如下:

    /**
     *  Convert byte[] to hex string
     * @param src
     * @return
     */
    public static String bytesToHexString(byte[] src){
    	StringBuilder stringBuilder = new StringBuilder("");
    	if(src==null||src.length<=0){
    		return null;
    	}
    	for (int i = 0; i < src.length; i++) {
    		int v = src[i] & 0xFF;
    		String hv = Integer.toHexString(v);
    		if (hv.length() < 2) {
    			stringBuilder.append(0);
    		}   
    		stringBuilder.append(hv);
    	}   
    	return stringBuilder.toString();   
    }
    
    /**
     * Convert hex string to byte[]
     * @param hexString
     * @return
     */
    public static byte[] hexStringToBytes(String hexString) {  
        if (hexString == null || hexString.equals("")) {  
            return null;  
        }  
        hexString = hexString.toUpperCase();  
        int length = hexString.length() / 2;  
        char[] hexChars = hexString.toCharArray();  
        byte[] d = new byte[length];  
        for (int i = 0; i < length; i++) {  
            int pos = i * 2;  
            d[i] = (byte) (charToByte(hexChars[pos]) << 4 | charToByte(hexChars[pos + 1]));  
        }  
        return d;  
    }
    
    /**
     * Convert char to byte
     * @param c
     * @return
     */
    private static byte charToByte(char c) {  
        return (byte) "0123456789ABCDEF".indexOf(c);  
    } 

    bytesToHexString方法中src[i] & 0xFF将一个byte和0xFF进行了与运算,然后使用Integer.toHexString取得了十六进制字符串,可以看出src[i] & 0xFF运算后得出的仍然是个int,那么为何要和0xFF进行与运算呢?直接 Integer.toHexString(src[i]);,将byte强转为int不行吗?答案是不行的.

    其原因在于:

    1. byte的大小为8bits而int的大小为32bits;
    2. java的二进制采用的是补码形式;

    如果还不明白,我们还是温习下计算机基础理论和Java的位运算知识吧。

    原码、反码和补码

    计算机中的符号数有三种表示方法,即原码、反码和补码。三种表示方法均有符号位和数值位两部分,符号位都是用0表示“正”,用1表示“负”,而数值位,三种表示方法各不相同。

    • 原码表示法是机器数的一种简单的表示法。其符号位用0表示正号,用:表示负号,数值一般用二进制形式表示。设有一数为x,则原码表示可记作[x]
      例如

      X1= +1010110
      X2= -1001010

      其原码记作:

      [X1]=[+1010110]=01010110
      [X2]=[-1001010]=11001010

    • 机器数的反码可由原码得到。如果机器数是正数,则该机器数的反码与原码一样;如果机器数是负数,则该机器数的反码是对它的原码(符号位除外)各位取反而得到的。设有一数X,则X的反码表示记作[X]
      例如

      X1= +1010110
      X2= -1001010

      [X1]=01010110
      [X1]=[X1]=01010110
      [X2]=11001010
      [X2]=10110101

    • 机器数的补码可由原码得到。如果机器数是正数,则该机器数的补码与原码一样;如果机器数是负数,则该机器数的补码是对它的原码(除符号位外)各位取反,并在未位加1而得到的。设有一数X,则X的补码表示记作[X]
      例如

      [X1]=+1010110
      [X2]=-1001010

      [X1]=01010110
      [X1]=01010110

      [X1]=[X1]=01010110
      [X2]= 11001010
      [X2]=10110101+1=10110110

    为何要使用原码, 反码和补码

    byte是一个字节保存的,有8个位,即8个0、1。8位的第一个位是符号位, 也就是说0000 0001代表的是数字1,而1000 0000代表的就是-1,所以正数最大位0111 1111,也就是数字127 负数最大为1111 1111,也就是数字-128。
    这里 0 是 00000000 ,而 10000000 是-1 ,正数计算里面去掉了一个0,所有最大值只能是2^7 -1 =127;而负数并没有用去掉0,所以是2^7 = -128 。
    现在我们知道了计算机可以有三种编码方式表示一个数。 对于正数因为三种编码方式的结果都相同:

    [+1] = [00000001] = [00000001] = [00000001]

    所以不需要过多解释, 但是对于负数:

    [-1] = [10000001] = [11111110] = [11111111]

    可见原码,,反码和补码是完全不同的。 既然原码才是被人脑直接识别并用于计算表示方式,为何还会有反码和补码呢?
    首先, 因为人脑可以知道第一位是符号位,在计算的时候我们会根据符号位,选择对真值区域的加减(真值的概念在本文最开头)。但是对于计算机,加减乘数已经是最基础的运算,要设计的尽量简单。计算机辨别"符号位"显然会让计算机的基础电路设计变得十分复杂! 于是人们想出了将符号位也参与运算的方法。我们知道,根据运算法则减去一个正数等于加上一个负数,即: 1-1 = 1 + (-1) = 0,所以机器可以只有加法而没有减法,这样计算机运算的设计就更简单了。
    于是人们开始探索 将符号位参与运算,并且只保留加法的方法。首先来看原码:计算十进制的表达式: 1-1=0

    1 - 1 = 1 + (-1) = [00000001] + [10000001] = [10000010] = -2

    如果用原码表示,让符号位也参与计算,显然对于减法来说,结果是不正确的。这也就是为何计算机内部不使用原码表示一个数。为了解决原码做减法的问题,出现了反码:

    1 - 1 = 1 + (-1) = [0000 0001] + [1000 0001]= [0000 0001] + [1111 1110] = [1111 1111] = [1000 0000] = -0

    发现用反码计算减法,结果的真值部分是正确的。 而唯一的问题其实就出现在"0"这个特殊的数值上。 虽然人们理解上+0和-0是一样的,但是0带符号是没有任何意义的。 而且会有[0000 0000]原和[1000 0000]原两个编码表示0。

    于是补码的出现,解决了0的符号以及两个编码的问题:

    1-1 = 1 + (-1) = [0000 0001] + [1000 0001] = [0000 0001] + [1111 1111] = [0000 0000]=[0000 0000]

    这样0用[0000 0000]表示,而以前出现问题的-0则不存在了。而且可以用[1000 0000]表示-128:

    (-1) + (-127) = [1000 0001] + [1111 1111] = [1111 1111] + [1000 0001] = [1000 0000]

    -1-127的结果应该是-128,在用补码运算的结果中,[1000 0000] 就是-128。 但是注意因为实际上是使用以前的-0的补码来表示-128,所以-128并没有原码和反码表示。(对-128的补码表示[1000 0000]算出来的原码是[0000 0000],这是不正确的)。

    使用补码,不仅仅修复了0的符号以及存在两个编码的问题,而且还能够多表示一个最低数。 这就是为什么8位二进制,使用原码或反码表示的范围为[-127,+127],而使用补码表示的范围为[-128,127]。

    因为机器使用补码,所以对于编程中常用到的32位int类型,可以表示范围是: [-231,231-1] 因为第一位表示的是符号位。而使用补码表示时又可以多保存一个最小值。

    Java的位运算

    位运算表达式由操作数和位运算符组成,实现对整数类型的二进制数进行位运算。位运算符可以分为逻辑运算符(包括~、&、|和^)及移位运算符(包括>>、<<和>>>)。

    1. 左移位运算符(<<)能将运算符左边的运算对象向左移动运算符右侧指定的位数(在低位补0)。
    2. “有符号”右移位运算符(>>)则将运算符左边的运算对象向右移动运算符右侧指定的位数。 “有符号”右移位运算符使用了“符号扩展”:若值为正,则在高位插入0;若值为负,则在高位插入1。
    3. Java也添加了一种“无符号”右移位运算符(>>>),它使用了“零扩展”:无论正负,都在高位插入0。这一运算符是C或C++没有的。
    4. 若对char,byte或者short进行移位处理,那么在移位进行之前,它们会自动转换成一个int,转换时使用“符号扩展规则”。

    在进行位运算时,需要注意以下几点。   

    1. >>>和>>的区别是:在执行运算时,>>>运算符的操作数高位补0,而>>运算符的操作数高位移入原来高位的值。
    2. 右移一位相当于除以2,左移一位(在不溢出的情况下)相当于乘以2;移位运算速度高于乘除运算。   
    3. 若进行位逻辑运算的两个操作数的数据长度不相同,则返回值应该是数据长度较长的数据类型。   
    4. 按位异或可以不使用临时变量完成两个值的交换,也可以使某个整型数的特定位的值翻转。   
    5. 按位与运算可以用来屏蔽特定的位,也可以用来取某个数型数中某些特定的位。   
    6. 按位或运算可以用来对某个整型数的特定位的值置。

    位运算符的优先级:~的优先级最高,其次是<<、>>和>>>,再次是&,然后是^,优先级最低的是|。

    回顾

    回顾上述问题:为什么在bytesToHexString方法中不直接把byte类型的src[i]强制转换成int使用?

    因为:byte会转换成int时,对于负数,会做符号扩展,如byte的-1(即0xff),转换成int的-1会扩展成0xffffffff,显然这不是我们所需要的。而把0xffffffff与0xff做与运算就能把高24位清零,这才是我们需要的。

    Java的MD5

    有了上述的理论知识我们不能写出MD5的加密方法啦

    /**
     * MD5加密
     * @param oraginalStr
     * @return
     * @throws NoSuchAlgorithmException
     */
    public static String md5(String oraginalStr) throws NoSuchAlgorithmException{
    	MessageDigest md5=MessageDigest.getInstance("MD5");
    	md5.update(oraginalStr.getBytes());
    	
    	return bytesToHexString(md5.digest()).toUpperCase(); 
    }

    附录:位操作用途

    位与运算的主要用途如下:

    1. 清零:快速对某一段数据单元的数据清零,即将其全部的二进制位为0。例如整型数a=321对其全部数据清零的操作为a&0x0。
        321= 0000 0001 0100 0001
      & 0= 0000 0000 0000 0000
      =   0000 0000 0000 0000
    2. 获取一个数据的指定位。例如获得整型数a=321的低八位数据的操作为a&0xFF。
        321= 0000 0001 0100 0001
      & 0xFF= 0000 0000 1111 11111
      =   0000 0000 0100 0001
      获得整型数a的高八位数据的操作为a&0xFF00
    3. 保留数据区的特定位。例如获得整型数a=的第7-8位(从0开始)位的数据:
        321= 0000 0001 0100 0001
      & 384= 0000 0001 1000 0000
      =   0000 0001 0000 0000

    位或运算的主要用途:设定一个数据的指定位。例如整型数a=321,将其低八位数据置为1的操作为a=a|0XFF。

      321= 0000 0001 0100 0001
    | 0XFF= 0000 0000 1111 1111
    =   0000 0000 1111 1111

    位异或运算的主要用途:

    1. 定位翻转:设定一个数据的指定位,将1换为0,0换为1。例如整型数a=321,,将其低八位数据进行翻位的操作为a^0XFF
        321= 0000 0001 0100 0001
      ^ 0XFF= 0000 0000 1111 1111
      =   0000 0001 1011 1110
    2. 数值交换:例如a=3,b=4,无须引入第三个变量,利用位运算即可实现数据交换:
      int a=3,b=4;
      System.out.println(a+","+b);
      a=a^b;
      b=b^a;
      a=a^b;
      System.out.println(a+","+b); 
      输出:

      3,4
      4,3

    左移运算主要用于除2操作,右移运算用于乘2操作,当然他们必须在不溢出的情况下。

    出处:http://www.zhaiqianfeng.com    
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
  • 相关阅读:
    支持向量机SVM知识点概括
    决策树知识点概括
    HDU 3081 Marriage Match II
    HDU 3572 Task Schedule
    HDU 4888 Redraw Beautiful Drawings
    Poj 2728 Desert King
    HDU 3926 Hand in Hand
    HDU 1598 find the most comfortable road
    HDU 4393 Throw nails
    POJ 1486 Sorting Slides
  • 原文地址:https://www.cnblogs.com/zhaiqianfeng/p/4620404.html
Copyright © 2011-2022 走看看