ApacheCommons——commons-text(模板替换与文本算法)

commons-text(模板替换与文本算法)

1、概述

commons-text(Apache Commons Text)是从 commons-lang3 独立出来的文本处理增强工具包。它补充了原生 Java 在复杂字符串替换、占位符插值、文本相似度度量(编辑距离)、字符转义/反转义以及随机文本生成等高级场景的功能支持。

分类常用类 (Class)核心功能与解决问题典型适用场景
文本替换与插值StringSubstitutor
StringTokenizer
提供灵活的模板占位符替换(支持动态环境变量、系统属性及自定义 StringLookup);高效处理复杂分隔符的字符串切分。• 动态配置文件解析
• 邮件/短信模板引擎渲染
• 带有递归变量替换的规则引擎
转义与反转义StringEscapeUtils提供 HTML、XML、Java、JavaScript、SQL、CSV 等多格式文本的字符转义与还原(Unescape),防范注入与解析报错。• 防止 Web 端 XSS 攻击与 HTML 标签过滤
• SQL 拼接/日志输出特殊字符防护
• 生成合规的 CSV 与 JSON 文本
文本相似度 (Distance)LevenshteinDistance
JaroWinklerDistance
CosineSimilarity
评估两个文本之间的相似度、编辑距离或余弦相似度,提供归一化评分机制。• 文本查重与模糊匹配
• 拼写纠错建议
• 英文/拼音人名匹配与去重
字符变换与匹配WordUtils
CharacterPredicates
实现单词首字母大写转换、按指定长度自动换行(Text Wrapping)以及通过谓词筛选特定字符集。• 终端或报表输出的文本格式化对齐
• 标题规范化处理(Title Case)
高级随机生成RandomStringGenerator
TextRandomProvider
支持基于字符范围(Code Points)与谓词约束(如仅数字、仅字母)构建高定制化的随机字符串生成器。• 自定义密码/验证码生成
• 接口测试数据伪造(Mock)
• 唯一 Token 与随机密钥生成
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-text</artifactId>
    <version>1.12.0</version>
</dependency>

2、文本替换与插值

commons-text(Apache Commons Text)提供了比 Java 原生 String.replace() 和 MessageFormat 更强大、更灵活的文本替换与插值(Template Interpolation)能力。

它支持自定义变量界定符(如 ${var}、#{var})、默认值解析(如 ${var:-default})、嵌套变量递归替换以及基于动态 Lookup 数据源的上下文插值。

核心类与架构一览

commons-text 的文本插值体系建立在 StringSubstitutor 与 StringLookup 的配合之上:

							  ┌──────────────────────────┐
                              │    StringSubstitutor(引擎:负责解析占位符并替换)
                              └─────────────┬────────────┘
                                            │ 依赖
                                            ▼
                              ┌──────────────────────────┐
                              │       StringLookup(接口:提供变量查找功能)
                              └─────────────┬────────────┘
                                            │ 实现
                ┌───────────────────────────┼───────────────────────────┐
                ▼                           ▼                           ▼
    ┌───────────────────────┐   ┌───────────────────────┐   ┌───────────────────────┐
    │      MapStringLookup  │   │ ExtInterpolatorStringLookup││   SystemProperty...   │
    │  (Map 查找键值对)   │   │  (解析 prefix:key 语法)│   │ (环境变量、系统属性等) │
    └───────────────────────┘   └───────────────────────┘   └───────────────────────┘
核心类作用典型应用场景
StringSubstitutor文本插值与变量替换(最核心)
支持 ${var} 占位符、默认值语法(${var:-default})及嵌套变量递归解析
配置文件模板动态解析、邮件/短信通知模版渲染、动态 SQL/Shell 脚本生成
StringLookupFactory预设变量查找器 (Lookup) 工厂
提供对环境变量(env)、系统属性(sys)、Base64 解码、Java 常量等的开箱即用支持
结合 StringSubstitutor 实现类似 ${sys:user.home}${env:DATABASE_URL} 的动态配置注入
StringTokenizer增强型文本分词与拆分
支持复杂的引名处理(Quoting)、忽略空格及匹配自定义分隔符(比 java.util.StringTokenizer 更稳定)
解析包含双引号的 CSV 单元格数据、处理带有转义符的复杂配置文本

2.1、核心API

常用构造方法与工厂方法

方法 / 构造器作用与描述典型适用场景
StringSubstitutor(Map<String, V> valueMap)最常用的构造器,绑定一个 Map 作为变量源(默认占位符为 ${key})。简单的 Map 键值对模板替换(如 JSON 模版、固定变量渲染)
StringSubstitutor(StringLookup variableResolver)绑定一个自定义或内置的 StringLookup 作为数据源。需要动态从 Redis、数据库、环境变量或加解密组件中提取变量的场景
StringSubstitutor(Map<String, V> valueMap, String prefix, String suffix)自定义占位符的前缀和后缀(例如 #{key}{{key}})。避免与现有 ${} 语法冲突的模板解析(如 Vue/Mustache 风格的 {{key}} 或 Spring 风格的 #{key}
createDefaultStringSubstitutor()(1.10.0+ 引入) 创建包含默认预定义 Lookup(环境变量、系统属性、日期等)的替换器。快速解析形如 ${sys:user.name}${env:PATH}${date:yyyy-MM-dd} 的丰富动态文本

核心配置 API

API 方法默认值作用与行为说明典型适用场景 / 示例
setEnableSubstitutionInVariables(boolean enable)false开启嵌套变量递归解析
允许在变量名内部再次嵌入变量。
动态环境路由,如解析 ${${env}_url}:先解析 ${env} 得到 prod,再解析 ${prod_url}
setEnableUndefinedVariableException(boolean fail)false未找到变量时抛出异常
设为 true 时,若占位符无对应值则抛出 IllegalArgumentException;设为 false 则原样保留 ${var} 文本。
严格校验配置完整性,防止遗漏关键参数;或用于允许分步/多轮渲染的模板引擎。

2.2、StringSubstitutor

StringSubstitutor 是 commons-text 中最常用的类,用于解析带有 ${var} 或自定义占位符的文本并替换为实际值。它支持嵌套变量、默认值以及动态查找器。

2.2.1、常用 API
  • StringSubstitutor(Map<String, V> valueMap):基于 Map 构建替换器。
  • StringSubstitutor(StringLookup variableResolver):基于自定义/系统 Lookup 构建替换器。
  • setVariablePrefix(String prefix) / setVariableSuffix(String suffix):自定义占位符的前后缀(默认是 ${ 和 })。
  • setValueDelimiter(String delimiter):设置变量与默认值的分隔符(默认是 : 或 :-)。
  • setEnableSubstitutionInVariables(boolean enable):是否开启变量名中的嵌套替换(如 KaTeX parse error: Expected '}', got 'EOF' at end of input: {{var}})。
  • replace(String source):执行替换并返回新文本。
2.2.2、使用示例

示例 1:基础 Map 替换与默认值

import org.apache.commons.text.StringSubstitutor;
import java.util.HashMap;
import java.util.Map;

public class SubstitutorBasicExample {
    public static void main(String[] args) {
        Map<String, String> valuesMap = new HashMap<>();
        valuesMap.put("animal", "quick brown fox");
        valuesMap.put("target", "lazy dog");

        // 默认使用 ${var} 语法
        StringSubstitutor sub = new StringSubstitutor(valuesMap);

        // 1. 基础替换
        String template = "The ${animal} jumps over the ${target}.";
        String result = sub.replace(template);
        System.out.println(result); // The quick brown fox jumps over the lazy dog.

        // 2. 使用默认值 (语法 ${var:-defaultValue})
        String templateWithDefault = "Hello, ${name:-Guest}! Welcome to ${target}.";
        System.out.println(sub.replace(templateWithDefault)); // Hello, Guest! Welcome to lazy dog.
    }
}
The quick brown fox jumps over the lazy dog.
Hello, Guest! Welcome to lazy dog.

示例 2:自定义占位符与嵌套替换

import org.apache.commons.text.StringSubstitutor;
import java.util.HashMap;
import java.util.Map;

public class SubstitutorAdvancedExample {
    public static void main(String[] args) {
        Map<String, String> map = new HashMap<>();
        map.put("key_user", "Alice");
        map.put("current_env", "user");

        StringSubstitutor sub = new StringSubstitutor(map);
        
        // 修改占位符为 #[var]
        sub.setVariablePrefix("#[");
        sub.setVariableSuffix("]");
        // 开启变量名嵌套解析
        sub.setEnableSubstitutionInVariables(true);

        // 嵌套解析:先解析内层的 #[current_env] 变成 user,再解析 #[key_user]
        String template = "Welcome, #[key_#[current_env]]!";
        System.out.println(sub.replace(template)); // Welcome, Alice!
    }
}
Welcome, Alice!

2.3、StringLookupFactory 与高级插值

commons-text 提供了内置的 插值机制(String Interpolation),允许通过前缀直接在文本中调用系统变量、环境变量、Base64 解码、日期格式化等。

2.3.1、常用 Lookup 前缀
  • sys::系统属性(如 ${sys:user.home})
  • env::环境变量(如 ${env:PATH})
  • date::日期格式化(如 ${date:yyyy-MM-dd})
  • base64Decoder::Base64 解码(如 ${base64Decoder:SGVsbG8=})
  • java::Java 版本/虚拟机信息
2.3.2、使用示例
import org.apache.commons.text.StringSubstitutor;
import org.apache.commons.text.lookup.StringLookupFactory;

public class InterpolationExample {
    public static void main(String[] args) {
        // 创建默认的开箱即用插值替换器
        StringSubstitutor interpolator = StringSubstitutor.createInterpolator();

        String template = "User Home: ${sys:user.home}\n" +
                         "Java Version: ${java:version}\n" +
                         "Today: ${date:yyyy-MM-dd HH:mm:ss}\n" +
                         "Decoded Secret: ${base64Decoder:SGVsbG8gV29ybGQ=}";

        String result = interpolator.replace(template);
        System.out.println(result);
    }
}
User Home: /Users/acton_zhang
Java Version: Java version 1.8.0_211
Today: 2026-09-05 13:47:55
Decoded Secret: Hello World

安全提示:在处理不可信用户输入时,请谨慎使用 StringSubstitutor.createInterpolator(),避免开启不必要的查找器(如 DNS 或脚本执行),以防范类似 Log4Shell 的注入攻击。

2.4、StringTokenizer 文本拆分

StringTokenizer 用于对文本进行高级切分,支持引用符(Quotes)、修剪空格(Trimming)以及处理连续分隔符。

2.4.1、核心 API
  • StringTokenizer.getCSVInstance():创建符合 CSV 标准的切分器。
  • setDelimiterChar(char delim):设置分隔符。
  • setQuoteChar(char quote):设置包裹引用的字符。
  • setIgnoreEmptyTokens(boolean flag):设置是否忽略空字段。
2.4.2、使用示例
import org.apache.commons.text.StringTokenizer;

public class TokenizerExample {
    public static void main(String[] args) {
        String input = "Apple, \"Banana, Sweet\", , Orange";

        // 创建 CSV 切分器(能正确处理双引号内的分隔符)
        StringTokenizer tokenizer = StringTokenizer.getCSVInstance(input);

        while (tokenizer.hasNext()) {
            System.out.println("Token: [" + tokenizer.next() + "]");
        }
        // 输出:
        // Token: [Apple]
        // Token: [Banana, Sweet]
        // Token: []
        // Token: [Orange]
    }
}
Token: [Apple]
Token: [Banana, Sweet]
Token: []
Token: [Orange]

3、转义与反转义

在处理 Web 安全(防御 XSS / SQL 注入)、文本序列化(JSON / XML / HTML / CSV)以及代码生成时,字符串的转义(Escape)与反转义(Unescape)是极为常见的操作。

commons-text 提供了专门处理文本转义的工具集,其中最常用的是 StringEscapeUtils 以及底层的 CharSequenceTranslator 架构。

架构概览 :
commons-text 的转义体系由以下两层构成:

  • 门面类(StringEscapeUtils):静态工具类,提供开箱即用的 HTML、XML、JSON、Java、JavaScript、CSV、XSI 等格式的转义与反转义方法。
  • 底层翻译器类(CharSequenceTranslator 及其子类):提供基于字符序列的规则转换链。StringEscapeUtils 内部的所有转义器本质上都是 CharSequenceTranslator 的具体实现(如 AggregateTranslator、LookupTranslator)。

3.1、StringEscapeUtils(常用静态门面)

3.1.1、核心API

StringEscapeUtils 涵盖了绝大多数日常开发所需的转义与解密操作。

转换类型转义 API反转义 API典型转换规则示例
HTML4StringEscapeUtils.escapeHtml4(str)StringEscapeUtils.unescapeHtml4(str)< → \rightarrow &lt; , " → \rightarrow &quot; , é → \rightarrow &eacute;
HTML5StringEscapeUtils.escapeHtml5(str)StringEscapeUtils.unescapeHtml5(str)支持最新的 HTML5 实体名称(如 &sup1;, &bigstar;
XMLStringEscapeUtils.escapeXml11(str)StringEscapeUtils.unescapeXml(str)< → \rightarrow &lt; , & → \rightarrow &amp; , ' → \rightarrow &apos;
JSONStringEscapeUtils.escapeJson(str)StringEscapeUtils.unescapeJson(str)"text" → \rightarrow \"text\" , 换行 → \rightarrow \n
JavaScriptStringEscapeUtils.escapeEcmaScript(str)StringEscapeUtils.unescapeEcmaScript(str)单引号 ' → \rightarrow \' , 双引号 " → \rightarrow \"
JavaStringEscapeUtils.escapeJava(str)StringEscapeUtils.unescapeJava(str)制表符 → \rightarrow \t , Unicode 字符 → \rightarrow \uXXXX
CSVStringEscapeUtils.escapeCsv(str)StringEscapeUtils.unescapeCsv(str)若包含逗号或换行,自动用双引号包裹并将内部双引号转义为 ""
XSI (Shell)StringEscapeUtils.escapeXsi(str)StringEscapeUtils.unescapeXsi(str)Linux Shell 路径转义,如 my file.txt → \rightarrow my\ file.txt
3.1.2、使用示例

1. HTML 与 XML 安全转义(防御 XSS)

在渲染用户输入的网页内容或拼接 XML 报文时,转义特殊字符可以有效防止脚本注入或 XML 解析破坏:

import org.apache.commons.text.StringEscapeUtils;

public class HtmlXmlEscapeDemo {

    public static void main(String[] args) {
        String userInput = "<script>alert('XSS & Hack!');</script>";

        // 1. HTML4 / HTML5 转义
        String escapedHtml = StringEscapeUtils.escapeHtml4(userInput);
        System.out.println("HTML4 转义: " + escapedHtml);
        // 输出: &lt;script&gt;alert(&apos;XSS &amp; Hack!&apos;);&lt;/script&gt;

        // 还原 HTML
        String unescapedHtml = StringEscapeUtils.unescapeHtml4(escapedHtml);
        System.out.println("HTML 还原: " + unescapedHtml);

        // 2. XML 1.1 转义
        String xmlContent = "<note author=\"张三 & Co.\">100% < 200%</note>";
        String escapedXml = StringEscapeUtils.escapeXml11(xmlContent);
        System.out.println("XML11 转义: " + escapedXml);
        // 输出: &lt;note author=&quot;张三 &amp; Co.&quot;&gt;100% &lt; 200%&lt;/note&gt;
    }
}
HTML4 转义: &lt;script&gt;alert('XSS &amp; Hack!');&lt;/script&gt;
HTML 还原: <script>alert('XSS & Hack!');</script>
XML11 转义: &lt;note author=&quot;张三 &amp; Co.&quot;&gt;100% &lt; 200%&lt;/note&gt;

2. JSON & JavaScript 字符串转义

将服务端拼接的变量嵌入到前端 JavaScript 或 JSON 文本块时,防止因换行符、引号导致语法错误:

import org.apache.commons.text.StringEscapeUtils;

public class JsonJsEscapeDemo {

    public static void main(String[] args) {
        String rawText = "Line 1\nLine 2 with \"quotes\" and 'single quote'";

        // 1. JSON 字符串转义
        String jsonEscaped = StringEscapeUtils.escapeJson(rawText);
        System.out.println("JSON 转义:\n" + jsonEscaped);
        // 输出: Line 1\nLine 2 with \"quotes\" and 'single quote'

        // 2. JavaScript / ECMA Script 转义
        String jsEscaped = StringEscapeUtils.escapeEcmaScript(rawText);
        System.out.println("JS 转义:\n" + jsEscaped);
        // 输出: Line 1\nLine 2 with \"quotes\" and \'single quote\'

        // 还原 JSON
        System.out.println("JSON 还原: " + StringEscapeUtils.unescapeJson(jsonEscaped));
    }
}
JSON 转义:
Line 1\nLine 2 with \"quotes\" and 'single quote'
JS 转义:
Line 1\nLine 2 with \"quotes\" and \'single quote\'
JSON 还原: Line 1
Line 2 with "quotes" and 'single quote'

3. CSV 文本转义

CSV 格式对于包含逗号 ,、双引号 " 和换行符 \n 的字段有严格的格式要求,escapeCsv 会自动判断是否需要加上双引号进行包裹:

import org.apache.commons.text.StringEscapeUtils;

public class CsvEscapeDemo {

    public static void main(String[] args) {
        // 普通文本:不需要转义
        System.out.println(StringEscapeUtils.escapeCsv("hello")); 
        // 输出: hello

        // 包含逗号的字段:自动用双引号包裹
        System.out.println(StringEscapeUtils.escapeCsv("Beijing, China")); 
        // 输出: "Beijing, China"

        // 包含双引号的字段:内层双引号变成两个双引号 "",并外层包裹
        System.out.println(StringEscapeUtils.escapeCsv("Say \"Hello\"")); 
        // 输出: "Say ""Hello"""
    }
}

hello
"Beijing, China"
"Say ""Hello"""

3.2、CharSequenceTranslator(自定义转换链引擎)

如果预设的 StringEscapeUtils 无法满足特殊的业务规则(例如:自定义脱敏规则或替换映射表),可以通过继承或组合 CharSequenceTranslator 来自定义转义器。

3.2.1、常见子类与组合工具
类名核心作用机制与特点典型适用场景
LookupTranslator传入一个 Map<CharSequence, CharSequence>,按精确匹配或映射关系替换字符基于字典查找表进行高效精确匹配替换,可一次性配置多个映射词对自定义关键词屏蔽、Emoji/特殊符号映射替换、老旧编码转换
AggregateTranslator将多个 CharSequenceTranslator 组合起来,形成链式转换器组合模式(Composite Pattern),按顺序依次尝试调用每个转换器直到匹配成功构建复合型的复杂转义器(如同时处理 HTML 实体与 Unicode 转义)
NumericEntityEscaper将字符转义为 Unicode 数字实体(如 &#32;&#128514;支持按字符 Code Point 范围过滤(如仅转义 ASCII 以外的字符)XML/HTML 文本安全转义、高兼容性 Web 页面字符防乱码
UnicodeEscaper将指定范围外的字符转义为 Unicode 转义序列(如 \u0020可指定区间 betweenoutsideOfbelowabove动态生成 Java / JavaScript 源代码、Properties 属性文件序列化
3.2.2、使用示例
import org.apache.commons.text.StringEscapeUtils;
import org.apache.commons.text.translate.AggregateTranslator;
import org.apache.commons.text.translate.CharSequenceTranslator;
import org.apache.commons.text.translate.LookupTranslator;
import org.apache.commons.text.translate.UnicodeEscaper;

import java.util.HashMap;
import java.util.Map;

public class CustomTranslatorDemo {

    public static void main(String[] args) {
        // 1. 创建自定义映射表(例如:敏感词替换或特规符号替换)
        Map<CharSequence, CharSequence> customMap = new HashMap<>();
        customMap.put("机密", "[REDACTED]");
        customMap.put("A", "α");

        LookupTranslator lookupTranslator = new LookupTranslator(customMap);

        // 2. 结合 UnicodeEscaper(将所有 ASCII 以外的字符转义为 \uXXXX 格式)
        UnicodeEscaper unicodeEscaper = UnicodeEscaper.above(127);

        // 3. 将多个转换规则组合为一个 AggregateTranslator 链
        CharSequenceTranslator myTranslator = new AggregateTranslator(
                lookupTranslator,
                unicodeEscaper
        );

        String input = "这是机密文件 A 级";
        String result = myTranslator.translate(input);

        System.out.println("自定义链式转义结果: " + result);
        // 输出示例: \u8FD9\u662F[REDACTED]\u6587\u4EF6 \u03B1 \u7EA7
    }
}

4、文本相似度

commons-text 在 org.apache.commons.text.similarity 包下提供了一套全面且高效的文本相似度与距离算法实现。

它将所有算法抽象为统一的泛型接口 EditDistance<R> 和 SimilarityScore<R>,既可用于单次计算,也能在频繁比较时通过预构建对象提高性能。

4.1、核心接口与分类

算法主要分为三类:

  • 基于编辑距离(Edit Distance):计算将一个字符串转换为另一个字符串所需的最少操作次数(插入、删除、替换或邻近交换)。数值越小越相似。
  • 基于字符匹配/重叠度(Matching / Overlap):基于公共字符数、顺序或 Token 重叠计算。数值越大越相似。
  • 基于向量/余弦相似度(Vector Space / Cosine):将文本转化为词频向量,计算向量夹角的余弦值(范围 [ 0 , 1 ] [0, 1] [0,1])。

4.2、LevenshteinDistance(莱文斯坦距离 / 编辑距离)

  • 原理:允许插入、删除和替换三种基本操作,计算从源字符串变换到目标字符串所需的最小单字符编辑次数。
  • 适用场景:拼写检查、模糊匹配、DNA 序列对比。
import org.apache.commons.text.similarity.LevenshteinDistance;

public class LevenshteinDemo {

    public static void main(String[] args) {
        // 1. 无限制阈值计算
        LevenshteinDistance distance = new LevenshteinDistance();
        
        // "kitten" -> "sitting" 需要 3 次替换/插入操作
        int d1 = distance.apply("kitten", "sitting");
        System.out.println("Levenshtein 距离: " + d1); // 3

        // 2. 带最大阈值 limit (超过限制直接返回 -1,用于大数据量性能优化)
        LevenshteinDistance limitDistance = new LevenshteinDistance(2);
        int d2 = limitDistance.apply("kitten", "sitting");
        System.out.println("带阈值限制结果: " + d2); // -1 (因为真实距离 3 > 阈值 2)
    }
}
自定义链式转义结果: \u8FD9\u662F[REDACTED]\u6587\u4EF6 α \u7EA7

4.3、JaroWinklerDistance 与 JaroWinklerSimilarity

  • 原理:基于两字符串中字符匹配的数量和顺序。JaroWinkler 在 Jaro 算法的基础上,给相同前缀(Prefix)赋予更高的权重。
  • 返回值:数值范围为 [ 0.0 , 1.0 ] [0.0, 1.0] [0.0,1.0],1.0 表示完全匹配,0.0 表示完全不同。
  • 适用场景:人名、地名、短标题的匹配(对开头字符敏感)。
import org.apache.commons.text.similarity.JaroWinklerSimilarity;

public class JaroWinklerDemo {

    public static void main(String[] args) {
        JaroWinklerSimilarity similarity = new JaroWinklerSimilarity();

        // 比较两组名称
        double score1 = similarity.apply("MARTHA", "MARHTA");
        double score2 = similarity.apply("DWAYNE", "DUANE");

        System.out.println("MARTHA vs MARHTA 相似度: " + score1); // 0.9611 (前缀相同,分值极高)
        System.out.println("DWAYNE vs DUANE 相似度: " + score2);   // 0.8400
    }
}
MARTHA vs MARHTA 相似度: 0.9611111111111111
DWAYNE vs DUANE 相似度: 0.8400000000000001

4.4、LongestCommonSubsequence (LCS 最长公共子序列)

  • 原理:寻找两字符串中保持相对顺序的最长字符序列(不要求连续)。
  • 衍生类:LongestCommonSubsequenceDistance(距离值 = 字符串长度之和 - 2 × LCS 长度)。
  • 适用场景:版本对比(Diff 工具)、文本结构重合度检测。
import org.apache.commons.text.similarity.LongestCommonSubsequence;

public class LcsDemo {

    public static void main(String[] args) {
        LongestCommonSubsequence lcs = new LongestCommonSubsequence();

        // 计算最长公共子序列长度
        int lcsLength = lcs.apply("ABCDEF", "ACDBCF");
        System.out.println("LCS 长度: " + lcsLength); // 4 (公共子序列为 "ACDF" 或 "ACBF")

        // 提取具体的公共子序列文本
        CharSequence subsequence = lcs.logestCommonSubsequence("ABCDEF", "ACDBCF");
        System.out.println("LCS 文本: " + subsequence); // ACDF
    }
}
LCS 长度: 4
LCS 文本: ACDF

4.5、CosineSimilarity (余弦相似度)

  • 原理:利用 TF(词频) 将两段文本向量化,计算两向量在多维空间中的夹角余弦值。
  • 返回值:范围为 [ 0.0 , 1.0 ] [0.0, 1.0] [0.0,1.0],1.0 表示两段文本词频分布完全一致。
  • 适用场景:长文本、文章段落、大文本重合度/查重比对。
import org.apache.commons.text.similarity.CosineSimilarity;
import org.apache.commons.text.similarity.RegexTokenizer;

import java.util.Map;

public class CosineDemo {

    public static void main(String[] args) {
        CosineSimilarity cosine = new CosineSimilarity();

        // 1. 构建词频向量 (通常通过分词工具生成 Map<CharSequence, Integer>)
        Map<CharSequence, Integer> text1Vector = Map.of("apple", 2, "banana", 1, "orange", 1);
        Map<CharSequence, Integer> text2Vector = Map.of("apple", 1, "banana", 2, "grape", 1);

        // 2. 计算夹角余弦值
        double score = cosine.cosineSimilarity(text1Vector, text2Vector);
        System.out.println("余弦相似度得分: " + score); // ~0.833
    }
}

4.6、JaccardSimilarity 与 JaccardDistance

  • 原理:通过交集大小 / 并集大小来计算集合重叠度。
  • 计算逻辑 Jaccard ( A , B ) = ∣ A ∩ B ∣ ∣ A ∪ B ∣ \text{Jaccard}(A, B) = \frac{\vert{}A \cap B\vert{}}{\vert{}A \cup B\vert{}} Jaccard(A,B)=ABAB
  • 适用场景:无序字符集合匹配、标签/关键词交集重合度比对。
import org.apache.commons.text.similarity.JaccardSimilarity;

public class JaccardDemo {

    public static void main(String[] args) {
        JaccardSimilarity jaccard = new JaccardSimilarity();

        // 比较两字符串中字符集合的交并比
        double score = jaccard.apply("frog", "fog");
        System.out.println("Jaccard 相似度: " + score); // 3/4 = 0.75 (字符集合为 {f,r,o,g} 与 {f,o,g})
    }
}

4.7、常见算法对比与选型指南

算法类 (Class)返回值类型数值范围特点 / 核心考量因素推荐使用场景
LevenshteinDistanceInteger [ 0 , + ∞ ) [0, +\infty) [0,+)按次计费:计算插入、删除、替换操作的最少次数;支持传入 Threshold 以提前终止超限计算。拼写纠错、短词替换检测、输入框自动补全关联
JaroWinklerSimilarityDouble [ 0.0 , 1.0 ] [0.0, 1.0] [0.0,1.0]前缀偏置:优先匹配开头相同的字符,前缀相同时给予更高的相似度权重。人名、地址、短关键词匹配与去重
LongestCommonSubsequenceInteger [ 0 , + ∞ ) [0, +\infty) [0,+)顺序匹配:不要求字符连续,仅关注字符出现的相对顺序,仅考虑增删。文本 Diff、代码/文件修改变化对比
CosineSimilarityDouble [ 0.0 , 1.0 ] [0.0, 1.0] [0.0,1.0]向量夹角:基于词频向量(Bag-of-Words)夹角余弦值计算,忽略顺序,关注词汇重合度。长文章查重、段落文档相似度分析
JaccardSimilarityDouble [ 0.0 , 1.0 ] [0.0, 1.0] [0.0,1.0]集合交并:基于字符/Token 集合交集与并集的比例,完全不考虑位置与顺序。标签匹配、无序短语重合度评估

5、字符变换与匹配

commons-text 在 org.apache.commons.text 以及 org.apache.commons.text.matcher 包中提供了一系列专门用于字符变换(Transformation/Case Mapping)与模式匹配(String Matching)的强大类库。

它们弥补了 JDK 原生 String / Pattern 在流式处理、复杂大小写转换以及高效字符查找方面的不足。

架构概览

							  ┌───────────────────────────────┐
                              │     org.apache.commons.text   │
                              └───────────────┬───────────────┘
                                              │
                ┌─────────────────────────────┼─────────────────────────────┐
                ▼                             ▼                             ▼
    ┌───────────────────────┐     ┌───────────────────────┐     ┌───────────────────────┐
    │     WordUtils         │     │     CaseUtils         │     │  StringMatcherFactory │
    │ (单词级大小写与排版)   │     │ (驼峰命名与格式变换)   │     │ (高效字符匹配器工厂)   │
    └───────────────────────┘     └───────────────────────┘     └───────────────────────┘

5.1、CaseUtils(命名规范转换)

CaseUtils 主要用于处理开发中常见的驼峰命名(Camel Case)转换,能够自动识别空格、下划线、减号等分隔符。

5.1.1、核心API
  • toCamelCase(String str, boolean capitalizeFirstLetter, char... delimiters):将包含分隔符的字符串转换为驼峰命名。
    • capitalizeFirstLetter:true 生成大驼峰(PascalCase),false 生成小驼峰(camelCase)。
    • delimiters:可选的分隔符列表,若不传则默认将空格、下划线 _、减号 - 等视为分隔符。
5.1.2、使用示例
import org.apache.commons.text.CaseUtils;

public class CaseUtilsDemo {
    public static void main(String[] args) {
        String input = "user_first_name-details";

        // 1. 转为小驼峰 (camelCase)
        String camelCase = CaseUtils.toCamelCase(input, false, '_', '-');
        System.out.println("小驼峰: " + camelCase); // userFirstNameDetails

        // 2. 转为大驼峰 / 帕斯卡命名 (PascalCase)
        String pascalCase = CaseUtils.toCamelCase(input, true, '_', '-');
        System.out.println("大驼峰: " + pascalCase); // UserFirstNameDetails
    }
}
小驼峰: userFirstNameDetails
大驼峰: UserFirstNameDetails

5.2、WordUtils(单词与排版变换)

WordUtils 关注于单词级别的字符变换与文本格式化排版(如首字母大写、换行排版、大小写翻转等)。

5.2.1、核心API
  • capitalize(String str) / uncapitalize(String str):单词首字母大写 / 小写。
  • capitalizeFully(String str):将每个单词的首字母大写,其余字母强制转换为小写。
  • swapCase(String str):翻转大小写(大写变小写,小写变大写)。
  • wrap(String str, int wrapLength):将长文本按照指定每行长度进行软换行排版。
  • initials(String str):提取短语中各个单词的首字母缩写。
5.2.2、使用示例
import org.apache.commons.text.WordUtils;

public class WordUtilsDemo {
    public static void main(String[] args) {
        // 1. 单词首字母格式化
        String text = "tHE qUICK bROWN fOX";
        System.out.println("Capitalize Fully: " + WordUtils.capitalizeFully(text)); 
        // 输出: The Quick Brown Fox

        // 2. 大小写颠倒/翻转
        System.out.println("Swap Case: " + WordUtils.swapCase("Java 17 & Commons-Text")); 
        // 输出: jAVA 17 & cOMMONS-tEXT

        // 3. 提取首字母缩写
        System.out.println("Initials: " + WordUtils.initials("Apache Commons Text")); 
        // 输出: ACT

        // 4. 文本长句自动排版换行
        String longSentence = "This is a very long sentence that needs to be wrapped cleanly for terminal output.";
        String wrapped = WordUtils.wrap(longSentence, 30);
        System.out.println("--- 自动换行排版 ---");
        System.out.println(wrapped);
        // 输出: 按照 30 字符宽度自动以换行符分隔单词
    }
}
Capitalize Fully: The Quick Brown Fox
Swap Case: jAVA 17 & cOMMONS-tEXT
Initials: ACT
--- 自动换行排版 ---
This is a very long sentence
that needs to be wrapped
cleanly for terminal output.

5.3、StringMatcher 与 StringMatcherFactory(字符匹配器)

org.apache.commons.text.matcher.StringMatcher 是一个高性能的字符/子串匹配接口,广泛应用于 StringTokenizer 和 StringSubstitutor 中。

相比正则表达式,StringMatcher 不涉及正则引擎编译,在针对固定字符、空格、引号、字符集等匹配场景时,性能极高。通过 StringMatcherFactory 工厂类获取具体匹配器实例。

5.3.1、核心API

工厂方法作用匹配规则 / 说明典型适用场景 / 示例
spaceMatcher()匹配标准空格字符仅匹配 ASCII 单个空格字符 ' '固定空格分隔的文本解析
splitMatcher()匹配标准空白符/分隔符匹配空格、制表符 \t、换行符 \n、回车符 \r 以及换页符 \f通用空白分割解析(类似 \s+ 行为)
quoteMatcher()匹配单/双引号匹配 '"忽略引名内部分隔符的文本拆分(如带引号的 CSV 字段)
trimMatcher()匹配控制字符与空白匹配 Code Point 小于等于 \u0020 的控制字符常用于消除 Token 两端的控制字符与无效空白
charMatcher(char ch)匹配指定单个字符精确匹配指定的字符 ch定界符切分,如逗号 ,、分号 ; 或冒号 :
stringMatcher(String str)匹配指定固定字符串匹配完整的多字符子串 str复杂多字符分隔符切分,如 "-->""::"
charSetMatcher(char... chars)匹配字符集中任意字符只要字符出现在传入的字符数组/字符串中即匹配混合分隔符切分,如匹配元音字母 ['a', 'e', 'i', 'o', 'u'] 或 `[‘,’, ‘;’, ’
noneMatcher()不匹配任何字符永远返回不匹配(匹配长度 0)显式禁用某些默认行为(如禁用默认的 Quote 匹配)
5.3.2、使用示例
import org.apache.commons.text.matcher.StringMatcher;
import org.apache.commons.text.matcher.StringMatcherFactory;

public class StringMatcherDemo {
    public static void main(String[] args) {
        char[] buffer = "hello = \"world\";".toCharArray();

        // 1. 创建匹配 '=' 的匹配器
        StringMatcher equalsMatcher = StringMatcherFactory.INSTANCE.charMatcher('=');

        // 2. 创建匹配引号的匹配器
        StringMatcher quoteMatcher = StringMatcherFactory.INSTANCE.quoteMatcher();

        // 3. 检查 index = 6 处是否匹配 '='
        int matchLength1 = equalsMatcher.isMatch(buffer, 6, 0, buffer.length);
        System.out.println("Index 6 匹配等于号长度: " + matchLength1); // 返回 1

        // 4. 检查 index = 8 处的双引号
        int matchLength2 = quoteMatcher.isMatch(buffer, 8, 0, buffer.length);
        System.out.println("Index 8 匹配引号长度: " + matchLength2); // 返回 1
    }
}
Index 6 匹配等于号长度: 1
Index 8 匹配引号长度: 1

5.4、选型对比

工具类 / 接口核心用途性能特点 / 推荐场景
CaseUtils驼峰命名互相转换
支持将带有各种分隔符的字符串转换为标准的驼峰命名(Camel Case)。
数据库字段/JSON 键名转换
例如将 USER_NAMEuser-name 自动转换为 Java 属性名 userName
WordUtils文本排版与大小写操作
提供单词首字母大写转换、大小写反转(Swap Case)、以及按指定长度自动换行(Text Wrapping)。
日志排版与标题规范化
终端/控制台输出格式化对齐、报表生成、文章标题规范化(Title Case)以及生成首字母缩写。
StringMatcher零正则的高性能字符/子串匹配
基于状态机或字符遍历实现的匹配器,完全不依赖较重的正则表达式引擎。
底层解析器构建
替代正则,打造高性能的自定义流式 Token 分词工具、解析器及规则引擎。

6、构建与生成

commons-text 在 org.apache.commons.text 包中提供了多个专门用于文本构建(Building)与随机生成(Generation)的核心工具类。它们极大地补充和增强了 JDK 原生的 StringBuilder、Formatter 和 Random 机制。

核心类架构职责典型应用场景
RandomStringGenerator基于 Builder 模式的高性能、可定制随机字符串生成器
支持 Unicode Code Point 范围筛选、字符集谓词过滤及加密安全随机源(SecureRandom)绑定。
生成安全验证码、强随机密码、API 唯一 Token、接口测试 Mock 模拟数据
TextStringBuilder增强版 StringBuilder(取代原 StrBuilder)
提供集合/数组直接追加、分隔符自动插入、原位查找替换以及流(Reader/Writer)无缝集成。
高效 SQL/CSV 动态拼接、高频缓冲区重用、大文本流式读写适配
FormattableUtils配合 java.util.Formattable 接口的格式化辅助类
提供字符串对齐(左对齐/右对齐)、超长截断以及填充字符插入等低层控制。
自定义领域对象的 printf / String.format() 格式化输出与报表对齐

6.1、RandomStringGenerator(随机文本生成器)

RandomStringGenerator 替代了传统的 RandomStringUtils(已废弃),采用 Builder 模式 提供了对生成字符范围、字符类型谓词、白名单字符集以及安全随机源(SecureRandom)的精细化控制。

6.1.1、核心API
  • withinRange(char minimumCodePoint, char maximumCodePoint):设置字符的 Unicode 码点闭区间(如 ‘0’, ‘z’)。
  • selectFrom(char... chars):仅从指定的白名单字符集中挑选。
  • filteredBy(CharacterPredicate... predicates):应用字符过滤器,例如 CharacterPredicates.LETTERS(仅字母)或 DIGITS(仅数字)。
  • usingRandom(TextRandomProvider randomProvider):自定义随机数源(可传入 SecureRandom::nextInt 以实现密码学安全)。
  • generate(int length) generate(int minLengthInclusive, int maxLengthInclusive):执行生成并返回随机字符串。
6.1.2、使用示例

示例 1:生成强安全的随机密码(Secure Token)

import org.apache.commons.text.CharacterPredicates;
import org.apache.commons.text.RandomStringGenerator;

import java.security.SecureRandom;

public class SecureRandomDemo {
    public static void main(String[] args) {
        // 使用 SecureRandom 确保密码学安全
        SecureRandom secureRandom = new SecureRandom();

        RandomStringGenerator generator = new RandomStringGenerator.Builder()
                .withinRange('0', 'z')
                // 仅保留字母和数字,过滤掉中间的标点符号
                .filteredBy(CharacterPredicates.LETTERS, CharacterPredicates.DIGITS)
                .usingRandom(secureRandom::nextInt)
                .build();

        // 生成 16 位随机强密码
        String password = generator.generate(16);
        System.out.println("强随机密码: " + password);
    }
}
强随机密码: 29HeMFapfwqYZs3O

示例 2:排除易混淆字符生成图形验证码

import org.apache.commons.text.RandomStringGenerator;

public class CaptchaDemo {
    public static void main(String[] args) {
        // 排除容易混淆的字符 (如 0, O, 1, l, I)
        char[] customChars = "23456789ABCDEFGHJKLMNPQRSTUVWXYZ".toCharArray();

        RandomStringGenerator captchaGenerator = new RandomStringGenerator.Builder()
                .selectFrom(customChars)
                .build();

        // 生成 6 位随机验证码
        String captcha = captchaGenerator.generate(6);
        System.out.println("图形验证码: " + captcha);
    }
}
图形验证码: JW6DGU

6.2、TextStringBuilder(可变字符序列增强)

TextStringBuilder 是对 JDK 原生 StringBuilder 的增强实现,增加了集合遍历追加、分隔符处理、模式替换以及无缝转为 Reader/Writer 的能力。

6.2.1、核心API
  • appendWithSeparators(Iterable<?> iterable, String separator):遍历追加集合元素,并在元素之间自动添加分隔符(避免手动判断是否是最后一个元素)。
  • appendPadding(int length, char padChar):追加指定数量的填充字符(常用于文本对齐)。
  • replaceAll(StringMatcher matcher, String replace):结合 StringMatcher 批量替换符合规则的字符/子串。
  • asReader() / asWriter():直接将内部 Buffer 暴露为 Reader 或 Writer 视图,避免内存拷贝。
  • clear():清空内部数组供对象重用,减少垃圾回收压力。
6.2.2、使用示例
import org.apache.commons.text.TextStringBuilder;
import java.util.List;

public class TextStringBuilderDemo {
    public static void main(String[] args) {
        List<String> columns = List.of("id", "username", "email", "status");

        TextStringBuilder sb = new TextStringBuilder();

        // 1. 自动处理字段间的逗号分隔符
        sb.append("SELECT ");
        sb.appendWithSeparators(columns, ", ");
        sb.append(" FROM sys_user WHERE status = ");
        sb.append(1);

        System.out.println("构建的 SQL: " + sb.toString());
        // 输出: SELECT id, username, email, status FROM sys_user WHERE status = 1

        // 2. 清空并重用缓冲区,进行居中填充排版
        sb.clear();
        sb.appendPadding(10, '=').append(" HEADER ").appendPadding(10, '=');
        System.out.println(sb.toString());
        // 输出: ========== HEADER ==========
    }
}

6.3、FormattableUtils(自定义对象格式化)

FormattableUtils 用于辅助实现 JDK 的 java.util.Formattable 接口。它能让自定义对象在通过 String.format() 输出时,优雅地支持对齐(左对齐/右对齐)、最小宽度和最大精度(超长截断并补省略号)。

6.3.1、核心 API
  • append(CharSequence seq, Formatter formatter, int flags, int width, int precision, char padChar, CharSequence ellipsis):将序列格式化并写入 Formatter。
6.3.2、使用示例
import org.apache.commons.text.FormattableUtils;

import java.util.Formattable;
import java.util.Formatter;

public class UserAccount implements Formattable {
    private final String accountName;

    public UserAccount(String accountName) {
        this.accountName = accountName;
    }

    @Override
    public void formatTo(Formatter formatter, int flags, int width, int precision) {
        // 当宽度超长时截断并补充 "...",不足宽度时补空格
        FormattableUtils.append(accountName, formatter, flags, width, precision, ' ', "...");
    }

    public static void main(String[] args) {
        UserAccount user = new UserAccount("EnterpriseSuperAdmin");

        // %15.10s 含义: 最小宽度 15,最大字符数 10
        String formatted = String.format("当前用户: [%15.10s]", user);
        System.out.println(formatted);
        // 输出格式化并补省略号后的文本
    }
}
6.4、选型与最佳实践
  • 生成敏感 Token/密码:务必使用 RandomStringGenerator.Builder.usingRandom(SecureRandom::nextInt),默认的伪随机源无法抵御安全攻击。
  • 避免频繁创建临时对象:在批量拼接、生成日志或构建报文时,建议重用 TextStringBuilder.clear() 实例。
  • 线程安全性:RandomStringGenerator 实例创建完毕后是线程安全的;TextStringBuilder 是非线程安全的(类似 StringBuilder),切勿在多线程共享。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值