【问题标题】:In Java, how do I convert a byte array to a string of hex digits while keeping leading zeros? [duplicate]在 Java 中,如何将字节数组转换为十六进制数字字符串,同时保持前导零? [复制]
【发布时间】:2010-09-24 19:53:31
【问题描述】:

我正在使用一些示例 java 代码来制作 md5 哈希。一部分将结果从字节转换为一串十六进制数字:

byte messageDigest[] = algorithm.digest();     
StringBuffer hexString = new StringBuffer();
for (int i=0;i<messageDigest.length;i++) {
    hexString.append(Integer.toHexString(0xFF & messageDigest[i]));
    }

但是,由于 toHexString 显然去掉了前导零,因此它并不完全有效。那么,从字节数组到保持前导零的十六进制字符串的最简单方法是什么?

【问题讨论】:

    标签: java md5 hex


    【解决方案1】:

    这是我用于 MD5 哈希的内容:

    public static String getMD5(String filename)
            throws NoSuchAlgorithmException, IOException {
        MessageDigest messageDigest = 
            java.security.MessageDigest.getInstance("MD5");
    
        InputStream in = new FileInputStream(filename);
    
        byte [] buffer = new byte[8192];
        int len = in.read(buffer, 0, buffer.length);
    
        while (len > 0) {
            messageDigest.update(buffer, 0, len);
            len = in.read(buffer, 0, buffer.length);
        }
        in.close();
    
        return new BigInteger(1, messageDigest.digest()).toString(16);
    }
    

    编辑:我已经测试过,我注意到这也减少了尾随零。但这只能在开始时发生,因此您可以与预期的长度进行比较并相应地填充。

    【讨论】:

      【解决方案2】:

      一种简单的方法是检查Integer.toHexString() 输出了多少位,并在需要时为每个字节添加前导零。像这样的:

      public static String toHexString(byte[] bytes) {
          StringBuilder hexString = new StringBuilder();
      
          for (int i = 0; i < bytes.length; i++) {
              String hex = Integer.toHexString(0xFF & bytes[i]);
              if (hex.length() == 1) {
                  hexString.append('0');
              }
              hexString.append(hex);
          }
      
          return hexString.toString();
      }
      

      【讨论】:

      • 不会为字节 0x01 生成“10”吗?
      • 不,0 在十六进制值之前附加到 hexString
      • 当我打电话给Integer.toHexString((byte)0xff) 时,由于符号扩展,它返回了“ffffffff”。所以可能需要获取返回字符串的最后两个字符。
      • 这不会返回额外的零吗?例如,如果字节数组是{0,1,2,3},它应该返回0123,但它会返回00010203,或者它是哈希的期望结果?
      • @juzerali:这个问题需要“同时保持前导零”。如果您不想要前导零,则没有理由使用此代码;只需使用问题中的代码即可。
      【解决方案3】:
      String result = String.format("%0" + messageDigest.length + "s", hexString.toString())
      

      鉴于您已有的情况,这是最短的解决方案。如果可以将字节数组转换为数值,String.format 可以同时将其转换为十六进制字符串。

      【讨论】:

      • 非常优雅,除了 Java 1.5+ 的要求。虽然现在不是问题......
      • 不起作用:线程“main”中的异常 java.util.FormatFlagsConversionMismatchException: Conversion = s, Flags = 0
      【解决方案4】:
      byte messageDigest[] = algorithm.digest();
      StringBuffer hexString = new StringBuffer();
      for (int i = 0; i < messageDigest.length; i++) {
          String hexByte = Integer.toHexString(0xFF & messageDigest[i]);
          int numDigits = 2 - hexByte.length();
          while (numDigits-- > 0) {
              hexString.append('0');
          }
          hexString.append(hexByte);
      }
      

      【讨论】:

        【解决方案5】:

        Apache Commons Codec 签出Hex.encodeHexString

        import org.apache.commons.codec.binary.Hex;
        
        String hex = Hex.encodeHexString(bytes);
        

        【讨论】:

        • 而且只要你使用 Apache Commons Codec 做 md5,看看DigestUtils.md5Hex()
        • DigestUtils 确实让事情变得更容易一些,但是将它包含在您的项目中可能会很麻烦。就我个人而言,一想到要弄乱 pom 文件,我就会呻吟。
        【解决方案6】:

        这个解决方案有点老,应该是内存效率的。

        public static String toHexString(byte bytes[]) {
            if (bytes == null) {
                return null;
            }
        
            StringBuffer sb = new StringBuffer();
            for (int iter = 0; iter < bytes.length; iter++) {
                byte high = (byte) ( (bytes[iter] & 0xf0) >> 4);
                byte low =  (byte)   (bytes[iter] & 0x0f);
                sb.append(nibble2char(high));
                sb.append(nibble2char(low));
            }
        
            return sb.toString();
        }
        
        private static char nibble2char(byte b) {
            byte nibble = (byte) (b & 0x0f);
            if (nibble < 10) {
                return (char) ('0' + nibble);
            }
            return (char) ('a' + nibble - 10);
        }
        

        【讨论】:

          【解决方案7】:

          您可以使用下面的那个。我用前导零字节和初始负字节测试了这个

          public static String toHex(byte[] bytes) {
              BigInteger bi = new BigInteger(1, bytes);
              return String.format("%0" + (bytes.length << 1) + "X", bi);
          }
          

          如果您需要小写十六进制数字,请使用字符串格式的"x"

          【讨论】:

          • 没有外部依赖,很好很短。另外,如果您知道您有 16 个字节/32 个十六进制数字,那么您的解决方案将浓缩为一个简单的单行。酷!
          • 效果很好,谢谢。
          • 谢谢。我需要它来将 16 字节 IPv6 字节数组转换为 Scala 中的零填充十六进制字符串:f"${BigInt(1, myIpv6ByteArray)}%032x"
          【解决方案8】:

          另一种选择

          public static String toHexString(byte[]bytes) {
              StringBuilder sb = new StringBuilder(bytes.length*2);
              for(byte b: bytes)
                sb.append(Integer.toHexString(b+0x800).substring(1));
              return sb.toString();
          }
          

          【讨论】:

            【解决方案9】:

            我发现 Integer.toHexString 有点慢。如果要转换许多字节,您可能需要考虑构建一个包含“00”..“FF”的字符串数组,并使用整数作为索引。即

            hexString.append(hexArray[0xFF & messageDigest[i]]);
            

            这样更快并确保正确的长度。只需要字符串数组:

            String[] hexArray = {
            "00","01","02","03","04","05","06","07","08","09","0A","0B","0C","0D","0E","0F",
            "10","11","12","13","14","15","16","17","18","19","1A","1B","1C","1D","1E","1F",
            "20","21","22","23","24","25","26","27","28","29","2A","2B","2C","2D","2E","2F",
            "30","31","32","33","34","35","36","37","38","39","3A","3B","3C","3D","3E","3F",
            "40","41","42","43","44","45","46","47","48","49","4A","4B","4C","4D","4E","4F",
            "50","51","52","53","54","55","56","57","58","59","5A","5B","5C","5D","5E","5F",
            "60","61","62","63","64","65","66","67","68","69","6A","6B","6C","6D","6E","6F",
            "70","71","72","73","74","75","76","77","78","79","7A","7B","7C","7D","7E","7F",
            "80","81","82","83","84","85","86","87","88","89","8A","8B","8C","8D","8E","8F",
            "90","91","92","93","94","95","96","97","98","99","9A","9B","9C","9D","9E","9F",
            "A0","A1","A2","A3","A4","A5","A6","A7","A8","A9","AA","AB","AC","AD","AE","AF",
            "B0","B1","B2","B3","B4","B5","B6","B7","B8","B9","BA","BB","BC","BD","BE","BF",
            "C0","C1","C2","C3","C4","C5","C6","C7","C8","C9","CA","CB","CC","CD","CE","CF",
            "D0","D1","D2","D3","D4","D5","D6","D7","D8","D9","DA","DB","DC","DD","DE","DF",
            "E0","E1","E2","E3","E4","E5","E6","E7","E8","E9","EA","EB","EC","ED","EE","EF",
            "F0","F1","F2","F3","F4","F5","F6","F7","F8","F9","FA","FB","FC","FD","FE","FF"};
            

            【讨论】:

            • @Marvo 0x000000FF == 0xFF,所以您提出的更改没有任何作用。掩码只是一个整数,就像任何其他数字一样。 0xFF != -1
            【解决方案10】:

            我一直在寻找同样的东西......这里有一些好主意,但我运行了一些微基准测试。我发现以下是最快的(根据 Ayman 的上述修改,大约是 2 倍,比 Steve 的快大约 50%):

            public static String hash(String text, String algorithm)
                    throws NoSuchAlgorithmException {
                byte[] hash = MessageDigest.getInstance(algorithm).digest(text.getBytes());
                return new BigInteger(1, hash).toString(16);
            }
            

            编辑:糟糕——错过了这与 kgiannakakis 的基本相同,因此可能会去掉前导 0。不过,将其修改为以下内容,它仍然是最快的:

            public static String hash(String text, String algorithm)
                    throws NoSuchAlgorithmException {
                byte[] hash = MessageDigest.getInstance(algorithm).digest(text.getBytes());
                BigInteger bi = new BigInteger(1, hash);
                String result = bi.toString(16);
                if (result.length() % 2 != 0) {
                    return "0" + result;
                }
                return result;
            }
            

            【讨论】:

            • 这还是不对。例如,如果哈希是{0, 0, 0, 0}BigIntegertoString 将只给出"0"。此代码添加另一个"0" 并返回"00",但结果应为"00000000"
            • BigInteger.toString() 是迄今为止我在 Java 中发现的最慢的方法,作为高性能实现慢了大约 100 倍,请参阅stackoverflow.com/a/58118078/774398。同样在您的回答中计算了一个哈希值,但这不是问题的一部分。
            • 这不是 OP 所要求的,但是如果您查找哈希字符串并将最后一个更改为 while(length
            【解决方案11】:

            看起来 concat 和 append 函数可能真的很慢。以下对我来说要快得多(比我以前的帖子)。在构建输出时更改为 char 数组是加快输出速度的关键因素。我没有与 Brandon DuRette 建议的 Hex.encodeHex 进行比较。

            public static String toHexString(byte[] bytes) {
                char[] hexArray = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
                char[] hexChars = new char[10000000];
                int c = 0;
                int v;
                for ( j = 0; j < bytes.length; j++ ) {
                    v = bytes[j] & 0xFF;
                    hexChars[c] = hexArray[v/16];
                    c++;
                    hexChars[c] = hexArray[v%16];
                    c++;
                }
                return new String(hexChars, 0, c); }
            

            【讨论】:

            • 那两兆字节的分配 (= new char[10000000];) 完全没有必要和浪费。
            • 20 兆字节,哈哈。虽然bytes.length * 4 字节就足够了。
            【解决方案12】:

            我喜欢 Steve 的提交,但他本可以在没有几个变量的情况下完成并在此过程中节省了几行代码。

            public static String toHexString(byte[] bytes) {
                char[] hexArray = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
                char[] hexChars = new char[bytes.length * 2];
                int v;
                for ( int j = 0; j < bytes.length; j++ ) {
                    v = bytes[j] & 0xFF;
                    hexChars[j*2] = hexArray[v/16];
                    hexChars[j*2 + 1] = hexArray[v%16];
                }
                return new String(hexChars);
            }
            

            我喜欢它的一点是,它很容易准确地看到它在做什么(而不是依赖一些神奇的 BigInteger 黑盒转换),而且您也不必担心诸如前导零之类的极端情况。该例程采用每个 4 位半字节并将其转换为十六进制字符。它使用表格查找,所以它可能很快。如果您将 v/16 和 v%16 替换为按位移位和 AND 可能会更快,但我现在懒得测试它。

            【讨论】:

            • 不错!通过使其适用于任意大小的字节数组,改进了 Steve 的“追加速度很慢”的想法。
            • 将 v/16 更改为 v >>> 4 并将 v%16 更改为 v & 0x0F 以提高速度。此外,您可以使用 j
            • 或者,最好将值添加到 '0' 以获取字符,这样就不需要查找表。例如hexChars[j >> 4 + '0')
            • (我的错!ASCII表没有a-f或A-F跟随0-9,前面的不行)
            • 一个反函数,也许有人需要它。 public static byte[] bytesFromHex(String hexString) { final char[] hexArray = {'0', '1', '2', '3', '4', '5', '6', '7', '8'、'9'、'A'、'B'、'C'、'D'、'E'、'F'}; char[] hexChars = hexString.toCharArray();字节[] 结果 = 新字节[hexChars.length / 2]; for (int j = 0; j
            【解决方案13】:
            static String toHex(byte[] digest) {
                StringBuilder sb = new StringBuilder();
                for (byte b : digest) {
                    sb.append(String.format("%1$02X", b));
                }
            
                return sb.toString();
            }
            

            【讨论】:

            • StringBuilder 的默认初始容量为 16 个字符。一个 MD5 哈希由 32 个字符组成。附加前 16 个字符后,内部数组将被复制到长度为 34 的新数组。String.format 还为摘要的每个字节创建一个新的Formatter 实例。默认情况下,每个Formatter 都会实例化一个新的StringBuilder 来缓冲其输出。我什至认为创建一个 StringBuffer 初始容量为 32 个字符 (new Formatter(new StringBuilder(32))) 的 Formatter 并使用其 formattoString 方法更容易。
            • 当然,对于可变摘要长度,您将使用 digest.length * 2 的初始容量。
            【解决方案14】:

            为了保持前导零,以下是 Paul 建议的一个小变化(例如 md5 哈希):

            public static String MD5hash(String text) throws NoSuchAlgorithmException {
                byte[] hash = MessageDigest.getInstance("MD5").digest(text.getBytes());
                return String.format("%032x",new BigInteger(1, hash));
            }
            

            糟糕,这看起来比 Ayman 建议的要差,抱歉

            【讨论】:

              【解决方案15】:
              static String toHex(byte[] digest) {
                  String digits = "0123456789abcdef";
                  StringBuilder sb = new StringBuilder(digest.length * 2);
                  for (byte b : digest) {
                      int bi = b & 0xff;
                      sb.append(digits.charAt(bi >> 4));
                      sb.append(digits.charAt(bi & 0xf));
                  }
                  return sb.toString();
              }
              

              【讨论】:

              • 我很想看看这与 Jemenake 的解决方案相比如何
              【解决方案16】:

              恕我直言,上述所有提供 sn-ps 删除前导零的解决方案都是错误的。

              byte messageDigest[] = algorithm.digest();
              for (int i = 0; i < messageDigest.length; i++) {
                  hexString.append(Integer.toHexString(0xFF & messageDigest[i]));
              }    
              

              根据这个sn-p,从字节数组中取出8位 迭代,转换为整数(因为 Integer.toHexString 函数需要 int 作为参数),然后将该整数转换为相应的哈希 价值。因此,例如,如果您有 00000001 00000001 二进制,根据 代码中,hexString 变量将 0x11 作为十六进制值,而 正确的值应该是 0x0101。因此,在计算 MD5 时,我们可能会得到哈希 长度

              问题的解决方法是将上面的代码sn-p替换为 以下sn-p:

              byte messageDigest[] = algorithm.digest();
              for (int i = 0; i < messageDigest.length; i++) {
                  int temp=0xFF & messageDigest[i];
                  String s=Integer.toHexString(temp);
                  if(temp<=0x0F){
                      s="0"+s;
                  }
                  hexString.append(s);
              }
              

              【讨论】:

                【解决方案17】:

                这也是等效的,但使用 Apache util HexBin 代码简化为更简洁

                HexBin.encode(messageDigest).toLowerCase();
                

                【讨论】:

                  【解决方案18】:

                  如果没有外部库,你可以减少它的编写:

                  String hex = (new HexBinaryAdapter()).marshal(md5.digest(YOUR_STRING.getBytes()))
                  

                  【讨论】:

                    【解决方案19】:

                    这个解决方案不需要位移或掩码、查找表或外部库,并且是我能得到的尽可能短的:

                    byte[] digest = new byte[16];       
                    
                    Formatter fmt = new Formatter();    
                    for (byte b : digest) { 
                      fmt.format("%02X", b);    
                    }
                    
                    fmt.toString()
                    

                    【讨论】:

                      【解决方案20】:

                      javax.xml.bind.DatatypeConverter.printHexBinary() 方法是Java Architecture for XML Binding (JAXB) 的一部分,是一种将byte[] 转换为十六进制字符串的便捷方法。 DatatypeConverter 类还包括许多其他有用的数据操作方法。

                      在 Java 8 及更早版本中,JAXB 是 Java 标准库的一部分。它是 deprecated 使用 Java 9 和 removed 使用 Java 11,作为将所有 Java EE 包移动到它们自己的库中的努力的一部分。 It's a long story。现在,javax.xml.bind 不存在,如果要使用包含 DatatypeConverter 的 JAXB,则需要从 Maven 安装 JAXB APIJAXB Runtime

                      示例用法:

                      byte bytes[] = {(byte)0, (byte)0, (byte)134, (byte)0, (byte)61};
                      String hex = javax.xml.bind.DatatypeConverter.printHexBinary(bytes);
                      

                      将导致:

                      000086003D
                      

                      【讨论】:

                      • 对于倒车,还有DatatypeConverter.parseHexBinary(hexString)
                      • 请记住,从 Java 11 开始,java.xml 包不再是 JDK 的一部分。
                      【解决方案21】:

                      这将为一个字节提供两个字符的长字符串。

                      public String toString(byte b){
                          final char[] Hex = new String("0123456789ABCDEF").toCharArray();
                          return  "0x"+ Hex[(b & 0xF0) >> 4]+ Hex[(b & 0x0F)];
                      }
                      

                      【讨论】:

                        【解决方案22】:

                        你怎么能再次从 ascii 转换回字节数组?

                        我按照以下代码转换为 Jemenake 给出的 ascii。

                        public static String toHexString(byte[] bytes) {
                            char[] hexArray = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
                            char[] hexChars = new char[bytes.length * 2];
                            int v;
                            for ( int j = 0; j < bytes.length; j++ ) {
                                v = bytes[j] & 0xFF;
                                hexChars[j*2] = hexArray[v/16];
                                hexChars[j*2 + 1] = hexArray[v%16];
                            }
                            return new String(hexChars);
                        }
                        

                        【讨论】:

                          【解决方案23】:

                          我的变种

                              StringBuilder builder = new StringBuilder();
                              for (byte b : bytes)
                              {
                                  builder.append(Character.forDigit(b/16, 16));
                                  builder.append(Character.forDigit(b % 16, 16));
                              }
                              System.out.println(builder.toString());
                          

                          它对我有用。

                          【讨论】:

                            【解决方案24】:

                            这是一个错误的解决方案吗? (android java)

                                // Create MD5 Hash
                                MessageDigest digest = java.security.MessageDigest.getInstance("MD5");
                                digest.update(s.getBytes());
                                byte[] md5sum = digest.digest();
                                BigInteger bigInt = new BigInteger(1, md5sum);
                                String stringMD5 = bigInt.toString(16);
                                // Fill to 32 chars
                                stringMD5 = String.format("%32s", stringMD5).replace(' ', '0');
                                return stringMD5;
                            

                            所以基本上它用 0 替换空格。

                            【讨论】:

                              【解决方案25】:

                              Guava 也很简单:

                              BaseEncoding.base16().encode( bytes );
                              

                              当 Apache Commons 不可用时,这是一个不错的选择。它还具有一些不错的输出控件,例如:

                              byte[] bytes = new byte[] { 0xa, 0xb, 0xc, 0xd, 0xe, 0xf };
                              BaseEncoding.base16().lowerCase().withSeparator( ":", 2 ).encode( bytes );
                              // "0a:0b:0c:0d:0e:0f"
                              

                              【讨论】:

                                【解决方案26】:

                                我很惊讶没有人提出以下解决方案:

                                StringWriter sw = new StringWriter();
                                com.sun.corba.se.impl.orbutil.HexOutputStream hex = new com.sun.corba.se.impl.orbutil.HexOutputStream(sw);
                                hex.write(byteArray);
                                System.out.println(sw.toString());
                                

                                【讨论】:

                                  【解决方案27】:

                                  我会使用这样的东西来固定长度,比如哈希:

                                  md5sum = String.format("%032x", new BigInteger(1, md.digest()));
                                  

                                  掩码中的0 进行填充...

                                  【讨论】:

                                  • 仅使用标准 Java 的单行解决方案!
                                  【解决方案28】:

                                  或者你可以这样做:

                                  byte[] digest = algorithm.digest();
                                  StringBuilder byteContet = new StringBuilder();
                                  for(byte b: digest){
                                   byteContent = String.format("%02x",b);
                                   byteContent.append(byteContent);
                                  }
                                  

                                  它简短,简单,基本上只是格式更改。

                                  【讨论】:

                                  • 差不多了..除了字节值是有符号的(-128 -> 127),所以你需要进行以下更改:byteContent = String.format("%02x",b&amp;0xff);
                                  猜你喜欢
                                  • 1970-01-01
                                  • 2018-05-09
                                  • 2010-09-24
                                  • 2011-02-27
                                  • 2013-01-14
                                  • 2020-08-02
                                  • 2014-12-30
                                  相关资源
                                  最近更新 更多