首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >如何在java中使用信用卡正则表达式提高掩蔽方法的性能

如何在java中使用信用卡正则表达式提高掩蔽方法的性能
EN

Stack Overflow用户
提问于 2022-03-25 10:27:26
回答 2查看 416关注 0票数 0

我有这样的功能,通过输入字符串中的正则表达式来识别信用卡,并在没有最后4位数字的情况下屏蔽它:

代码语言:javascript
复制
public CharSequence obfuscate(CharSequence data) {
    String[] result = data.toString().replaceAll("[^a-zA-Z0-9-_*]", " ").trim().replaceAll(" +", " ").split(" ");
    for(String str : result){
        String originalString = str;
        String cleanString = str.replaceAll("[-_]","");
        CardType cardType = CardType.detect(cleanString);
        if(!CardType.UNKNOWN.equals(cardType)){
            String maskedReplacement = maskWithoutLast4Digits(cleanString ,replacement);
            data = data.toString().replace(originalString , maskedReplacement);
        }
    }
    return data;
}

static String maskWithoutLast4Digits(String input , String replacement) {
    if(input.length() < 4){
        return input;
    }
    return input.replaceAll(".(?=.{4})", replacement);
}

//模式枚举

代码语言:javascript
复制
 public enum CardType {
UNKNOWN,
VISA("^4[0-9]{12}(?:[0-9]{3}){0,2}$"),
MASTERCARD("^(?:5[1-5]|2(?!2([01]|20)|7(2[1-9]|3))[2-7])\\d{14}$"),
AMERICAN_EXPRESS("^3[47][0-9]{13}$"),
DINERS_CLUB("^3(?:0[0-5]|[68][0-9])[0-9]{11}$"),
DISCOVER("^6(?:011|[45][0-9]{2})[0-9]{12}$");

private Pattern pattern;

CardType() {
    this.pattern = null;
}

CardType(String pattern) {
    this.pattern = Pattern.compile(pattern);
}

public static CardType detect(String cardNumber) {

    for (CardType cardType : CardType.values()) {
        if (null == cardType.pattern) continue;
        if (cardType.pattern.matcher(cardNumber).matches()) return cardType;
    }

    return UNKNOWN;
}


public Pattern getPattern() {
    return pattern;
}
}

input1:“有效美国运通卡: 371449635398431”。

output1:“有效美国运通卡:*8431”

input2:“无效信用卡: 1234222222222”//没有任何信用卡模式

output2:“无效信用卡: 1234222222222”

input3:“带有垃圾字符的有效美国运通卡:<3714-4963-5398-431>”

输出:“带有垃圾字符的有效美国运通卡:<***********8431>”

这不是最好的掩蔽方法,因为这个方法将被调用到巨型html中的每个标记和大型文本文件中的每一行--如何提高这个方法的性能。

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2022-03-28 21:06:40

这篇文章完全基于上述答复中的评论,特别是任择议定书中的这一评论:

代码语言:javascript
复制
And also the input string can be "my phone number 12345678 and credit card 1234567890"

如果您一心想要从特定的字符串中检索电话号码和信用卡号码,那么可以使用这个RegEx正则表达式:

代码语言:javascript
复制
String regex = String regex = "(\\+?\\d+.{0,1}\\d+.{0,1}\\d+.{0,1}\\d+)|"
                            + "(\\+{0,1}\\d+{0,3}\\s{0,1}\\-{0,1}\\({0,1}\\d+"       // Phone Numbers
                            + "\\){0,1}\\s{0,1}\\-{0,1}\\d+\\s{0,1}\\-{0,1}\\d+)";   // Credit Cards

要使用这个regex字符串,您需要通过Pattern/Matcher机制运行它,例如:

代码语言:javascript
复制
String strg = "Valid Phone #: <+1 (212) 555-3456> - "
            + "Valid American Express card 24 with garbage 33.6 characters: <3714-4963-5398-431>";

final java.util.List<String> numbers = new java.util.ArrayList<>();

final String regex = "(\\+?\\d+.{0,1}\\d+.{0,1}\\d+.{0,1}\\d+)|"       // Phone Numbers
                   + "(\\+{0,1}\\d+{0,3}\\s{0,1}\\-{0,1}\\({0,1}\\d+"  // Credit Cards
                   + "\\){0,1}\\s{0,1}\\-{0,1}\\d+\\s{0,1}\\-{0,1}\\d+)";

final java.util.regex.Pattern pattern = java.util.regex.Pattern.compile(regex); // the regex
final java.util.regex.Matcher matcher = pattern.matcher(strg); // your string
while (matcher.find()) { 
    numbers.add(matcher.group()); 
}
        
for (String str : numbers) {
    System.out.println(str);
}

有了上面提供的字符串,控制台窗口将显示:

代码语言:javascript
复制
+1 (212) 555-3456
3714-4963-5398-431

考虑一下这些原始电话号码和信用卡号子字符串。将这些字符串放入诸如、origPhoneNum、和等回复变量中。现在验证这些数字。您已经在前面的答案中提供了验证信用卡号码的工具。这里有一个验证电话号码的方法:

代码语言:javascript
复制
public static boolean isValidPhoneNumber(String phoneNumber) {
    return phoneNumber.matches("^(?!\\b(0)\\1+\\b)(\\+?\\d{1,3}[. -]?)?"
                             + "\\(?\\d{3}\\)?([. -]?)\\d{3}\\3\\d{4}$");
}

我已经测试了上面提供的regex字符串与来自、许多、不同国家、不同格式的电话号码的关系,并取得了成功。它还测试了不同的信用卡号码,以许多不同的格式,再次成功。当然,永远不会少一些可能会导致特定问题的格式,因为数据生成源的数字条目显然没有任何规则。

以我在这篇文章顶部所显示的评论为例:

代码语言:javascript
复制
And also the input string can be "my phone number 12345678 and credit card 1234567890"

没有办法区分哪个号码应该是电话号码,哪个应该是信用卡号码,除非与字符串中的文本像上面的字符串那样具体地声明为文本。明天或下周可能不会,因为这里似乎没有任何数据输入规则。

字符串表示12345678的电话号码,它是8位数字。该字符串还指示信用卡号码1234567890。在国际上,电话号码可以从9到多达13位数不等,视国家而定。在当地,数字范围的数目将根据国家的不同再次缩小。由于电话号码(国际上)有如此多的数字范围,所以不可能知道被认为是信用卡号码的数字实际上是信用卡号码,除非字符串在数字之前或之后告诉你。如果有的话,它将在下一个输入字符串中。?

为此,我把它留给你来决定如何处理这种情况,但不管它是什么,不要期待它的任何伟大的速度。就像我在之前的回答开始时写的那样:

代码语言:javascript
复制
Wouldn't it be nice if all validations were done before the card numbers
went into the database (or data files).

编辑:基于您在前面的答案:下面的最新评论

我做了一个小演示:

代码语言:javascript
复制
// Place this code into a method or event somewhere...
String inputString = "my phone number is +54 123 344-4567 and CC 2222 4053 4324 8877 bla bla bla";
System.out.println("Input:  " + inputString);
System.out.println();

final java.util.List<String> numbers = new java.util.ArrayList<>();
    
final String regex = "(\\+?\\d+.{0,1}\\d+.{0,1}\\d+.{0,1}\\d+)|"       // Phone Numbers
                   + "(\\+{0,1}\\d+{0,3}\\s{0,1}\\-{0,1}\\({0,1}\\d+"  // Credit Cards
                   + "\\){0,1}\\s{0,1}\\-{0,1}\\d+\\s{0,1}\\-{0,1}\\d+)";

final java.util.regex.Pattern pattern = java.util.regex.Pattern.compile(regex);
final java.util.regex.Matcher matcher = pattern.matcher(inputString); 
while (matcher.find()) { 
    numbers.add(matcher.group()); 
}
    
String outputString = inputString;
    
for (String str : numbers) {
    //System.out.println(str);  // Uncomment for testing.
    // Is substring a valid Phone Number?
    int len = str.replaceAll("\\D","").length();  // Crushed number length
    if (isValidPhoneNumber(str)) {
        outputString = outputString.replace(str, maskAllExceptLast(str, 3, "x"));
    }
    else if (isValidCreditCardNumber(str)) {
        outputString = outputString.replace(str, 
        maskAllExceptLast(str.replaceAll("\\D",""), 4, "*"));
    }
}

System.out.println("Output: " + outputString);

支持方法.

代码语言:javascript
复制
public static String maskAllExceptLast (String inputString, int exceptLast_N, String... maskCharacter) {
    if(inputString.length() < exceptLast_N){
        return inputString;
    }
    String mask = "*";  // Default mask character.
    if (maskCharacter.length > 0) {
        mask = maskCharacter[0];
    }
    return inputString.replaceAll(".(?=.{" + exceptLast_N + "})", mask);
}

/**
 * Method to validate a supplied phone number. Currently validates phone
 * numbers supplied in the following fashion:
 * <pre>
 *
 *      Phone number 1234567890 validation result: true
 *      Phone number 123-456-7890 validation result: true
 *      Phone number 123-456-7890 x1234 validation result: true
 *      Phone number 123-456-7890 ext1234 validation result: true
 *      Phone number (123)-456-7890 validation result: true
 *      Phone number 123.456.7890 validation result: true
 *      Phone number 123 456 7890 validation result: true
 *      Phone number 01 123 456 7890 validation result: true
 *      Phone number 1 123-456-7890 validation result: true
 *      Phone number 1-123-456-7890 validation result: true</pre>
 *
 * @param phoneNumber (String) The phone number to check.<br>
 *
 * @return (boolean) True is returned if the supplied phone number is valid.
 *         False if it isn't.
 */
public static boolean isValidPhoneNumber(String phoneNumber) {
    boolean isValid = false;
    long len = phoneNumber.replaceAll("\\D","").length(); // Crush the phone Number into only digits
    // Check phone Number's length range. Must be from 8 to 12 digits long
    if (len < 8 || len > 12) {
        return false;
    }
    // Validate phone numbers of format "xxxxxxxx to xxxxxxxxxxxx"
    else if (phoneNumber.matches("\\d+")) {
        isValid = true;
    }
    //validating phone number with -, . or spaces
    else if (phoneNumber.matches("^(\\+\\d{1,3}( )?)?((\\(\\d{1,3}\\))|\\d{1,3})[- .]?\\d{3,4}[- .]?\\d{4}$")) {
        isValid = true;
    }
    /* Validating phone number with -, . or spaces and long distance prefix.
       This regex also ensures:
          - The actual number (withoug LD prefix) should be 10 digits only.
          - For North American, numbers with area code may be surrounded 
              with parentheses ().
          - The country code can be 1 to 3 digits long. Optionally may be 
            preceded by a + sign.
          - There may be dashes, spaces, dots or no spaces between country 
            code, area code and the rest of the number.
          - A valid phone number cannot be all zeros.                 */
    else if (phoneNumber.matches("^(?!\\b(0)\\1+\\b)(\\+?\\d{1,3}[. -]?)?"
                               + "\\(?\\d{3}\\)?([. -]?)\\d{3}\\3\\d{4}$")) {
        isValid = true;
    }
    //validating phone number with extension length from 3 to 5
    else if (phoneNumber.matches("\\d{3}-\\d{3}-\\d{4}\\s(x|(ext))\\d{3,5}")) {
        isValid = true;
    } 
    //validating phone number where area code is in braces ()
    else if (phoneNumber.matches("^(\\(\\d{1,3}\\)|\\d{1,3})[- .]?\\d{2,4}[- .]?\\d{4}$")) {
        isValid = true;
    } 
    //return false if nothing matches the input
    else {
        isValid = false;
    }
    return isValid;
}

/**
 * Returns true if card (ie: MasterCard, Visa, etc) number is valid using
 * the 'Luhn Algorithm'. First this method validates for a correct Card 
 * Network Number. The supported networks are:<pre>
 * 
 *    Number            Card Network
 *    ====================================
 *      2               Mastercard (BIN 2-Series) This is NEW!!
 *      30, 36, 38, 39  Diners-Club
 *      34, 37          American Express
 *      35              JBC
 *      4               Visa
 *      5               Mastercard
 *      6               Discovery</pre><br>
 * 
 * Next, the overall Credit Card number is checked with the 'Luhn Algorithm' 
 * for validity.<br>
 *
 * @param cardNumber (String)
 *
 * @return (Boolean) True if valid, false if not.
 */
public static boolean isValidCreditCardNumber(String cardNumber) {
    if (cardNumber == null || cardNumber.trim().isEmpty()) {
        return false;
    }
    // Strip card number of all non-digit characters.
    cardNumber = cardNumber.replaceAll("\\D", "");
    
    long len = cardNumber.length();
    if (len < 14 || len > 16) {   // Only going to 16 digits here 
        return false;
    }
        
    // Validate Card Network
    String[] cardNetworks = {"2", "30", "34", "35", "36", "37", "38", "39", "4", "5", "6"};
    String cardNetNum = cardNumber.substring(0, (cardNumber.startsWith("3") ? 2 : 1));
    boolean pass = false;
    for (String netNum : cardNetworks) {
        if (netNum.equals(cardNetNum)) {
            pass = true;
            break;
        }
    }
    if (!pass) {
        return false;  // Invalid Card Network
    }

    // Validate card number with the 'Luhn algorithm'.
    int nDigits = cardNumber.length();

    int nSum = 0;
    boolean isSecond = false;
    for (int i = nDigits - 1; i >= 0; i--) {
        int d = cardNumber.charAt(i) - '0';
        if (isSecond == true) {
            d = d * 2;
        }
        nSum += d / 10;
        nSum += d % 10;
        isSecond = !isSecond;
    }
    return (nSum % 10 == 0);
}

上面的代码绝对不会很快!

调整正则表达式或代码,以满足您的特定需求。

票数 1
EN

Stack Overflow用户

发布于 2022-03-25 20:53:02

如果所有的验证都是在之前完成的,那么卡片号就进入了数据库(或数据文件),这不是很好吗?

如果您想要的是速度,那么我不相信在代码的任何部分使用RegEx一定是最好的方法,因为处理正则表达式会消耗大量的时间。例如,以在maskWithoutLast4Digits()方法中执行字符串掩蔽的行为例:

代码语言:javascript
复制
static String maskWithoutLast4Digits(String input, String replacement) {
    if(input.length() <= 4){
        return input;    // There is nothing to mask!
    }
    return input.replaceAll(".(?=.{4})", replacement);
}

并将其替换为以下代码:

代码语言:javascript
复制
static String maskWithoutLast4Digits(String input, String replacement) {
    if (input.length() <= 4) {
        return input; // There is nothing to mask!
    }
    char[] chars = input.toCharArray();
    Arrays.fill(chars, 0, chars.length - 4, replacement);
    return new String(chars);
}

您可能会发现,的总体代码将对单个信用卡号字符串执行该任务,其速度几乎是使用正则表达式方法的两倍。这是一个很大的不同。事实上,如果您通过分析器运行代码,您很可能会发现,包含正则表达式的方法对于处理的每个字符串可能会逐渐变慢,而第二个方法将使事情保持更恒定的速度。

不同的信用卡基本上是以一个特定的数字数值开始的,除了几张卡,例如,如果信用卡号从3开始,那么它总是美国运通、餐厅俱乐部或Carte Blanche支付网络的一部分。如果卡以4开头,那么它就是Visa。从5开始的卡号是MasterCards的一部分,而以6开头的卡属于Discover。

代码语言:javascript
复制
  Card                   Starts With                   No. of Digits
  ==================================================================
  American Express       can be 34 or usually 37       15
  JBC                    35                            16
  Diners Club            usually 36 or can be 38       14
  VISA                   4                             16
  Mastercard             5                             16
  Discovery              6                             16

您不需要regex来确定信用卡号是否以这些值开头,应该注意的是,如果数字的话,某些信用卡不一定总是包含相同的数字。这可能取决于发卡者,我相信你已经知道了,但无论如何,作为Visa、Mastercard和Discover付款网络的一部分的信用卡有16位数,而属于美国运通支付网络的信用卡只有15位数。虽然信用卡最常见的是16位数,但可能只有13位数,多达19位数。我没有浏览过你的,但我确信它们已经涵盖了这一点(对吧?)。

要删除Regex的使用,可以使用switch/case机制,例如:

代码语言:javascript
复制
// Demo card number...
    String cardNumber = "371449635398431";
    
/* Remove all Characters other than digits. 
   Don't want them for validation.      */
cardNumber = cardNumber.replaceAll("\\D", ""); // Remove all Characters other than digits
String cardName;  // Used to store the card's name 
switch (cardNumber.substring(0, 1)) {
    case "3":
        String typeNum = cardNumber.substring(0, 2);
        switch(typeNum) {
            case "34": case "37":
               cardName = "American-Express";
               break;
            case "35":
               cardName = "JBC";
               break;        
            case "30": case "36": case "38": case "39":
                cardName = "Diners-Club";
                break;
            default: 
                cardName = "UNKNOWN";
        }
        break;
    case "4":
        cardName = "Visa";
        break;
    case "5":
        cardName= "Mastercard";
        break;
    case "6":
        cardName = "Discovery";
        break;
    default:
        cardName = "UNKNOWN";
}

如果您要在此代码上运行速度测试,而不是迭代一堆RegEx的代码,我相信您会发现considerable的速度有所提高,即使您还想检查每个case中处理的每个卡号的长度。

验证信用卡号码的最佳方法是使用Luhn公式(也称为Luhn Algorithm),它基本上遵循此方案:

  1. 首先将您正在验证的卡号中的每一个奇数的值加倍。如果任何给定的加倍运算的结果和大于9(例如,7x2= 14或9x2= 18),那么将该运算的数字相加(例如,14: 1+4=5或18: 1+8=9)。
  2. 现在将所有得到的数字加起来,包括偶数,没有乘以2。
  3. 如果收到的总数以0结尾,则根据Luhn算法,卡号是有效的;否则无效。

当然,如果使用方便,可以将整个过程放入一种方法中,例如:

代码语言:javascript
复制
/**
 * Returns true if card (ie: MasterCard, Visa, etc) number is valid using
 * the 'Luhn Algorithm'.
 *
 * @param cardNumber (String)
 *
 * @return (Boolean)
 */
public static boolean isValidCardNumber(String cardNumber) {
    if (cardNumber == null || cardNumber.trim().isEmpty()) {
        return false;
    }
    cardNumber = cardNumber.replaceAll("\\D", "");
    
    // Luhn algorithm
    int nDigits = cardNumber.length();

    int nSum = 0;
    boolean isSecond = false;
    for (int i = nDigits - 1; i >= 0; i--) {
        int d = cardNumber.charAt(i) - '0';
        if (isSecond == true) {
            d = d * 2;
        }
        // We add two digits to handle 
        // cases that make two digits  
        // after doubling 
        nSum += d / 10;
        nSum += d % 10;
        isSecond = !isSecond;
    }
    return (nSum % 10 == 0);
}

要将所有这些放在一起,您的代码看起来可能与以下内容类似:

代码语言:javascript
复制
public static String validateCreditCardNumber(String cardNumber) {
    // Remove all Characters other than digits
    cardNumber = cardNumber.replaceAll("\\D", ""); // Remove all Characters other than digits
    String cardName;  // Used to store the card's name 
    switch (cardNumber.substring(0, 1)) {
        case "3":
            String typeNum = cardNumber.substring(0, 2);
            switch(typeNum) {
                case "34": case "37":
                   cardName = "American-Express";
                   break;
                case "35":
                   cardName = "JBC";
                   break;        
                case "30": case "36": case "38": case "39":
                    cardName = "Diners-Club";
                    break;
                default: 
                    cardName = "UNKNOWN";
            }
            break;
        case "4":
            cardName = "Visa";
            break;
        case "5":
            cardName= "Mastercard";
            break;
        case "6":
            cardName = "Discovery";
            break;
        default:
            cardName = "UNKNOWN";
    }
    
    if (!cardName.equals("UNKNOWN") && isValidCardNumber(cardNumber)) {
        return ("The " + cardName + " card number (" + maskWithoutLast4Digits(cardNumber, '*') + ") is VALID!");
    }
    else {
        return ("The " + cardName + " card number (" +  maskWithoutLast4Digits(cardNumber, '*') + ") is NOT VALID!");
    }
}

public static String maskWithoutLast4Digits (String input, char replacement) {
    if (input.length() <= 4) {
        return input; // Nothing to mask
    }
    char[] buf = input.toCharArray();
    Arrays.fill(buf, 0, buf.length - 4, replacement);
    return new String(buf);
}

/**
 * Returns true if card (ie: MasterCard, Visa, etc) number is valid using
 * the 'Luhn Algorithm'.
 *
 * @param cardNumber (String)
 *
 * @return (Boolean)
 */
public static boolean isValidCardNumber(String cardNumber) {
    if (cardNumber == null || cardNumber.trim().isEmpty()) {
        return false;
    }
    cardNumber = cardNumber.replaceAll("\\D", "");
    
    // Luhn algorithm
    int nDigits = cardNumber.length();

    int nSum = 0;
    boolean isSecond = false;
    for (int i = nDigits - 1; i >= 0; i--) {
        int d = cardNumber.charAt(i) - '0';
        if (isSecond == true) {
            d = d * 2;
        }
        // We add two digits to handle 
        // cases that make two digits  
        // after doubling 
        nSum += d / 10;
        nSum += d % 10;
        isSecond = !isSecond;
    }
    return (nSum % 10 == 0);
}

并且基本上使用上面的内容:

代码语言:javascript
复制
// Demo card number...
String cardNumber = "371449635398431";
    
String isItValid = validateCreditCardNumber(cardNumber);
System.out.println(isItValid);

输出到控制台将是:

代码语言:javascript
复制
The American-Express card number (***********8431) is VALID!

我不太清楚您的输出是如何进行的,但是最好在显示它之前将其归档,因为您的速度总是受限于该过程。另外,将数据分解成可管理的块并使用多个executor-Service线程来处理数据将大大提高处理速度,就像使用新的JDK( Java8)之一和利用一些新的方法一样。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/71615423

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档