1. 概述

在Java开发中,我们经常需要对字符串进行掩码处理,比如隐藏日志文件或用户界面中的敏感信息。本文将探讨几种简单高效的Java实现方案,重点保留字符串最后N个字符(本文以N=4为例)。

2. 问题引入

实际开发中,我们常需处理信用卡号、社保号或邮箱等敏感信息。常见需求是隐藏大部分字符,仅保留最后几位。以以下三个字符串为例:

static final String INPUT_1 = "a b c d 1234";
static final String INPUT_2 = "a b c d     ";
static final String INPUT_3 = "a b";

目标是将每个字符串除最后4个字符外的所有字符替换为星号(*),预期结果如下:

static final String EXPECTED_1 = "********1234";
static final String EXPECTED_2 = "********    ";
static final String EXPECTED_3 = "a b";

关键规则:

  • ✅ 当字符串长度≤4时,直接返回原字符串
  • ✅ 空白字符与普通字符同等处理
  • ✅ 掩码字符统一使用*

接下来我们将用不同方案实现该功能,并通过单元测试验证结果。

3. 使用字符数组方案

字符串本质是字符序列,可通过字符数组操作实现掩码:

String maskByCharArray(String input) {
    if (input.length() <= 4) {
        return input;
    }
    char[] chars = input.toCharArray();
    Arrays.fill(chars, 0, chars.length - 4, '*');
    return new String(chars);
}

实现解析:

  1. 先检查长度≤4的情况
  2. 将字符串转为字符数组
  3. 使用Arrays.fill()填充掩码字符:
    • 从索引0开始
    • 填充至length-4的位置
  4. 将处理后的数组转回字符串

测试验证:

assertEquals(EXPECTED_1, maskByCharArray(INPUT_1));
assertEquals(EXPECTED_2, maskByCharArray(INPUT_2));
assertEquals(EXPECTED_3, maskByCharArray(INPUT_3));

4. 双子字符串方案

将字符串拆分为两部分:待掩码部分和保留部分:

String maskBySubstring(String input) {
    if (input.length() <= 4) {
        return input;
    }
    String toMask = input.substring(0, input.length() - 4);
    String keepPlain = input.substring(input.length() - 4);
    return toMask.replaceAll(".", "*") + keepPlain;
}

关键步骤:

  1. 长度检查(同上)
  2. 提取两个子字符串:
    • toMask:前length-4个字符
    • keepPlain:最后4个字符
  3. 使用replaceAll()替换掩码部分:
    • ⚠️ .是正则表达式元字符,匹配任意字符
  4. 拼接处理后的两部分

测试结果:

assertEquals(EXPECTED_1, maskBySubstring(INPUT_1));
assertEquals(EXPECTED_2, maskBySubstring(INPUT_2));
assertEquals(EXPECTED_3, maskBySubstring(INPUT_3));

5. 正则表达式方案

利用正则表达式的前瞻断言(lookahead)特性,一步完成掩码:

String maskByRegex(String input) {
    if (input.length() <= 4) {
        return input;
    }
    return input.replaceAll(".(?=.{4})", "*");
}

正则解析:

  • .:匹配任意字符
  • (?=.{4}):正向零宽断言,确保当前位置后恰好有4个字符
  • 整体效果:替换所有"后面还有4个字符"的字符

测试验证:

assertEquals(EXPECTED_1, maskByRegex(INPUT_1));
assertEquals(EXPECTED_2, maskByRegex(INPUT_2));
assertEquals(EXPECTED_3, maskByRegex(INPUT_3));

6. 使用repeat()方法(Java 11+)

Java 11引入的repeat()方法可简化掩码字符生成:

String maskByRepeat(String input) {
    if (input.length() <= 4) {
        return input;
    }
    int maskLen = input.length() - 4;
    return "*".repeat(maskLen) + input.substring(maskLen);
}

实现要点:

  1. 计算掩码长度maskLen
  2. "*".repeat(maskLen)生成掩码字符串
  3. 拼接保留部分input.substring(maskLen)

测试结果:

assertEquals(EXPECTED_1, maskByRepeat(INPUT_1));
assertEquals(EXPECTED_2, maskByRepeat(INPUT_2));
assertEquals(EXPECTED_3, maskByRepeat(INPUT_3));

7. 总结

我们对比了四种字符串掩码方案:

方案 优点 适用场景
字符数组 直观高效 所有Java版本
双子字符串 逻辑清晰 需要分段处理时
正则表达式 代码简洁 复杂模式匹配
repeat()方法 最简洁 Java 11+环境

实际应用时:

  • ✅ 可灵活调整掩码字符(如#)和保留位数(N值)
  • ✅ 所有方案均需注意空字符串处理
  • ❌ 避免在超长字符串中使用正则方案(性能问题)

完整代码示例可参考:GitHub仓库