【问题标题】:Verify if String is hexadecimal验证字符串是否为十六进制
【发布时间】:2012-07-10 14:12:42
【问题描述】:

我有一个类似“09a”的字符串,我需要一种方法来确认文本是否为十六进制。我发布的代码做了类似的事情,它验证一个字符串是一个十进制数。我也想做同样的事情,但要使用十六进制。

    private static boolean isNumeric(String cadena) {
    try {
        Long.parseLong(cadena);
        return true;
    } catch (NumberFormatException nfe) {
        JOptionPane.showMessageDialog(null,"Uno de los números, excede su capacidad.");
        return false;
    }
}

【问题讨论】:

    标签: java string hex


    【解决方案1】:

    试试这个。

    static boolean isHexadecimal(String s) {
        return s.chars()
            .skip(s.startsWith("-") ? 1 : 0)
            .allMatch(c -> "0123456789ABCDEFabcdef".indexOf(c) >= 0);
    }
    
    public static void main(String[] args) {
        System.out.println(isHexadecimal("-0e34a29Fb"));
        System.out.println(isHexadecimal("-ff-"));
        System.out.println(isHexadecimal("ef-"));
        System.out.println(isHexadecimal("efg"));
    }
    

    输出:

    true
    false
    false
    false
    

    如果你不允许这个标志,你可以省略.skip(s.startsWith("-") ? 1 : 0)

    【讨论】:

      【解决方案2】:

      这里有一些用于不同选项和执行时间结果的代码(JDK 11):

      execution time isHex1: 3670 ms
      execution time isHex2: 3294 ms
      execution time isHex3: 3946 ms
      execution time regex: 31288 ms
      

      测试代码:

      public class HexPerformanceTest {
          @Test
          public void testPerformance() {
              int count = 100000000;
              char[] chars = {
                  '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f',
                  '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
              };
              String regexString = new String(chars);
      
              Predicate<String> isHex1Test = testString -> {
                  boolean isHex = true;
                  for (char c: testString.toCharArray()) {
                      if (!isHex1(c)) {
                          isHex = false;
                          break;
                      }
                  }
                  return isHex;
              };
      
              Predicate<String> isHex2Test = testString -> {
                  boolean isHex = true;
                  for (char c: testString.toCharArray()) {
                      if (!isHex2(c)) {
                          isHex = false;
                          break;
                      }
                  }
                  return isHex;
              };
      
              Predicate<String> isHex3Test = testString -> {
                  boolean isHex = true;
                  for (char c: testString.toCharArray()) {
                      if (!isHex3(c)) {
                          isHex = false;
                          break;
                      }
                  }
                  return isHex;
              };
      
              Pattern pattern = Pattern.compile("^[0-9a-fA-F]+$");
              Predicate<String> regexTest = testString -> {
                  Matcher matcher = pattern.matcher(regexString);
                  return matcher.matches();
              };
      
              System.out.println("execution time isHex1: " + milliseconds(regexString, isHex1Test, count) + " ms");
              System.out.println("execution time isHex2: " + milliseconds(regexString, isHex2Test, count) + " ms");
              System.out.println("execution time isHex3: " + milliseconds(regexString, isHex3Test, count) + " ms");
              System.out.println("execution time regex: " + milliseconds(regexString, regexTest, count) + " ms");
          }
      
          private long milliseconds(String testString, Predicate<String> hexTest, int count) {
              long start = System.currentTimeMillis();
              for (int i = 0; i < count; i++) {
                  hexTest.test(testString);
              }
              return System.currentTimeMillis() - start;
          }
      
          private boolean isHex1(char c) {
              return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
          }
      
          private boolean isHex2(char c) {
              switch (c) {
                  case '0':
                  case '1':
                  case '2':
                  case '3':
                  case '4':
                  case '5':
                  case '6':
                  case '7':
                  case '8':
                  case '9':
                  case 'a':
                  case 'b':
                  case 'c':
                  case 'd':
                  case 'e':
                  case 'f':
                  case 'A':
                  case 'B':
                  case 'C':
                  case 'D':
                  case 'E':
                  case 'F':
                      return true;
                  default:
                      return false;
              }
          }
      
          private boolean isHex3(char c) {
              return (Character.digit(c, 16) != -1);
          }
      }
      

      【讨论】:

        【解决方案3】:

        对异常的可怕滥用。永远不要这样做! (不是我,是 Josh Bloch 的 Effective Java)。无论如何,我建议

        private static final Pattern HEXADECIMAL_PATTERN = Pattern.compile("\\p{XDigit}+");
        
        private boolean isHexadecimal(String input) {
            final Matcher matcher = HEXADECIMAL_PATTERN.matcher(input);
            return matcher.matches();
        }
        

        【讨论】:

        • 你的错误引用 Effective Java,应该避免异常来控制逻辑流。如果不正确的十六进制字符串确实是一个异常事件,那么没有理由不使用parseLong 并捕获异常。 Josh 的书给出了一个示例,其中总是会抛出异常来控制循环,但这并不是那样的。此外,Josh 的担忧也与效率和可读性有关,但您建议使用 REGEX,它速度较慢且可读性较差。
        • 现在(根据我的基准测试)使用正则表达式比使用 parseLong 慢一点,假设绝大多数字符串实际上是正确的。我实际上同意使用异常可能并不完美,并且有更好的解决方案,我对您的回答的主要反对意见是您提出的建议比使用异常更糟糕。
        • 这将在每次检查时编译正则表达式,通常最好使用模式。
        • +1 是这里唯一不遍历字符串字符的解决方案,从纯粹的美学角度来看,我通常认为这很糟糕(即使它更有效):)
        • @JurabekAzizkhujaev 我同意这一点,但最好完全禁止 Java 和 C++ ;)。我确信 Go 可能有非常好的库来以其他方式处理异常。在函数式编程中,我们使用验证 Monad,这很棒。
        【解决方案4】:

        您可以使用以下方法检查任何长度的文本。

        public static boolean isHexadecimal(String text) {
            Objects.requireNonNull(text);
            if(text.length() < 1)
                throw new IllegalArgumentException("Text cannot be empty.");
        
            char[] hexDigits = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
                    'a', 'b', 'c', 'd', 'e', 'f', 'A', 'B', 'C', 'D', 'E', 'F' };
        
            for (char symbol : text.toCharArray()) {
                boolean found = false;
                for (char hexDigit : hexDigits) {
                    if (symbol == hexDigit) {
                        found = true;
                        break;
                    }
                }
                if(!found)
                    return false;
            }
            return true;
        }
        

        【讨论】:

        • 如果你想检查输入是否为十六进制格式,你不应该在开头修剪它。此外,如果您只在第一次出现非十六进制字符时返回 false,您的循环可能会更快失败(可能有一些方法可以检查包含,所以您可以这样做:if (!hexDigits.Contains(symbol)) return false;)。
        • 这不是一个非常有效的方法来检查一个String是否是十六进制并且return语句中的三元运算符是多余的。
        【解决方案5】:

        在我自己的代码中使用它来检查字符串是否是 MAC 地址

        boolean isHex = mac_addr.matches("^[0-9a-fA-F]+$");
        

        我对这个线程中提供的其他答案的不满是,如果字符串的长度很大,它也会引发异常。因此,如果您正在测试 MAC 地址是否由有效的十六进制组成,则不是很有用。

        不要害怕使用正则表达式!

        【讨论】:

        • 这将在每次检查时编译正则表达式,通常最好使用模式。
        【解决方案6】:

        无库方法

        public static boolean isHexadecimal(String value)
        {
            if (value.startsWith("-"))
            {
                value = value.substring(1);
            }
        
            value = value.toLowerCase();
        
            if (value.length() <= 2 || !value.startsWith("0x"))
            {
                return false;
            }
        
            for (int i = 2; i < value.length(); i++)
            {
                char c = value.charAt(i);
        
                if (!(c >= '0' && c <= '9' || c >= 'a' && c <= 'f'))
                {
                    return false;
                }
            }
        
            return true;
        }
        

        【讨论】:

          【解决方案7】:

          有一个重载的Long.parseLong 接受第二个参数,指定基数:

          Long.parseLong(cadena,16);
          

          作为替代方案,您可以遍历字符串中的字符并在它们上调用Character.digit(c,16)(如果它们中的任何一个返回-1,则它不是有效的十六进制数字)。如果字符串太大而无法放入 long 中,这将特别有用(正如 cmets 中所指出的那样,如果使用第一种方法会导致异常)。示例:

          private static boolean isNumeric(String cadena) {
              if ( cadena.length() == 0 || 
                   (cadena.charAt(0) != '-' && Character.digit(cadena.charAt(0), 16) == -1))
                  return false;
              if ( cadena.length() == 1 && cadena.charAt(0) == '-' )
                  return false;
          
              for ( int i = 1 ; i < cadena.length() ; i++ )
                  if ( Character.digit(cadena.charAt(i), 16) == -1 )
                      return false;
              return true;
          }
          

          顺便说一句,我建议将“测试有效数字”和“向用户显示消息”的关注点分开,这就是为什么我在上面的示例中简单地返回 false 而不是先通知用户。

          最后,您可以简单地使用正则表达式:

          cadena.matches("-?[0-9a-fA-F]+");
          

          【讨论】:

          • 作为替代方案,您可以遍历字符串中的字符并在它们上调用 Character.forDigit(c,16)(如果它们中的任何一个返回 null - '\0' - 它不是有效的十六进制数字)。或者使用正则表达式(虽然它会更复杂并且性能可能更差)。
          • 您指定的Character.forDigit(..) 方法无效。 Character.forDigit(..) 接受 int 并将其转换为 char,而不是相反。
          • 如果字符串太长,这也会抛出异常,这可能是不希望的(尤其是如果字符串用于创建 BigInteger,例如)。
          • 当我们这样做时,您的 isNumeric 方法会为数字前带有“-”的字符串返回 false,这可能是有效的负数。在迭代之前,我会检查第一个字符是否为“-”,如果是,则迭代应该从第二个索引开始。如果 '-' 是字符串中的唯一字符,它也应该返回 false。
          • 备注:Long.parseLong("f7e511c46d1b8a03", 16) 不起作用,会触发 NumberFormatException (invalid long)。因此,对于较大的十六进制,迭代或正则表达式可能是更好的选择。
          【解决方案8】:

          Long.parseLong 有第二种形式,它接受一个基数作为它的第二个参数。

          private static boolean isHexNumber (String cadena) {
            try {
              Long.parseLong(cadena, 16);
              return true;
            }
            catch (NumberFormatException ex) {
              // Error handling code...
              return false;
            }
          }
          

          【讨论】:

          • 如果我调用 isHexNumber("1234567890abcdef1234567890abcdef") 会发生什么?即使它是一个十六进制数字,也会得到一些其他异常,对于“long”来说太长了
          猜你喜欢
          • 2011-02-08
          • 2023-01-18
          • 2012-07-20
          • 1970-01-01
          • 1970-01-01
          • 2018-01-31
          • 2019-07-27
          • 1970-01-01
          • 2018-01-22
          相关资源
          最近更新 更多