{"id":13486319,"url":"https://github.com/houbb/sensitive-word","last_synced_at":"2025-05-12T15:24:42.087Z","repository":{"id":38363991,"uuid":"232232349","full_name":"houbb/sensitive-word","owner":"houbb","description":" 👮‍♂️The sensitive word tool for java.(敏感词/违禁词/违法词/脏词。基于 DFA 算法实现的高性能 java 敏感词过滤工具框架。内置支持单词标签分类分级。请勿发布涉及政治、广告、营销、翻墙、违反国家法律法规等内容。高性能敏感词检测过滤组件，附带繁体简体互换，支持全角半角互换，汉字转拼音，模糊搜索等功能。)","archived":false,"fork":false,"pushed_at":"2025-05-02T13:09:16.000Z","size":5581,"stargazers_count":4993,"open_issues_count":4,"forks_count":679,"subscribers_count":31,"default_branch":"master","last_synced_at":"2025-05-02T13:19:53.287Z","etag":null,"topics":["dfa","dirty-word","filter","java","nlp","pinyin","search","sensitive","sensitive-word","sensitive-word-filter","string-matching","textfliter","trie-tree"],"latest_commit_sha":null,"homepage":"https://houbb.github.io/opensource/sensitive-word","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/houbb.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGE_LOG.md","contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-01-07T03:10:44.000Z","updated_at":"2025-05-02T13:09:20.000Z","dependencies_parsed_at":"2024-02-19T13:29:29.346Z","dependency_job_id":"8a424690-a3a6-461e-b388-d0257db39fe5","html_url":"https://github.com/houbb/sensitive-word","commit_stats":{"total_commits":154,"total_committers":7,"mean_commits":22.0,"dds":0.5909090909090908,"last_synced_commit":"4d606eaf08f4640cfad61829032f5e8777708ed7"},"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/houbb%2Fsensitive-word","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/houbb%2Fsensitive-word/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/houbb%2Fsensitive-word/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/houbb%2Fsensitive-word/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/houbb","download_url":"https://codeload.github.com/houbb/sensitive-word/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253764295,"owners_count":21960553,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["dfa","dirty-word","filter","java","nlp","pinyin","search","sensitive","sensitive-word","sensitive-word-filter","string-matching","textfliter","trie-tree"],"created_at":"2024-07-31T18:00:44.032Z","updated_at":"2025-05-12T15:24:42.056Z","avatar_url":"https://github.com/houbb.png","language":"Java","readme":"# sensitive-word\n\n[sensitive-word](https://github.com/houbb/sensitive-word) 基于 DFA 算法实现的高性能敏感词工具。\n\n[![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.github.houbb/sensitive-word/badge.svg)](http://mvnrepository.com/artifact/com.github.houbb/sensitive-word)\n[![Open Source Love](https://badges.frapsoft.com/os/v2/open-source.svg?v=103)](https://github.com/houbb/sensitive-word)\n[![](https://img.shields.io/badge/license-Apache2-FF0080.svg)](https://github.com/houbb/sensitive-word/blob/master/LICENSE.txt)\n\n\u003e [在线体验](https://houbb.github.io/opensource/sensitive-word)\n\n如果有一些疑难杂症，可以加入：[技术交流群](https://mp.weixin.qq.com/s/rkSvXxiiLGjl3S-ZOZCr0Q)\n\n[sensitive-word-admin](https://github.com/houbb/sensitive-word-admin) 是对应的控台的应用，目前功能处于初期开发中，MVP 版本可用。\n\n## 创作目的\n\n大家好，我是老马。\n\n一直想实现一款简单好用敏感词工具，于是开源实现了这个工具。\n\n基于 DFA 算法实现，目前敏感词库内容收录 6W+（源文件 18W+，经过一次删减）。\n\n后期将进行持续优化和补充敏感词库，并进一步提升算法的性能。\n\nv0.24.0 开始内置支持对敏感词的分类细化，不过工作量比较大，难免存在疏漏。\n\n欢迎 PR 改进， github 提需求，或者加入技术交流群沟通吹牛！\n\n## 特性\n\n- 6W+ 词库，且不断优化更新\n\n- 基于 fluent-api 实现，使用优雅简洁\n\n- [基于 DFA 算法，性能为 7W+ QPS，应用无感](https://github.com/houbb/sensitive-word#benchmark)\n\n- [支持敏感词的判断、返回、脱敏等常见操作](https://github.com/houbb/sensitive-word#%E6%A0%B8%E5%BF%83%E6%96%B9%E6%B3%95)\n\n- [支持常见的格式转换](https://github.com/houbb/sensitive-word#%E6%9B%B4%E5%A4%9A%E7%89%B9%E6%80%A7)\n\n全角半角互换、英文大小写互换、数字常见形式的互换、中文繁简体互换、英文常见形式的互换、忽略重复词等\n\n- [支持敏感词检测、邮箱检测、数字检测、网址检测、IPV4等](https://github.com/houbb/sensitive-word#%E6%9B%B4%E5%A4%9A%E6%A3%80%E6%B5%8B%E7%AD%96%E7%95%A5)\n\n- [支持自定义替换策略](https://github.com/houbb/sensitive-word#%E8%87%AA%E5%AE%9A%E4%B9%89%E6%9B%BF%E6%8D%A2%E7%AD%96%E7%95%A5)\n\n- [支持用户自定义敏感词和白名单](https://github.com/houbb/sensitive-word#%E9%85%8D%E7%BD%AE%E4%BD%BF%E7%94%A8)\n\n- [支持数据的数据动态更新（用户自定义），实时生效](https://github.com/houbb/sensitive-word#%E5%8A%A8%E6%80%81%E5%8A%A0%E8%BD%BD%E7%94%A8%E6%88%B7%E8%87%AA%E5%AE%9A%E4%B9%89)\n\n- [支持敏感词的标签接口+内置分类实现](https://github.com/houbb/sensitive-word#%E6%95%8F%E6%84%9F%E8%AF%8D%E6%A0%87%E7%AD%BE)\n\n- [支持跳过一些特殊字符，让匹配更灵活](https://github.com/houbb/sensitive-word#%E5%BF%BD%E7%95%A5%E5%AD%97%E7%AC%A6)\n\n- [支持黑白名单单个的新增/修改，无需全量初始化](https://github.com/houbb/sensitive-word?tab=readme-ov-file#%E9%92%88%E5%AF%B9%E5%8D%95%E4%B8%AA%E8%AF%8D%E7%9A%84%E6%96%B0%E5%A2%9E%E5%88%A0%E9%99%A4%E6%97%A0%E9%9C%80%E5%85%A8%E9%87%8F%E5%88%9D%E5%A7%8B%E5%8C%96)\n\n- [支持词匹配模式的两种模式](https://github.com/houbb/sensitive-word?tab=readme-ov-file#wordfailfast-%E6%95%8F%E6%84%9F%E8%AF%8D%E5%8C%B9%E9%85%8D%E5%BF%AB%E9%80%9F%E5%A4%B1%E8%B4%A5%E6%A8%A1%E5%BC%8F)\n\n## 变更日志\n\n[CHANGE_LOG.md](https://github.com/houbb/sensitive-word/blob/master/CHANGE_LOG.md)\n\n## 更多资料\n\n### 敏感词控台\n\n有时候敏感词有一个控台，配置起来会更加灵活方便。\n\n\u003e [java 如何实现开箱即用的敏感词控台服务？](https://mp.weixin.qq.com/s/rQo75cfMU_OEbTJa0JGMGg)\n\n### 敏感词标签文件\n\n梳理了大量的敏感词标签文件，可以让我们的敏感词更加方便。\n\n这两个资料阅读可在下方文章获取：\n\n\u003e [v0.11.0-敏感词新特性及对应标签文件](https://mp.weixin.qq.com/s/m40ZnR6YF6WgPrArUSZ_0g)\n\n目前 v0.24.0 已内置实现单词标签，需要的建议升级到最新版本。\n\n# 支持开源\n\n开源不易，如果本项目对你有帮助，你可以请老马喝一杯奶茶。\n\n\u003cimg src=\"https://github.com/houbb/sensitive-word/raw/master/lmxxf_reword.png?raw=true\" style=\"width: 300px; height: 200px;\"/\u003e\n\n# 快速开始\n\n## 准备\n\n- JDK1.8+\n\n- Maven 3.x+\n\n## Maven 引入\n\n```xml\n\u003cdependency\u003e\n    \u003cgroupId\u003ecom.github.houbb\u003c/groupId\u003e\n    \u003cartifactId\u003esensitive-word\u003c/artifactId\u003e\n    \u003cversion\u003e0.26.0\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\n## 核心方法\n\n`SensitiveWordHelper` 作为敏感词的工具类，核心方法如下：\n\n注意：`SensitiveWordHelper` 提供的都是默认配置。如果你希望进行灵活的自定义配置，可参考 [引导类特性配置](https://github.com/houbb/sensitive-word/?tab=readme-ov-file#%E5%BC%95%E5%AF%BC%E7%B1%BB%E7%89%B9%E6%80%A7%E9%85%8D%E7%BD%AE)\n\n| 方法                                     | 参数                       | 返回值    | 说明           |\n|:---------------------------------------|:-------------------------|:-------|:-------------|\n| contains(String)                       | 待验证的字符串                  | 布尔值    | 验证字符串是否包含敏感词 |\n| replace(String, ISensitiveWordReplace) | 使用指定的替换策略替换敏感词           | 字符串    | 返回脱敏后的字符串    |\n| replace(String, char)                  | 使用指定的 char 替换敏感词         | 字符串    | 返回脱敏后的字符串    |\n| replace(String)                        | 使用 `*` 替换敏感词             | 字符串    | 返回脱敏后的字符串    |\n| findAll(String)                        | 待验证的字符串                  | 字符串列表  | 返回字符串中所有敏感词  |\n| findFirst(String)                      | 待验证的字符串                  | 字符串    | 返回字符串中第一个敏感词 |\n| findAll(String, IWordResultHandler)    | IWordResultHandler 结果处理类 | 字符串列表  | 返回字符串中所有敏感词  |\n| findFirst(String, IWordResultHandler)  | IWordResultHandler 结果处理类 | 字符串    | 返回字符串中第一个敏感词 |\n| tags(String)       | 获取敏感词的标签                 | 敏感词字符串 | 返回敏感词的标签列表   |\n\n\n\n### 判断是否包含敏感词\n\n```java\nfinal String text = \"五星红旗迎风飘扬，毛主席的画像屹立在天安门前。\";\n\nAssert.assertTrue(SensitiveWordHelper.contains(text));\n```\n\n### 返回第一个敏感词\n\n```java\nfinal String text = \"五星红旗迎风飘扬，毛主席的画像屹立在天安门前。\";\n\nString word = SensitiveWordHelper.findFirst(text);\nAssert.assertEquals(\"五星红旗\", word);\n```\n\nSensitiveWordHelper.findFirst(text) 等价于：\n\n```java\nString word = SensitiveWordHelper.findFirst(text, WordResultHandlers.word());\n```\n\n### 返回所有敏感词\n\n```java\nfinal String text = \"五星红旗迎风飘扬，毛主席的画像屹立在天安门前。\";\n\nList\u003cString\u003e wordList = SensitiveWordHelper.findAll(text);\nAssert.assertEquals(\"[五星红旗, 毛主席, 天安门]\", wordList.toString());\n```\n\n返回所有敏感词用法上类似于 SensitiveWordHelper.findFirst()，同样也支持指定结果处理类。\n\nSensitiveWordHelper.findAll(text) 等价于：\n\n```java\nList\u003cString\u003e wordList = SensitiveWordHelper.findAll(text, WordResultHandlers.word());\n```\n\nWordResultHandlers.raw() 可以保留对应的下标信息、类别信息：\n\n```java\nfinal String text = \"五星红旗迎风飘扬，毛主席的画像屹立在天安门前。\";\n\n// 默认敏感词标签为空\nList\u003cWordTagsDto\u003e wordList1 = SensitiveWordHelper.findAll(text, WordResultHandlers.wordTags());\nAssert.assertEquals(\"[WordTagsDto{word='五星红旗', tags=[]}, WordTagsDto{word='毛主席', tags=[]}, WordTagsDto{word='天安门', tags=[]}]\", wordList1.toString());\n```\n\n### 默认的替换策略\n\n```java\nfinal String text = \"五星红旗迎风飘扬，毛主席的画像屹立在天安门前。\";\nString result = SensitiveWordHelper.replace(text);\nAssert.assertEquals(\"****迎风飘扬，***的画像屹立在***前。\", result);\n```\n\n### 指定替换的内容\n\n```java\nfinal String text = \"五星红旗迎风飘扬，毛主席的画像屹立在天安门前。\";\nString result = SensitiveWordHelper.replace(text, '0');\nAssert.assertEquals(\"0000迎风飘扬，000的画像屹立在000前。\", result);\n```\n\n### 自定义替换策略\n\nV0.2.0 支持该特性。\n\n场景说明：有时候我们希望不同的敏感词有不同的替换结果。比如【游戏】替换为【电子竞技】，【失业】替换为【灵活就业】。\n\n诚然，提前使用字符串的正则替换也可以，不过性能一般。\n\n使用例子：\n\n```java\n/**\n * 自定替换策略\n * @since 0.2.0\n */\n@Test\npublic void defineReplaceTest() {\n    final String text = \"五星红旗迎风飘扬，毛主席的画像屹立在天安门前。\";\n\n    ISensitiveWordReplace replace = new MySensitiveWordReplace();\n    String result = SensitiveWordHelper.replace(text, replace);\n\n    Assert.assertEquals(\"国家旗帜迎风飘扬，教员的画像屹立在***前。\", result);\n}\n```\n\n其中 `MySensitiveWordReplace` 是我们自定义的替换策略，实现如下：\n\n```java\npublic class MyWordReplace implements IWordReplace {\n\n    @Override\n    public void replace(StringBuilder stringBuilder, final char[] rawChars, IWordResult wordResult, IWordContext wordContext) {\n        String sensitiveWord = InnerWordCharUtils.getString(rawChars, wordResult);\n        // 自定义不同的敏感词替换策略，可以从数据库等地方读取\n        if(\"五星红旗\".equals(sensitiveWord)) {\n            stringBuilder.append(\"国家旗帜\");\n        } else if(\"毛主席\".equals(sensitiveWord)) {\n            stringBuilder.append(\"教员\");\n        } else {\n            // 其他默认使用 * 代替\n            int wordLength = wordResult.endIndex() - wordResult.startIndex();\n            for(int i = 0; i \u003c wordLength; i++) {\n                stringBuilder.append('*');\n            }\n        }\n    }\n\n}\n```\n\n我们针对其中的部分词做固定映射处理，其他的默认转换为 `*`。\n\n## IWordResultHandler 结果处理类\n\nIWordResultHandler 可以对敏感词的结果进行处理，允许用户自定义。\n\n内置实现见 `WordResultHandlers` 工具类：\n\n- WordResultHandlers.word()\n\n只保留敏感词单词本身。\n\n- WordResultHandlers.raw()\n\n保留敏感词相关信息，包含敏感词的开始和结束下标。\n\n- WordResultHandlers.wordTags()\n\n同时保留单词，和对应的词标签信息。\n\n### 使用实例\n\n所有测试案例参见 [SensitiveWordHelperTest](https://github.com/houbb/sensitive-word/blob/master/src/test/java/com/github/houbb/sensitive/word/core/SensitiveWordHelperTest.java)\n\n1）基本例子\n\n```java\nfinal String text = \"五星红旗迎风飘扬，毛主席的画像屹立在天安门前。\";\n\nList\u003cString\u003e wordList = SensitiveWordHelper.findAll(text);\nAssert.assertEquals(\"[五星红旗, 毛主席, 天安门]\", wordList.toString());\nList\u003cString\u003e wordList2 = SensitiveWordHelper.findAll(text, WordResultHandlers.word());\nAssert.assertEquals(\"[五星红旗, 毛主席, 天安门]\", wordList2.toString());\n\nList\u003cIWordResult\u003e wordList3 = SensitiveWordHelper.findAll(text, WordResultHandlers.raw());\nAssert.assertEquals(\"[WordResult{startIndex=0, endIndex=4}, WordResult{startIndex=9, endIndex=12}, WordResult{startIndex=18, endIndex=21}]\", wordList3.toString());\n```\n\n2) wordTags 例子\n\n我们在 `dict_tag_test.txt` 文件中指定对应词的标签信息。\n\n```java\nfinal String text = \"五星红旗迎风飘扬，毛主席的画像屹立在天安门前。\";\n\n// 默认敏感词标签为空\nList\u003cWordTagsDto\u003e wordList1 = SensitiveWordHelper.findAll(text, WordResultHandlers.wordTags());\nAssert.assertEquals(\"[WordTagsDto{word='五星红旗', tags=[]}, WordTagsDto{word='毛主席', tags=[]}, WordTagsDto{word='天安门', tags=[]}]\", wordList1.toString());\n\nList\u003cWordTagsDto\u003e wordList2 = SensitiveWordBs.newInstance()\n        .wordTag(WordTags.file(\"dict_tag_test.txt\"))\n        .init()\n        .findAll(text, WordResultHandlers.wordTags());\nAssert.assertEquals(\"[WordTagsDto{word='五星红旗', tags=[政治, 国家]}, WordTagsDto{word='毛主席', tags=[政治, 伟人, 国家]}, WordTagsDto{word='天安门', tags=[政治, 国家, 地址]}]\", wordList2.toString());\n```\n\n# 更多特性\n\n后续的诸多特性，主要是针对各种针对各种情况的处理，尽可能的提升敏感词命中率。\n\n这是一场漫长的攻防之战。\n\n## 样式处理\n\n### 忽略大小写\n\n```java\nfinal String text = \"fuCK the bad words.\";\n\nString word = SensitiveWordHelper.findFirst(text);\nAssert.assertEquals(\"fuCK\", word);\n```\n\n### 忽略半角圆角\n\n```java\nfinal String text = \"ｆｕｃｋ the bad words.\";\n\nString word = SensitiveWordHelper.findFirst(text);\nAssert.assertEquals(\"ｆｕｃｋ\", word);\n```\n\n### 忽略数字的写法\n\n这里实现了数字常见形式的转换。\n\n```java\nfinal String text = \"这个是我的微信：9⓿二肆⁹₈③⑸⒋➃㈤㊄\";\n\nList\u003cString\u003e wordList = SensitiveWordBs.newInstance().enableNumCheck(true).init().findAll(text);\nAssert.assertEquals(\"[9⓿二肆⁹₈③⑸⒋➃㈤㊄]\", wordList.toString());\n```\n\n### 忽略繁简体\n\n```java\nfinal String text = \"我爱我的祖国和五星紅旗。\";\n\nList\u003cString\u003e wordList = SensitiveWordHelper.findAll(text);\nAssert.assertEquals(\"[五星紅旗]\", wordList.toString());\n```\n\n### 忽略英文的书写格式\n\n```java\nfinal String text = \"Ⓕⓤc⒦ the bad words\";\n\nList\u003cString\u003e wordList = SensitiveWordHelper.findAll(text);\nAssert.assertEquals(\"[Ⓕⓤc⒦]\", wordList.toString());\n```\n\n### 忽略重复词\n\n```java\nfinal String text = \"ⒻⒻⒻfⓤuⓤ⒰cⓒ⒦ the bad words\";\n\nList\u003cString\u003e wordList = SensitiveWordBs.newInstance()\n        .ignoreRepeat(true)\n        .init()\n        .findAll(text);\nAssert.assertEquals(\"[ⒻⒻⒻfⓤuⓤ⒰cⓒ⒦]\", wordList.toString());\n```\n\n## 更多检测策略\n\n### 说明\n\nv0.25.0 目前的几个策略，也支持用户引导类自定义。所有的策略都是接口，支持用户自定义实现。\n\n| 序号 | 方法                   | 说明                                         | 默认值   |\n|:---|:---------------------|:-------------------------------------------|:------|\n| 16 | wordCheckNum          | 数字检测策略(v0.25.0开始支持)                        | `WordChecks.num()`   |\n| 17 | wordCheckEmail          | 邮箱检测策略(v0.25.0开始支持)                        | `WordChecks.email()`   |\n| 18 | wordCheckUrl          | URL检测策略(v0.25.0开始支持)，内置还是实现了 `urlNoPrefix()` | `(WordChecks.url()`   |\n| 19 | wordCheckIpv4          | ipv4检测策略(v0.25.0开始支持)                      | `WordChecks.ipv4()`   |\n| 20 | wordCheckWord          | 敏感词检测策略(v0.25.0开始支持)                       | `WordChecks.word()`   |\n\n内置实现：\n\na) `WordChecks.urlNoPrefix()` 作为 url 的额外实现，可以不需要 `https://` 和 `http://` 前缀。 \n\n### 邮箱检测\n\n邮箱等个人信息，默认未启用。\n\n```java\nfinal String text = \"楼主好人，邮箱 sensitiveword@xx.com\";\nList\u003cString\u003e wordList = SensitiveWordBs.newInstance().enableEmailCheck(true).init().findAll(text);\nAssert.assertEquals(\"[sensitiveword@xx.com]\", wordList.toString());\n```\n\n### 连续数字检测\n\n一般用于过滤手机号/QQ等广告信息，默认未启用。\n\nV0.2.1 之后，支持通过 `numCheckLen(长度)` 自定义检测的长度。\n\n```java\nfinal String text = \"你懂得：12345678\";\n\n// 默认检测 8 位\nList\u003cString\u003e wordList = SensitiveWordBs.newInstance()\n.enableNumCheck(true)\n.init().findAll(text);\nAssert.assertEquals(\"[12345678]\", wordList.toString());\n\n// 指定数字的长度，避免误杀\nList\u003cString\u003e wordList2 = SensitiveWordBs.newInstance()\n.enableNumCheck(true)\n.numCheckLen(9)\n.init()\n.findAll(text);\nAssert.assertEquals(\"[]\", wordList2.toString());\n```\n\n### 网址检测\n\n用于过滤常见的网址信息，默认未启用。\n\nv0.18.0 优化 URL 检测，更加严格，降低误判率\n\n```java\nfinal String text = \"点击链接 https://www.baidu.com 查看答案\";\nfinal SensitiveWordBs sensitiveWordBs = SensitiveWordBs.newInstance().enableUrlCheck(true).init();\nList\u003cString\u003e wordList = sensitiveWordBs.findAll(text);\nAssert.assertEquals(\"[https://www.baidu.com]\", wordList.toString());\nAssert.assertEquals(\"点击链接 ********************* 查看答案\", sensitiveWordBs.replace(text));\n```\n\nv0.25.0 内置支持不需要 http 协议的前缀检测：\n\n```java\nfinal String text = \"点击链接 https://www.baidu.com 查看答案，当然也可以是 baidu.com、www.baidu.com\";\n\nfinal SensitiveWordBs sensitiveWordBs = SensitiveWordBs.newInstance()\n        .enableUrlCheck(true) // 启用URL检测\n        .wordCheckUrl(WordChecks.urlNoPrefix()) //指定检测的方式\n        .init();\nList\u003cString\u003e wordList = sensitiveWordBs.findAll(text);\nAssert.assertEquals(\"[www.baidu.com, baidu.com, www.baidu.com]\", wordList.toString());\n\nAssert.assertEquals(\"点击链接 https://************* 查看答案，当然也可以是 *********、*************\", sensitiveWordBs.replace(text));\n```\n\n### IPV4 检测\n\nv0.17.0 支持\n\n避免用户通过 ip 绕过网址检测等，默认未启用。\n\n```java\nfinal String text = \"个人网站，如果网址打不开可以访问 127.0.0.1。\";\nfinal SensitiveWordBs sensitiveWordBs = SensitiveWordBs.newInstance().enableIpv4Check(true).init();\nList\u003cString\u003e wordList = sensitiveWordBs.findAll(text);\nAssert.assertEquals(\"[127.0.0.1]\", wordList.toString());\n```\n\n# 引导类特性配置\n\n## 说明\n\n上面的特性默认都是开启的，有时业务需要灵活定义相关的配置特性。\n\n所以 v0.0.14 开放了属性配置。\n\n## 配置方法\n\n为了让使用更加优雅，统一使用 fluent-api 的方式定义。\n\n用户可以使用 `SensitiveWordBs` 进行如下定义：\n\n注意：配置后，要使用我们新定义的 `SensitiveWordBs` 的对象，而不是以前的工具方法。工具方法配置都是默认的。\n\n```java\nSensitiveWordBs wordBs = SensitiveWordBs.newInstance()\n        .ignoreCase(true)\n        .ignoreWidth(true)\n        .ignoreNumStyle(true)\n        .ignoreChineseStyle(true)\n        .ignoreEnglishStyle(true)\n        .ignoreRepeat(false)\n        .enableNumCheck(false)\n        .enableEmailCheck(false)\n        .enableUrlCheck(false)\n        .enableIpv4Check(false)\n        .enableWordCheck(true)\n        .wordFailFast(true)\n        .wordCheckNum(WordChecks.num())\n        .wordCheckEmail(WordChecks.email())\n        .wordCheckUrl(WordChecks.url())\n        .wordCheckIpv4(WordChecks.ipv4())\n        .wordCheckWord(WordChecks.word())\n        .numCheckLen(8)\n        .wordTag(WordTags.none())\n        .charIgnore(SensitiveWordCharIgnores.defaults())\n        .wordResultCondition(WordResultConditions.alwaysTrue())\n        .init();\n\nfinal String text = \"五星红旗迎风飘扬，毛主席的画像屹立在天安门前。\";\nAssert.assertTrue(wordBs.contains(text));\n```\n## 配置说明\n\n其中各项配置的说明如下：\n\n| 序号 | 方法                  | 说明                           | 默认值                       |\n|:---|:--------------------|:-----------------------------|:--------------------------|\n| 1  | ignoreCase          | 忽略大小写                        | true                      |\n| 2  | ignoreWidth         | 忽略半角圆角                       | true                      |\n| 3  | ignoreNumStyle      | 忽略数字的写法                      | true                      |\n| 4  | ignoreChineseStyle  | 忽略中文的书写格式                    | true                      |\n| 5  | ignoreEnglishStyle  | 忽略英文的书写格式                    | true                      |\n| 6  | ignoreRepeat        | 忽略重复词                        | false                     |\n| 7  | enableNumCheck      | 是否启用数字检测。                    | false                     |\n| 8  | enableEmailCheck    | 是有启用邮箱检测                     | false                     |\n| 9  | enableUrlCheck      | 是否启用链接检测                     | false                     |\n| 10 | enableIpv4Check     | 是否启用IPv4检测                   | false                     |\n| 11 | enableWordCheck     | 是否启用敏感单词检测                   | true                      |\n| 12 | numCheckLen         | 数字检测，自定义指定长度。                | 8                         |\n| 13 | wordTag             | 词对应的标签                       | none                      |\n| 14 | charIgnore          | 忽略的字符                        | none                      |\n| 15 | wordResultCondition | 针对匹配的敏感词额外加工，比如可以限制英文单词必须全匹配 | 恒为真                       |\n| 16 | wordCheckNum        | 数字检测策略(v0.25.0开始支持)          | `WordChecks.num()`        |\n| 17 | wordCheckEmail      | 邮箱检测策略(v0.25.0开始支持)          | `WordChecks.email()`      |\n| 18 | wordCheckUrl        | URL检测策略(v0.25.0开始支持)         | `(WordChecks.url()`       |\n| 19 | wordCheckIpv4       | ipv4检测策略(v0.25.0开始支持)        | `WordChecks.ipv4()`       |\n| 20 | wordCheckWord       | 敏感词检测策略(v0.25.0开始支持)         | `WordChecks.word()`       |\n| 21 | wordReplace         | 替换策略                         | `WordReplaces.defaults()` |\n| 22 | wordFailFast        | 敏感词匹配模式是否快速返回                | true                      |\n\n\n## wordFailFast 敏感词匹配快速失败模式\n\n### 场景说明\n\nv0.26.0 开始支持。\n\n默认情况下，wordFailFast=true。匹配时快速返回，性能较好。\n\n但是有时候不太符合人的直觉。\n\n默认如下：\n\n```java\nSensitiveWordBs bs2 = SensitiveWordBs.newInstance()\n        .wordDeny(new IWordDeny() {\n            @Override\n            public List\u003cString\u003e deny() {\n                return Arrays.asList(\"我的世界\", \"我的\");\n            }\n        }).init();\nString text = \"他的世界它的世界和她的世界都不是我的也不是我的世界\";\nList\u003cString\u003e textList2 = bs2.findAll(text);\nAssert.assertEquals(Arrays.asList(\"我的\", \"我的\"), textList2);\n```\n\n此时会优先匹配短的【我的】，导致后面的【我的世界】被跳过。\n\n### failOver 模式\n\n尽可能找到最长的匹配词。\n\n```java\nSensitiveWordBs bs = SensitiveWordBs.newInstance()\n        .wordFailFast(false)\n        .wordDeny(new IWordDeny() {\n            @Override\n            public List\u003cString\u003e deny() {\n                return Arrays.asList(\"我的世界\", \"我的\");\n            }\n        }).init();\n\nString text = \"他的世界它的世界和她的世界都不是我的也不是我的世界\";\nList\u003cString\u003e textList = bs.findAll(text);\nAssert.assertEquals(Arrays.asList(\"我的\", \"我的世界\"), textList);\n```\n\n## 内存资源的释放\n\nv0.16.1 开始支持，有时候我们需要释放内存，可以如下：\n\n\u003e [关于内存回收问题](https://github.com/houbb/sensitive-word/issues/53)\n\n```java\nSensitiveWordBs wordBs = SensitiveWordBs.newInstance()\n                .init();\n// 后续因为一些原因移除了对应信息，希望释放内存。\nwordBs.destroy();\n```\n\n## 针对单个黑名单词的新增/删除，无需全量初始化\n\n使用场景：在初始化之后，我们希望针对单个词的新增/删除，而不是完全重新初始化。这个特性就是为此准备的。\n\n支持版本：v0.19.0\n\n### 方法说明 \n\n`addWord(word)` 新增敏感词，支持单个词/集合\n\n`removeWord(word)` 删除敏感词，支持单个词/集合\n\n### 实例代码：\n\n```java\nfinal String text = \"测试一下新增敏感词，验证一下删除和新增对不对\";\n\nSensitiveWordBs sensitiveWordBs =\nSensitiveWordBs.newInstance()\n        .wordAllow(WordAllows.empty())\n        .wordDeny(WordDenys.empty())\n        .init();\n\n// 当前\nAssert.assertEquals(\"[]\", sensitiveWordBs.findAll(text).toString());\n\n// 新增单个\nsensitiveWordBs.addWord(\"测试\");\nsensitiveWordBs.addWord(\"新增\");\nAssert.assertEquals(\"[测试, 新增, 新增]\", sensitiveWordBs.findAll(text).toString());\n\n// 删除单个\nsensitiveWordBs.removeWord(\"新增\");\nAssert.assertEquals(\"[测试]\", sensitiveWordBs.findAll(text).toString());\nsensitiveWordBs.removeWord(\"测试\");\nAssert.assertEquals(\"[]\", sensitiveWordBs.findAll(text).toString());\n\n// 新增集合\nsensitiveWordBs.addWord(Arrays.asList(\"新增\", \"测试\"));\nAssert.assertEquals(\"[测试, 新增, 新增]\", sensitiveWordBs.findAll(text).toString());\n// 删除集合\nsensitiveWordBs.removeWord(Arrays.asList(\"新增\", \"测试\"));\nAssert.assertEquals(\"[]\", sensitiveWordBs.findAll(text).toString());\n\n// 新增数组\nsensitiveWordBs.addWord(\"新增\", \"测试\");\nAssert.assertEquals(\"[测试, 新增, 新增]\", sensitiveWordBs.findAll(text).toString());\n// 删除集合\nsensitiveWordBs.removeWord(\"新增\", \"测试\");\nAssert.assertEquals(\"[]\", sensitiveWordBs.findAll(text).toString());\n```\n\n## 针对单个白名单词的新增/删除，无需全量初始化\n\n使用场景：在初始化之后，我们希望针对单个词的新增/删除，而不是完全重新初始化。这个特性就是为此准备的。\n\n支持版本：v0.21.0\n\n### 方法说明\n\n`addWordAllow(word)` 新增白名单，支持单个词/集合\n\n`removeWordAllow(word)` 删除白名单，支持单个词/集合\n\n### 使用例子\n\n```java\n        final String text = \"测试一下新增敏感词白名单，验证一下删除和新增对不对\";\n\n        SensitiveWordBs sensitiveWordBs =\n                SensitiveWordBs.newInstance()\n                        .wordAllow(WordAllows.empty())\n                        .wordDeny(new IWordDeny() {\n                            @Override\n                            public List\u003cString\u003e deny() {\n                                return Arrays.asList(\"测试\", \"新增\");\n                            }\n                        })\n                        .init();\n\n        // 当前\n        Assert.assertEquals(\"[测试, 新增, 新增]\", sensitiveWordBs.findAll(text).toString());\n\n        // 新增单个\n        sensitiveWordBs.addWordAllow(\"测试\");\n        sensitiveWordBs.addWordAllow(\"新增\");\n        Assert.assertEquals(\"[]\", sensitiveWordBs.findAll(text).toString());\n\n        // 删除单个\n        sensitiveWordBs.removeWordAllow(\"测试\");\n        Assert.assertEquals(\"[测试]\", sensitiveWordBs.findAll(text).toString());\n        sensitiveWordBs.removeWordAllow(\"新增\");\n        Assert.assertEquals(\"[测试, 新增, 新增]\", sensitiveWordBs.findAll(text).toString());\n\n        // 新增集合\n        sensitiveWordBs.addWordAllow(Arrays.asList(\"新增\", \"测试\"));\n        Assert.assertEquals(\"[]\", sensitiveWordBs.findAll(text).toString());\n        // 删除集合\n        sensitiveWordBs.removeWordAllow(Arrays.asList(\"新增\", \"测试\"));\n        Assert.assertEquals(\"[测试, 新增, 新增]\", sensitiveWordBs.findAll(text).toString());\n\n        // 新增数组\n        sensitiveWordBs.addWordAllow(\"新增\", \"测试\");\n        Assert.assertEquals(\"[]\", sensitiveWordBs.findAll(text).toString());\n        // 删除集合\n        sensitiveWordBs.removeWordAllow(\"新增\", \"测试\");\n        Assert.assertEquals(\"[测试, 新增, 新增]\", sensitiveWordBs.findAll(text).toString());\n```\n\n## 全量初始化\n\n### 说明\n\n此方式**已废弃**，建议使用上面增量添加的方式，避免全量加载。为了兼容，此方式依然保留。\n\n使用方式：在调用 `sensitiveWordBs.init()` 的时候，根据 IWordDeny+IWordAllow 重新构建敏感词库。 因为初始化可能耗时较长（秒级别），所有优化为 init 未完成时**不影响旧的词库功能，完成后以新的为准**。\n\n### 例子\n\n```java\n@Component\npublic class SensitiveWordService {\n\n    @Autowired\n    private SensitiveWordBs sensitiveWordBs;\n\n    /**\n     * 更新词库\n     *\n     * 每次数据库的信息发生变化之后，首先调用更新数据库敏感词库的方法。\n     * 如果需要生效，则调用这个方法。\n     *\n     * 说明：重新初始化不影响旧的方法使用。初始化完成后，会以新的为准。\n     */\n    public void refresh() {\n        // 每次数据库的信息发生变化之后，首先调用更新数据库敏感词库的方法，然后调用这个方法。\n        sensitiveWordBs.init();\n    }\n\n}\n```\n\n如上，你可以在数据库词库发生变更时，需要词库生效，主动触发一次初始化 `sensitiveWordBs.init();`。\n\n其他使用保持不变，无需重启应用。\n\n# wordResultCondition-针对匹配词进一步判断\n\n## 说明\n\n支持版本：v0.13.0\n\n有时候我们可能希望对匹配的敏感词进一步限制，比如虽然我们定义了【av】作为敏感词，但是不希望【have】被匹配。\n\n就可以自定义实现 wordResultCondition 接口，实现自己的策略。\n\n系统内置的策略在 `WordResultConditions#alwaysTrue()` 恒为真，`WordResultConditions#englishWordMatch()` 则要求英文必须全词匹配。\n\n## 内置策略\n\nWordResultConditions 工具类可以获取匹配策略\n\n| 实现                                         | 说明                  | 支持版本    |\n|:-------------------------------------------|:--------------------|:--------|\n| alwaysTrue                                 | 恒为真                 |         |\n| englishWordMatch                           | 英文单词全词匹配            | v0.13.0 |\n| englishWordNumMatch                        | 英文单词/数字全词匹配         | v0.20.0 |\n| wordTags                                   | 满足特定标签的，比如只关注【广告】标签 | v0.23.0 |\n| chains(IWordResultCondition ...conditions) | 支持指定多个条件，同时满足       | v0.23.0 |\n\n## 使用例子\n\n原始的默认情况：\n\n```java\nfinal String text = \"I have a nice day。\";\n\nList\u003cString\u003e wordList = SensitiveWordBs.newInstance()\n        .wordDeny(new IWordDeny() {\n            @Override\n            public List\u003cString\u003e deny() {\n                return Collections.singletonList(\"av\");\n            }\n        })\n        .wordResultCondition(WordResultConditions.alwaysTrue())\n        .init()\n        .findAll(text);\nAssert.assertEquals(\"[av]\", wordList.toString());\n```\n\n我们可以指定为英文必须全词匹配。\n\n```java\nfinal String text = \"I have a nice day。\";\n\nList\u003cString\u003e wordList = SensitiveWordBs.newInstance()\n        .wordDeny(new IWordDeny() {\n            @Override\n            public List\u003cString\u003e deny() {\n                return Collections.singletonList(\"av\");\n            }\n        })\n        .wordResultCondition(WordResultConditions.englishWordMatch())\n        .init()\n        .findAll(text);\nAssert.assertEquals(\"[]\", wordList.toString());\n```\n\n当然可以根据需要实现更加复杂的策略。\n\n## wordTags 单词标签\n\n支持版本： `v0.23.0`\n\n我们可以只返回隶属于某一种标签的敏感词。\n\n我们指定了两个敏感词：商品、AV\n\nMyWordTag 是我们定义的一个敏感词标签实现：\n\n```java\n/**\n * 自定义单词标签\n * @since 0.23.0\n */\npublic class MyWordTag extends AbstractWordTag {\n\n    private static Map\u003cString, Set\u003cString\u003e\u003e dataMap;\n\n    static {\n        dataMap = new HashMap\u003c\u003e();\n        dataMap.put(\"商品\", buildSet(\"广告\", \"中文\"));\n        dataMap.put(\"AV\", buildSet(\"色情\", \"单词\", \"英文\"));\n    }\n\n    private static Set\u003cString\u003e buildSet(String... tags) {\n        Set\u003cString\u003e set = new HashSet\u003c\u003e();\n        for(String tag : tags) {\n            set.add(tag);\n        }\n        return set;\n    }\n\n    @Override\n    protected Set\u003cString\u003e doGetTag(String word) {\n        return dataMap.get(word);\n    }\n\n}\n```\n\n测试用例如下，我们模拟了两个不同的实现类，每一个关注的单词标签不同。\n\n```java\n// 只关心SE情\nSensitiveWordBs sensitiveWordBsYellow = SensitiveWordBs.newInstance()\n        .wordDeny(new IWordDeny() {\n            @Override\n            public List\u003cString\u003e deny() {\n                return Arrays.asList(\"商品\", \"AV\");\n            }\n        })\n        .wordAllow(WordAllows.empty())\n        .wordTag(new MyWordTag())\n        .wordResultCondition(WordResultConditions.wordTags(Arrays.asList(\"色情\")))\n        .init();\n\n// 只关心广告\nSensitiveWordBs sensitiveWordBsAd = SensitiveWordBs.newInstance()\n        .wordDeny(new IWordDeny() {\n            @Override\n            public List\u003cString\u003e deny() {\n                return Arrays.asList(\"商品\", \"AV\");\n            }\n        })\n        .wordAllow(WordAllows.empty())\n        .wordTag(new MyWordTag())\n        .wordResultCondition(WordResultConditions.wordTags(Arrays.asList(\"广告\")))\n        .init();\n\nfinal String text = \"这些 AV 商品什么价格？\";\nAssert.assertEquals(\"[AV]\", sensitiveWordBsYellow.findAll(text).toString());\nAssert.assertEquals(\"[商品]\", sensitiveWordBsAd.findAll(text).toString());\n```\n\n# 忽略字符\n\n## 说明\n\n我们的敏感词一般都是比较连续的，比如【傻帽】\n\n那就有大聪明发现，可以在中间加一些字符，比如【傻!@#$帽】跳过检测，但是骂人等攻击力不减。\n\n那么，如何应对这些类似的场景呢？\n\n我们可以指定特殊字符的跳过集合，忽略掉这些无意义的字符即可。\n\nv0.11.0 开始支持\n\n## 例子\n\n其中 charIgnore 对应的字符策略，用户可以自行灵活定义。\n\n```java\nfinal String text = \"傻@冒，狗+东西\";\n\n//默认因为有特殊字符分割，无法识别\nList\u003cString\u003e wordList = SensitiveWordBs.newInstance().init().findAll(text);\nAssert.assertEquals(\"[]\", wordList.toString());\n\n// 指定忽略的字符策略，可自行实现。\nList\u003cString\u003e wordList2 = SensitiveWordBs.newInstance()\n        .charIgnore(SensitiveWordCharIgnores.specialChars())\n        .init()\n        .findAll(text);\n\nAssert.assertEquals(\"[傻@冒, 狗+东西]\", wordList2.toString());\n```\n\n# 敏感词标签\n\n## 说明\n\n有时候我们希望对敏感词加一个分类标签：比如社情、暴/力等等。\n\n这样后续可以按照标签等进行更多特性操作，比如只处理某一类的标签。\n\n支持版本：v0.10.0\n\n主要特性支持版本：v0.24.0\n\n## 标签接口\n\n这里只是一个抽象的接口，用户可以自行定义实现。比如从数据库查询、文件读取、api 调用等。\n\n```java\npublic interface IWordTag {\n\n    /**\n     * 查询标签列表\n     * @param word 脏词\n     * @return 结果\n     */\n    Set\u003cString\u003e getTag(String word);\n\n}\n```\n\n## 内置实现\n\n### 方法列表\n\n为了方便大部分情况使用，内置实现一些场景策略在 `WordTags` 类中\n\n| 实现方法                                                              | 说明                   | 备注         |\n|:------------------------------------------------------------------|:---------------------|:-----------|\n| none()                                                            | 空实现                  | v0.10.0 支持 |\n| file(String filePath)                                             | 指定文件路径               | v0.10.0 支持 |\n| file(String filePath, String wordSplit, String tagSplit)          | 指定文件路径，以及单词分隔符、标签分隔符 | v0.10.0 支持 |\n| map(final Map\u003cString, Set\u003cString\u003e\u003e wordTagMap)                    | 根据 map初始化            | v0.24.0 支持 |\n| lines(Collection\u003cString\u003e lines)                                   | 字符串列表                | v0.24.0 支持 |\n| lines(Collection\u003cString\u003e lines, String wordSplit, String tagSpli) | 字符串列表，以及单词分隔符、标签分隔符  | v0.24.0 支持 |\n| system()                                                          | 系件文件内置实现，整合网络分类      | v0.24.0 支持 |\n| defaults()                                                        | 默认策略，目前为 system      | v0.24.0 支持 |\n| chains(IWordTag... others)                 | 链式方法，支持用户整合实现多个策略    | v0.24.0 支持 |\n\n### 格式约定\n\n敏感词标签的格式我们默认约定如下 `敏感词 tag1,tag2`，代表这 `敏感词` 的标签为 tag1 和 tag2\n\n比如 \n\n```\n五星红旗 政治,国家\n```\n\n所有的文件行内容，和指定的字符串行内容也建议用这种方式。如果不满足，自定义实现即可。\n\n## 系统内置实现（默认效果）\n\nv0.24.0 版本开始，默认的单词标签为 `WordTags.system()`。\n\n说明：目前数据统计自网络，存在不少疏漏。也欢迎大家指正，持续改进中...\n\n```java\nSensitiveWordBs sensitiveWordBs = SensitiveWordBs.newInstance()\n.wordTag(WordTags.system())\n.init();\nSet\u003cString\u003e tagSet = sensitiveWordBs.tags(\"博彩\");\nAssert.assertEquals(\"[3]\", tagSet.toString());\n```\n\n这里为了压缩大小优化，对应的类别用数字表示。\n\n数字的含义列表如下：\n\n```\n0 政治\n1 毒品\n2 色情\n3 赌博\n4 违法\n```\n\n## 文件入门例子\n\n这里以文件为例子，演示一下如何使用。\n\n```java\nfinal String path = \"~\\\\test\\\\resources\\\\dict_tag_test.txt\";\n\n// 演示默认方法\nIWordTag wordTag = WordTags.file(path);\nSensitiveWordBs sensitiveWordBs = SensitiveWordBs.newInstance()\n        .wordTag(wordTag)\n        .init();\n\nSet\u003cString\u003e tagSet = sensitiveWordBs.tags(\"零售\");\n        Assert.assertEquals(\"[广告, 网络]\", tagSet.toString());\n\n\n// 演示指定分隔符\nIWordTag wordTag2 = WordTags.file(path, \" \", \",\");\nSensitiveWordBs sensitiveWordBs2 = SensitiveWordBs.newInstance()\n        .wordTag(wordTag2)\n        .init();\nSet\u003cString\u003e tagSet2 = sensitiveWordBs2.tags(\"零售\");\n        Assert.assertEquals(\"[广告, 网络]\", tagSet2.toString());\n```\n\n其中 `dict_tag_test.txt` 我们自定义的内容如下：\n\n```\n零售 广告,网络\n```\n\n## 单词标签和敏感词发现的联动\n\n我们在获取敏感词的时候，是可以设置对应的结果处理策略，从而获取对应的敏感词标签信息\n\n```java\n// 自定义测试标签类\nIWordTag wordTag = WordTags.lines(Arrays.asList(\"天安门 政治,国家,地址\"));\n\n// 指定初始化\nSensitiveWordBs sensitiveWordBs = SensitiveWordBs.newInstance()\n        .wordTag(wordTag)\n        .init()\n        ;\n\nList\u003cWordTagsDto\u003e wordTagsDtoList1 = sensitiveWordBs.findAll(\"天安门\", WordResultHandlers.wordTags());\nAssert.assertEquals(\"[WordTagsDto{word='天安门', tags=[政治, 国家, 地址]}]\", wordTagsDtoList1.toString());\n```\n\n我们自定义了 `天安门` 关键词的标签，然后通过指定 findAll 的结果处理策略为 `WordResultHandlers.wordTags()`，就可以在获取敏感词的同时，获取对应的标签列表。\n\n# 动态加载（用户自定义）\n\n## 情景说明\n\n有时候我们希望将敏感词的加载设计成动态的，比如控台修改，然后可以实时生效。\n\nv0.0.13 支持了这种特性。\n\n## 接口说明\n\n为了实现这个特性，并且兼容以前的功能，我们定义了两个接口。\n\n### IWordDeny\n\n接口如下，可以自定义自己的实现。\n\n返回的列表，表示这个词是一个敏感词。\n\n```java\n/**\n * 拒绝出现的数据-返回的内容被当做是敏感词\n * @author binbin.hou\n * @since 0.0.13\n */\npublic interface IWordDeny {\n\n    /**\n     * 获取结果\n     * @return 结果\n     * @since 0.0.13\n     */\n    List\u003cString\u003e deny();\n\n}\n```\n\n比如：\n\n```java\npublic class MyWordDeny implements IWordDeny {\n\n    @Override\n    public List\u003cString\u003e deny() {\n        return Arrays.asList(\"我的自定义敏感词\");\n    }\n\n}\n```\n\n### IWordAllow\n\n接口如下，可以自定义自己的实现。\n\n返回的列表，表示这个词不是一个敏感词。\n\n```java\n/**\n * 允许的内容-返回的内容不被当做敏感词\n * @author binbin.hou\n * @since 0.0.13\n */\npublic interface IWordAllow {\n\n    /**\n     * 获取结果\n     * @return 结果\n     * @since 0.0.13\n     */\n    List\u003cString\u003e allow();\n\n}\n```\n\n如：\n\n```java\npublic class MyWordAllow implements IWordAllow {\n\n    @Override\n    public List\u003cString\u003e allow() {\n        return Arrays.asList(\"五星红旗\");\n    }\n\n}\n```\n\n## 配置使用\n\n**接口自定义之后，当然需要指定才能生效。**\n\n为了让使用更加优雅，我们设计了引导类 `SensitiveWordBs`。\n\n可以通过 wordDeny() 指定敏感词，wordAllow() 指定非敏感词，通过 init() 初始化敏感词字典。\n\n### 系统的默认配置\n\n```java\nSensitiveWordBs wordBs = SensitiveWordBs.newInstance()\n        .wordDeny(WordDenys.defaults())\n        .wordAllow(WordAllows.defaults())\n        .init();\n\nfinal String text = \"五星红旗迎风飘扬，毛主席的画像屹立在天安门前。\";\nAssert.assertTrue(wordBs.contains(text));\n```\n\n备注：init() 对于敏感词 DFA 的构建是比较耗时的，一般建议在应用初始化的时候**只初始化一次**。而不是重复初始化！\n\n### 指定自己的实现\n\n我们可以测试一下自定义的实现，如下:\n\n```java\nString text = \"这是一个测试，我的自定义敏感词。\";\n\nSensitiveWordBs wordBs = SensitiveWordBs.newInstance()\n        .wordDeny(new MyWordDeny())\n        .wordAllow(new MyWordAllow())\n        .init();\n\nAssert.assertEquals(\"[我的自定义敏感词]\", wordBs.findAll(text).toString());\n```\n\n这里只有 `我的自定义敏感词` 是敏感词，而 `测试` 不是敏感词。\n\n当然，这里是全部使用我们自定义的实现，一般建议使用系统的默认配置+自定义配置。\n\n可以使用下面的方式。\n\n### 同时配置多个\n\n- 多个敏感词\n\n`WordDenys.chains()` 方法，将多个实现合并为同一个 IWordDeny。\n\n- 多个白名单\n\n`WordAllows.chains()` 方法，将多个实现合并为同一个 IWordAllow。\n\n例子：\n\n```java\nString text = \"这是一个测试。我的自定义敏感词。\";\n\nIWordDeny wordDeny = WordDenys.chains(WordDenys.defaults(), new MyWordDeny());\nIWordAllow wordAllow = WordAllows.chains(WordAllows.defaults(), new MyWordAllow());\n\nSensitiveWordBs wordBs = SensitiveWordBs.newInstance()\n        .wordDeny(wordDeny)\n        .wordAllow(wordAllow)\n        .init();\n\nAssert.assertEquals(\"[我的自定义敏感词]\", wordBs.findAll(text).toString());\n```\n\n这里都是同时使用了系统默认配置，和自定义的配置。\n\n注意：**我们初始化了新的 wordBs，那么用新的 wordBs 去判断。而不是用以前的 `SensitiveWordHelper` 工具方法，工具方法配置是默认的！**\n\n# spring 整合\n\n## 背景\n\n实际使用中，比如可以在页面配置修改，然后实时生效。\n\n数据存储在数据库中，下面是一个伪代码的例子，可以参考 [SpringSensitiveWordConfig.java](https://github.com/houbb/sensitive-word/blob/master/src/test/java/com/github/houbb/sensitive/word/spring/SpringSensitiveWordConfig.java)\n\n要求，版本 v0.0.15 及其以上。\n\n## 自定义数据源\n\n简化伪代码如下，数据的源头为数据库。\n\nMyDdWordAllow 和 MyDdWordDeny 是基于数据库为源头的自定义实现类。\n\n```java\n@Configuration\npublic class SpringSensitiveWordConfig {\n\n    @Autowired\n    private MyDdWordAllow myDdWordAllow;\n\n    @Autowired\n    private MyDdWordDeny myDdWordDeny;\n\n    /**\n     * 初始化引导类\n     * @return 初始化引导类\n     * @since 1.0.0\n     */\n    @Bean\n    public SensitiveWordBs sensitiveWordBs() {\n        SensitiveWordBs sensitiveWordBs = SensitiveWordBs.newInstance()\n                .wordAllow(WordAllows.chains(WordAllows.defaults(), myDdWordAllow))\n                .wordDeny(myDdWordDeny)\n                // 各种其他配置\n                .init();\n\n        return sensitiveWordBs;\n    }\n\n}\n```\n\n敏感词库的初始化较为耗时，建议程序启动时做一次 init 初始化。\n\n# Benchmark\n\nV0.6.0 以后，添加对应的 benchmark 测试。\n\n\u003e [BenchmarkTimesTest](https://github.com/houbb/sensitive-word/blob/master/src/test/java/com/github/houbb/sensitive/word/benchmark/BenchmarkTimesTest.java)\n\n## 环境\n\n测试环境为普通的笔记本:\n\n```\n处理器\t12th Gen Intel(R) Core(TM) i7-1260P   2.10 GHz\n机带 RAM\t16.0 GB (15.7 GB 可用)\n系统类型\t64 位操作系统, 基于 x64 的处理器\n```\n\nps: 不同环境会有差异，但是比例基本稳定。\n\n## 测试效果记录\n\n测试数据：100+ 字符串，循环 10W 次。\n\n| 序号 | 场景               | 耗时 | 备注            |\n|:----|:-----------------|:----|:--------------|\n| 1 | 只做敏感词，无任何格式转换    | 1470ms，约 7.2W QPS | 追求极致性能，可以这样配置 |\n| 2 | 只做敏感词，支持全部格式转换  | 2744ms，约 3.7W QPS | 满足大部分场景       |\n\n# STAR\n\n[![Stargazers over time](https://starchart.cc/houbb/sensitive-word.svg)](https://starchart.cc/houbb/sensitive-word)\n\n# 后期 road-map\n\n- [x] 移除单个汉字的敏感词，在中国，要把词组当做一次词，降低误判率。\n\n- [x] 支持单个的敏感词变化？\n\nremove、add、edit?\n\n- [x] 敏感词标签接口支持\n\n- [x] 敏感词处理时标签支持\n\n- [x] wordData 的内存占用对比 + 优化\n\n- [x] 用户指定自定义的词组，同时允许指定词组的组合获取，更加灵活\n\nFormatCombine/CheckCombine/AllowDenyCombine 组合策略，允许用户自定义。\n\n- [ ] word check 策略的优化，统一遍历+转换\n\n- [ ] 添加 ThreadLocal 等性能优化\n\n# 拓展阅读\n\n# 敏感词系列\n\n[sensitive-word-admin 敏感词控台 v1.2.0 版本开源](https://mp.weixin.qq.com/s/7wSy0PuJLTudEo9gTY5s5w)\n\n[sensitive-word-admin v1.3.0 发布 如何支持分布式部署？](https://mp.weixin.qq.com/s/4wia8SlQQbLV5_OHplaWvg)\n\n[01-开源敏感词工具入门使用](https://houbb.github.io/2020/01/07/sensitive-word-00-overview)\n\n[02-如何实现一个敏感词工具？违禁词实现思路梳理](https://houbb.github.io/2020/01/07/sensitive-word-01-intro)\n\n[03-敏感词之 StopWord 停止词优化与特殊符号](https://houbb.github.io/2020/01/07/sensitive-word-02-stopword)\n\n[04-敏感词之字典瘦身](https://houbb.github.io/2020/01/07/sensitive-word-03-slim)\n\n[05-敏感词之 DFA 算法(Trie Tree 算法)详解](https://houbb.github.io/2020/01/07/sensitive-word-04-dfa)\n\n[06-敏感词(脏词) 如何忽略无意义的字符？达到更好的过滤效果](https://houbb.github.io/2020/01/07/sensitive-word-05-ignore-char)\n\n[v0.10.0-脏词分类标签初步支持](https://juejin.cn/post/7308782855941292058?searchId=20231209140414C082B3CCF1E7B2316EF9)\n\n[v0.11.0-敏感词新特性：忽略无意义的字符，词标签字典](https://mp.weixin.qq.com/s/m40ZnR6YF6WgPrArUSZ_0g)\n\n[v0.12.0-敏感词/脏词词标签能力进一步增强](https://mp.weixin.qq.com/s/-wa-if7uAy2jWsZC13C0cQ)\n\n[v0.13.0-敏感词特性版本发布 支持英文单词全词匹配](https://mp.weixin.qq.com/s/DXv5OUyOs0y2dAq8nFWJ9A)\n\n[v0.16.1-敏感词新特性之字典内存资源释放](https://mp.weixin.qq.com/s/zbeJR-OkWjxashtjiopnMA)\n\n[v0.19.0-敏感词新特性之敏感词单个编辑，不必重复初始化](https://houbb.github.io/2020/01/07/sensitive-word-10-v0.19.0-deny-word-edit)\n\n[v0.20.0 敏感词新特性之数字全部匹配，而不是部分匹配](https://houbb.github.io/2020/01/07/sensitive-word-11-v0.20.0-num-match)\n\n[v0.21.0 敏感词新特性之白名单支持单个编辑，修正白名单包含黑名单时的问题](https://houbb.github.io/2020/01/07/sensitive-word-12-v0.21.0-allow-word-edit)\n\n[v0.23.0 敏感词结果条件拓展，内置支持链式+单词标签](https://houbb.github.io/2020/01/07/sensitive-word-13-v0.23.0-result-condition-enhance)\n\n[v0.24.0 新特性支持标签分类，内置实现多种策略](https://houbb.github.io/2020/01/07/sensitive-word-13-v0.24.0-word-tag-impl)\n\n[v0.25.0 新特性之 wordCheck 策略支持用户自定义](https://houbb.github.io/2020/01/07/sensitive-word-14-v0.25.0-url-define)\n\n[v0.25.1 新特性之返回匹配词，修正 tags 标签](https://houbb.github.io/2020/01/07/sensitive-word-14-v0.25.1-tags-match)\n\n![wechat](https://img-blog.csdnimg.cn/63926529df364f09bcb203a8a9016854.png)\n\n# NLP 开源矩阵\n\n[pinyin 汉字转拼音](https://github.com/houbb/pinyin)\n\n[pinyin2hanzi 拼音转汉字](https://github.com/houbb/pinyin2hanzi)\n\n[segment 高性能中文分词](https://github.com/houbb/segment)\n\n[opencc4j 中文繁简体转换](https://github.com/houbb/opencc4j)\n\n[nlp-hanzi-similar 汉字相似度](https://github.com/houbb/nlp-hanzi-similar)\n\n[word-checker 拼写检测](https://github.com/houbb/word-checker)\n\n[sensitive-word 敏感词](https://github.com/houbb/sensitive-word)\n\n","funding_links":[],"categories":["Java","NLP语料和数据集","人工智能"],"sub_categories":["大语言对话模型及数据","自然语言处理"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhoubb%2Fsensitive-word","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhoubb%2Fsensitive-word","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhoubb%2Fsensitive-word/lists"}