Java 如何在不使用正则表达式的情况下检查字符串格式?

Java 如何在不使用正则表达式的情况下检查字符串格式?,java,Java,我正在做一个项目,我需要检查字符串的格式是否正确ABC1234表示3个字母后跟4个数字。我被告知不要用正则表达式来解决这个问题 我提出了以下代码,但它很笨重,所以我正在寻找更干净、更高效的东西 String sample = ABC1234 char[] chars = sample.toCharArray(); if(Character.isLetter(chars[0]) && Character.isLetter(chars[1]) && Cha

我正在做一个项目,我需要检查字符串的格式是否正确ABC1234表示3个字母后跟4个数字。我被告知不要用正则表达式来解决这个问题

我提出了以下代码,但它很笨重,所以我正在寻找更干净、更高效的东西

String sample = ABC1234

char[] chars = sample.toCharArray();

if(Character.isLetter(chars[0]) && Character.isLetter(chars[1]) && 
   Character.isLetter(chars[2]) && Character.isDigit(chars[3]) && 
   Character.isDigit(chars[4]) && Character.isDigit(chars[5]) && 
   Character.isDigit(chars[6])){

    list.add(sample);
}

// OUTPUT: ABC1234 gets added to "list". When it prints, it appears as ABC1234.
所有输出都如预期的那样,但我知道这可以更有效地完成,或者总体上做得更好

我只是检查前3个字符,确认它们都是字母,最后4个字符应该是数字


有什么建议吗?提前感谢。

我将编写两个实用方法;一个用于检查给定的
字符串
是否都是字母,另一个用于检查给定的
字符串
是否都是数字。然后使用
String.substring(int,int)
调用这两个方法来比较相关的子字符串。像

但是,在实际代码中,正则表达式更好-

Pattern p = Pattern.compile("\\D{3}\\d{4}");
if (p.matcher(s).matches()) {
    // ...
}

您可以使用
Integer.parseInt(sample.substring(3,7))一起检查最后4个
不过我想不出一个更快的字母转换方法。
Integer.parseInt
将抛出一个
NumberFormatException
,如果它不是一个数字,那么在
块中尝试一下
首先,你唯一能添加的是
长度检查

if (chars.length == 7 && Character.isLetter(chars[0]) &&
        Character.isLetter(chars[1]) && Character.isLetter(chars[2]) &&
        Character.isDigit(chars[3]) && Character.isDigit(chars[4]) &&
        Character.isDigit(chars[5]) && Character.isDigit(chars[6])) {
    //..
}
使用一些循环不会更有效,因为
&&
已经短路,并且会在发现您不需要的
错误
布尔值时停止

char[] chars = sample.toCharArray();
相反,你可以这样做

if(Character.isLetter(sample.charAt(0))
你也可以更花哨一些,做一些类似的事情:

void myFonc(string sample) {
 for (int i =0; i < 3; ++i)
        if (!Character.isLetter(sample.charAt(i)))
            return;

 for (int i =3; i < 7; ++i)
        if (!Character.isDigit(sample.charAt(i)))
            return;
list.add(sample);

}
void myFonc(字符串示例){
对于(int i=0;i<3;++i)
if(!Character.isleter(sample.charAt(i)))
返回;
对于(int i=3;i<7;++i)
if(!Character.isDigit(sample.charAt(i)))
返回;
列表。添加(样本);
}

鉴于您不能使用正则表达式,您当前的方法很好,但这里有另一种方法:

 boolean isValid = sample.length() == 7 &&
                sample.substring(0, 3).codePoints()
                        .allMatch(Character::isLetter)
                && sample.substring(3, 7).codePoints()
                .allMatch(Character::isDigit);
这是另一种方法

String sample = "ABC1234";
if (sample.substring(0, 3).chars().allMatch(Character::isLetter)
      && sample.substring(3).chars().allMatch(Character::isDigit)) {
  list.add(sample);
}

由于问题中包含“所有输出都如预期的那样,但我知道这可以更有效地完成,或者总体上做得更好。”(由于我喜欢性能),我编写了一些基准测试,比较每个答案,以得出关于效率的结论(查看吞吐量)

整个基准代码可以在问题的底部找到,如果您注意到任何错误,我很乐意纠正它(即使它不是完美的,每个答案都有很好的性能指标)

测试在安装了OpenJDK8的DigitalOcean droplet、2GB ram、2个vCore(
Intel(R)Xeon(R)CPU E5-2650 v4@2.20GHz
)上运行,JMH版本为1.21

每个答案用3个字符串进行测试,
“ABC1234”
以反映问题中的示例,
“ABC123D”
应该失败,以及
“ABC123”
太短(不确定是否与OP相关).测试配置为
5
forks、
5
1秒的预热迭代、
20
1秒的测量迭代

结果 图表 有两个不同的图表,因为
ABC123
图表中的吞吐量大得多(因为某些方法在比较字符串长度后返回false),如果将其添加到吞吐量较小的其余图表中,则无法读取

图表中的数字表示每秒的吞吐量(执行)。

一些注意事项和改进 mrB

由于这不是一个完整的答案(仅用于检查int部分),我使用了@elliotFrisch的字符验证方法。当字符串为
ABC1234
时,它当然很快,但是当尝试
ABC123D
并捕获
NumberFormatException
时,您可以看到性能很差

elliotFrisch

在查看了性能不如其他一些性能快的原因后,虽然可读性很强,但我得出结论,这是因为调用了
s.tocharray()
一次用于验证字符,一次用于验证数字

我对此做了改进,因此它只被调用一次,这可以在
elliotfrischooptimized
下的结果中看到

azro

很好的解决方案,但是由于调用
char[]c=s.tocharray()
然后验证
c.length
而不是直接验证
s.length()
,因此
ABC123
的性能低于其他方法。执行此检查的改进可以在结果中看到
mark

太长了,读不下去了 原始代码已经很快了,执行长度检查可以加快速度,如
azro
的回答所示。要使此长度检查更快(防止调用
s.tocharray()
),请使用
标记

如果你想要一个更具可读性/通用性且可重复使用的解决方案,我会选择
elliotfrischooptimized
方法,它(几乎)同样快

如果您对性能不太在意(从结果中可以看出,它仍将每秒检查近700万个字符串),那么使用@elliotFrisch提供的正则表达式就可以完成这项工作,它可读性强,易于维护

代码
@Fork(5)
@预热(迭代次数=5次,时间=1次)
@测量(迭代次数=20次,时间=1次)
@状态(Scope.Thread)
公共类MyBenchmark{
@参数({“ABC1234”、“ABC123D”、“AB123”})
串样;
模式p;
int-goodLength;
@设置
公共作废设置(){
this.p=Pattern.compile(“\\D{3}\\D{4}”);
这个.goodLength=7;
}
@基准
公共布尔基线Carlosdelatorre(){
char[]chars=this.sample.toCharArray();
if(Character.isLetter(chars[0])&Character.isLetter(chars[1])&&
Character.islitter(chars[2])&&Character.isDigit(chars[3])&&
Character.isDigit(chars[4])&Character.isDigit(chars[5]))&&
String sample = "ABC1234";
if (sample.substring(0, 3).chars().allMatch(Character::isLetter)
      && sample.substring(3).chars().allMatch(Character::isDigit)) {
  list.add(sample);
}
Benchmark                            (sample)   Mode  Cnt          Score         Error  Units
MyBenchmark.aomine                    ABC1234  thrpt  100    5102477.405 ±   92474.543  ops/s
MyBenchmark.aomine                    ABC123D  thrpt  100    5325954.315 ±  118367.303  ops/s
MyBenchmark.aomine                      AB123  thrpt  100  228544750.370 ± 2972826.551  ops/s
MyBenchmark.azro                      ABC1234  thrpt  100   38550638.399 ±  582816.997  ops/s
MyBenchmark.azro                      ABC123D  thrpt  100   38159991.786 ±  791457.371  ops/s
MyBenchmark.azro                        AB123  thrpt  100   76372552.584 ± 1131365.381  ops/s
MyBenchmark.baselineCarlosDeLaTorre   ABC1234  thrpt  100   37584463.448 ±  444739.798  ops/s
MyBenchmark.baselineCarlosDeLaTorre   ABC123D  thrpt  100   38461464.626 ±  461497.068  ops/s
MyBenchmark.baselineCarlosDeLaTorre     AB123  thrpt  100   52743609.713 ±  590609.005  ops/s
MyBenchmark.elliotFrisch              ABC1234  thrpt  100   16531274.955 ±  313705.782  ops/s
MyBenchmark.elliotFrisch              ABC123D  thrpt  100   16861377.659 ±  361382.816  ops/s
MyBenchmark.elliotFrisch                AB123  thrpt  100  227980231.801 ± 3071776.693  ops/s
MyBenchmark.elliotFrischOptimized     ABC1234  thrpt  100   37031168.714 ±  749067.222  ops/s
MyBenchmark.elliotFrischOptimized     ABC123D  thrpt  100   33383546.778 ±  799217.656  ops/s
MyBenchmark.elliotFrischOptimized       AB123  thrpt  100  214954411.915 ± 5283511.503  ops/s
MyBenchmark.elliotFrischRegex         ABC1234  thrpt  100    6862779.467 ±  122048.790  ops/s
MyBenchmark.elliotFrischRegex         ABC123D  thrpt  100    6830229.583 ±  119561.120  ops/s
MyBenchmark.elliotFrischRegex           AB123  thrpt  100   10797021.026 ±  558964.833  ops/s
MyBenchmark.mark                      ABC1234  thrpt  100   38451993.441 ±  478379.375  ops/s
MyBenchmark.mark                      ABC123D  thrpt  100   37667656.659 ±  680548.809  ops/s
MyBenchmark.mark                        AB123  thrpt  100  228656962.146 ± 2858730.169  ops/s
MyBenchmark.mrB                       ABC1234  thrpt  100   15490382.831 ±  233777.324  ops/s
MyBenchmark.mrB                       ABC123D  thrpt  100     575122.575 ±   10201.967  ops/s
MyBenchmark.mrB                         AB123  thrpt  100  231175971.072 ± 2074819.634  ops/s
MyBenchmark.pradipforever             ABC1234  thrpt  100    5105663.672 ±  171843.786  ops/s
MyBenchmark.pradipforever             ABC123D  thrpt  100    5305419.983 ±   80514.769  ops/s
MyBenchmark.pradipforever               AB123  thrpt  100   12211850.301 ±  217850.395  ops/s
@Fork(5)
@Warmup(iterations = 5, time = 1)
@Measurement(iterations = 20, time = 1)
@State(Scope.Thread)
public class MyBenchmark {

    @Param({ "ABC1234", "ABC123D", "AB123" })
    String sample;

    Pattern p;

    int goodLength;

    @Setup
    public void setup() {
        this.p = Pattern.compile("\\D{3}\\d{4}");
        this.goodLength = 7;
    }

    @Benchmark
    public boolean baselineCarlosDeLaTorre() {
        char[] chars = this.sample.toCharArray();

        if (Character.isLetter(chars[0]) && Character.isLetter(chars[1]) &&
                Character.isLetter(chars[2]) && Character.isDigit(chars[3]) &&
                Character.isDigit(chars[4]) && Character.isDigit(chars[5]) &&
                Character.isDigit(chars[6])) {
            return true;
        }
        return false;
    }

    @Benchmark
    public boolean mark() {
        if (this.sample.length() != this.goodLength) {
            return false;
        }

        char[] chars = this.sample.toCharArray();

        return Character.isLetter(chars[0]) && Character.isLetter(chars[1]) &&
                Character.isLetter(chars[2]) && Character.isDigit(chars[3]) &&
                Character.isDigit(chars[4]) && Character.isDigit(chars[5]) &&
                Character.isDigit(chars[6]);
    }

    @Benchmark
    public boolean azro() {
        char[] chars = this.sample.toCharArray();

        if (chars.length == this.goodLength && Character.isLetter(chars[0]) &&
                Character.isLetter(chars[1]) && Character.isLetter(chars[2]) &&
                Character.isDigit(chars[3]) && Character.isDigit(chars[4]) &&
                Character.isDigit(chars[5]) && Character.isDigit(chars[6])) {
            return true;
        }
        return false;
    }

    public boolean elliotFrischAllLLettersOptimized(char[] chars, int from, int to) {
        for (int i = from; i < to; i++) {
            if (!Character.isLetter(chars[i])) {
                return false;
            }
        }
        return true;
    }

    public boolean elliotFrischAllDigitsOptimized(char[] chars, int from, int to) {
        for (int i = from; i < to; i++) {
            if (!Character.isDigit(chars[i])) {
                return false;
            }
        }
        return true;
    }

    @Benchmark
    public boolean elliotFrischOptimized() {
        if (this.sample.length() != this.goodLength) {
            return false;
        }

        char[] chars = this.sample.toCharArray();

        return elliotFrischAllLLettersOptimized(chars, 0, 3)
                && elliotFrischAllDigitsOptimized(chars, 3, 7);
    }

    public boolean elliotFrischAllLLetters(String s) {
        for (char ch : s.toCharArray()) {
            if (!Character.isLetter(ch)) {
                return false;
            }
        }
        return true;
    }

    public boolean elliotFrischAllDigits(String s) {
        for (char ch : s.toCharArray()) {
            if (!Character.isDigit(ch)) {
                return false;
            }
        }
        return true;
    }

    @Benchmark
    public boolean elliotFrisch() {
        return this.sample.length() == this.goodLength
                && elliotFrischAllLLetters(this.sample.substring(0, 3))
                && elliotFrischAllDigits(this.sample.substring(3));
    }

    @Benchmark
    public boolean elliotFrischRegex() {
        return this.p.matcher(this.sample).matches();
    }

    @Benchmark
    public boolean aomine() {
        return this.sample.length() == this.goodLength &&
                this.sample.substring(0, 3).codePoints()
                        .allMatch(Character::isLetter)
                && this.sample.substring(3, 7).codePoints()
                        .allMatch(Character::isDigit);
    }

    @Benchmark
    public boolean pradipforever() {
        if (this.sample.substring(0, 3).chars().allMatch(Character::isLetter)
                && this.sample.substring(3).chars().allMatch(Character::isDigit)) {
            return true;
        }
        return false;
    }

    public boolean mrBParseInt(String s) {
        try {
            Integer.parseInt(s);
            return true;
        } catch (NumberFormatException ex) {
            return false;
        }
    }

    @Benchmark
    public boolean mrB() {
        return this.sample.length() == this.goodLength
                && elliotFrischAllLLetters(this.sample.substring(0, 3))
                && mrBParseInt(this.sample.substring(3));
    }

}