如何检查一个字符串是否包含小写字母、大写字母、特殊字符和数字?

21

我已经谷歌了很多,但我没有找到答案:

如何使用正则表达式检查一个字符串是否至少包含以下四个中的每一个:

  • 大写字母
  • 小写字母
  • 数字
  • 特殊字符: ~`!@#$%^&*()-_=+\|[{]};:'",<.>/?

所以我需要至少一个大写字母至少一个小写字母至少一个数字至少一个特殊字符。

我相信答案非常简单,但我找不到它。任何帮助都将不胜感激。


在谷歌上搜索“用于强密码验证的正则表达式”。 - theglauber
6个回答

44

正则表达式不太适合测试需要同时满足多个条件的情况。

因此,最简单的答案就是不要尝试同时测试所有条件,而是依次测试这四个类。虽然你的代码可能会稍微慢一点,但它会更易于阅读和维护,例如:

public boolean isLegalPassword(String pass) {

     if (!pass.matches(".*[A-Z].*")) return false;

     if (!pass.matches(".*[a-z].*")) return false;

     if (!pass.matches(".*\\d.*")) return false;

     if (!pass.matches(".*[~!.......].*")) return false;

     return true;
}

编辑修正引号-最近一直在写JS编程...


1
@AlanMoore 我误操作给了你一个+1... .matches() 方法在正则表达式方面的命名确实非常不准确,它期望匹配整个输入,然而解决方法并_不是_在两侧使用 .*。真正的解决方法是使用 .find() 方法,它可以执行真正的正则表达式匹配。 - fge
7
很遗憾,String类没有find()方法。我认为告诉人们使用Pattern.compile("[A-Z]").matcher(pass).find())并没有意义,因为pass.matches(".*[A-Z].*")就可以完成任务。需要注意的唯一一件事是如果字符串中有换行符(\ n\ r等),但我认为这在这里不是问题。 - Alan Moore
2
这也将允许您向用户返回特定于条件的错误消息,如果您选择这样做的话。 - Trevortni

16

我同意@Alnitak的答案最易于阅读,但它存在一个问题,即每次运行时都必须评估正则表达式。既然正则表达式是固定的,那么编译它们然后进行比较就是有意义的。 例如:

    private static final Pattern [] passwordRegexes = new Pattern[4];
    {
        passwordRegexes[0] = Pattern.compile(".*[A-Z].*");
        passwordRegexes[1] = Pattern.compile(".*[a-z].*");
        passwordRegexes[2] = Pattern.compile(".*\\d.*");
        passwordRegexes[3] = Pattern.compile(".*[~!].*");
    }
    public boolean isLegalPassword(String pass) {

        for(int i = 0; i < passwordRegexes.length; i++){
            if(!passwordRegexes[i].matcher(pass).matches())
                return false;
        }
        return true;
    }

当对一个长度为10的密码运行100,000次时,以上代码的速度是原来的两倍。虽然我猜现在你可能会说这段代码更难以阅读了!不过没关系!


1
这完全取决于你的需求,不是吗。硬件限制总是会起作用的,任何没有在设计中考虑到它们的人最终都会付出代价,无论是像一个单一的 SQL 查询运行三天导致应用程序崩溃那么明显,还是像必须比原计划提前三个月购买更大的分数协作机房那么微妙。 - MetaEd

16

这个单一的正则表达式可以在java中完成你想要的事情,尽管我个人会使用像Mark Rhodes提供的解决方案那样的东西。随着规则变得更加复杂,这将很快变得荒谬(如果还没有的话…)。

String regex = "^(?=.*?\\p{Lu})(?=.*?[\\p{L}&&[^\\p{Lu}]])(?=.*?\\d)" + 
               "(?=.*?[`~!@#$%^&*()\\-_=+\\\\\\|\\[{\\]};:'\",<.>/?]).*$"
  1. ^ 表示匹配字符串的开头,这不是必需的,但我发现这有助于提高可读性和理解性。而且,在可以使用它时,使用它通常会带来很大的性能改进,并且几乎从不影响效率。

  2. (?=X) 这被称为正向先行断言。基本上,我们在说“必须以这个东西X紧随其后,才能匹配字符串的开头(^),但是不要将光标移动到X的末尾,仍然停留在行首(这就是“向前查看”的部分)。”

  3. .*?\p{Lu} 匹配行首之后的字符,直到找到一个大写字母。如果没有找到大写字母,这个表达式将无法匹配成功。我们使用\p{Lu}而不是A-Z,因为我们不希望其他地区的人抱怨我们的软件是由无知的美国人编写的。

  4. 现在我们回到行首(因为我们使用了 lookahead),开始搜索.*?[\p{L}&&[^\p{Lu}]] 的缩写,表示“所有字母,减去大写字母”(因此与小写字母匹配)。

  5. .*?\d + .*?[`~!@#$%^&*()\-_=+\\\|\[{\]};:'\",<.>/?] 用于匹配数字和一系列特殊字符的表达式。

  6. .*$ 匹配行尾之前的所有内容。我们这样做只是因为java中的“matches”方法语义会检查整个字符串是否与正则表达式完全匹配。您可以省略此部分并使用Matcher#find()方法,得到相同的结果。

  7. The Owl是任何技术主题上撰写的最好的书之一。它很短而且阅读速度很快。我强烈推荐。


+1 谢谢,Affe,你能给我解释一下吗?我必须承认我不是一个正则表达式专家,我只是想用正则表达式来解决我的问题,而不是逐个字符地检查我的字符串。 - Lajos Arpad
谢谢你,Affe。我想再次给你点赞,但我不能。好的,我可以给你的评论点赞。 - Lajos Arpad

7
由于这些字符没有特定的顺序,您需要为每个所需的字符类别添加前瞻断言:
(?=.*[A-Z])(?=.*[a-z])(?=.*[0-9])(?=.*[~!@#$%\^&*()\-_=+\|\[{\]};:'",<.>/?])

(注意:由于反斜杠、脱字符、连字符和方括号在范围内可能是特殊字符,所以如果它们出现在范围内,应该进行反斜杠转义,如第四个前瞻断言所示。)
使用空格和注释可以使这个结构更易读,如果你的正则表达式变量支持x修饰符的话。在java.util.regex中,你可以这样做:
(?x)         # extended syntax
(?=.*[A-Z])  # look ahead for at least one upper case
(?=.*[a-z])  # look ahead for at least one lower case
(?=.*[0-9])  # look ahead for at least one numeral
(?=.*[~!@#$%\^&*()\-_=+\|\[{\]};:'",<.>/?])
             # look ahead for at least one of the listed symbols

3
这证明了我的观点-我使用正则表达式已经不知道多少年了,从未使用过前瞻断言,并且如果用了我会很难理解。而四个简单正则表达式的版本可以被任何人理解。 - Alnitak
1
(+1) 很好的答案。只需确保使用全局匹配(这是最常见的方式)。 - Brigand
@Alnitak 我现在已经演示了扩展语法的使用,这使得表达式易于阅读,并且被 OP 的语言支持。我倾向于在示例中避免使用它,只是为了使我的表达式更普遍适用,即使在没有扩展语法的正则表达式实现中也是如此。 - MetaEd
蒂姆是正确的。另外,你是如何应用正则表达式的?如果你使用matches()方法,你需要在正则表达式的末尾加上.*或其他内容,以使其消耗整个字符串。 - Alan Moore
1
原始问题没有提供上下文。例如,它没有说明正则表达式将如何“原样”传递给matches()。 OP很可能想要使用解决方案与matcher()一起使用,或作为更大正则表达式的一部分。因此,我只是提供执行必要断言的片段。如果所需答案更具体,我可以修改代码以适应特定需求。 - MetaEd
显示剩余2条评论

-1

您正在寻找字符类

  • 大写字母:[A-Z]
  • 小写字母:[a-z]
  • 数字:[0-9] 或 \d
  • 特殊字符:[^A-Za-z0-9](即不是其他任何字符,其中^表示否定该类)

如果你想测试'this'或者'that',你可以结合这些范围。例如,大写或小写字母可以用[A-Za-z]表示。


1
OP 不是在寻找 '这个' 或者 '那个',而是要 '这个' '那个'。我无法仅通过字符类来实现这一点。 - MetaEd
+1@MetaEd 是的,MetaEd,我正在寻找至少一个大写字母、至少一个小写字母、至少一个数字和至少一个特殊字符。 - Lajos Arpad
这并不是一个完整的回答,但至少这个链接是相关的,这就是为什么我没有删除它的原因。 - Brigand

-1

\w:用于匹配字母数字(字母可以是大写或小写)
\W:用于匹配特殊字符

我认为这个正则表达式对你会有帮助:

[\w|\W]+

这里有一个很好的正则表达式模拟器, 你可以用它来构建自己的正则表达式。


2
\W匹配特殊字符”最多是误导性的。\W\w的补集,因此[\w\W]匹配任何单词字符或任何非单词字符,即任何字符。(在字符类中不需要指定“OR”,因此|匹配文字|——这已经被\W匹配了。)无论如何,您忽略了问题的要点,即确保每种四种字符中的每一种都存在。 - Alan Moore
+1@Alan Moore 是的,MetaEd,我正在寻找至少一个大写字母、至少一个小写字母、至少一个数字和至少一个特殊字符。我将编辑我的帖子,将“所有这些中的一个”更改为“每个中的一个”,以避免混淆。 - Lajos Arpad

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接