使用方法
- 在「待处理文本」框里粘贴内容。默认示例是一段 91 个码点的中文,里面有连续叠写的「人人人」「天天天」,也有隔开的重复字,还有两组大小写不同的
AABBC与aabbc。 - 在「去重模式」里选一种:
全局去重把整段里第二次及以后出现的字符全部去掉;仅压缩连续重复只把紧挨着的相同字符并成一个。同一个示例,前者去掉 37 个码点,后者只去掉 8 个。 - 要把
A和a当成同一个字符就勾「忽略大小写」——比较时用小写形式当键,但保留首次出现的那个原形;要把空格、制表符、换行一起去重就勾「忽略空白与换行」;要保留所有数字就勾「忽略数字」。 - 「白名单」填不参与去重的字符,例如填
,。就保留每一个逗号和句号,最多 64 个字符。 - 点「开始去重」,或在文本框里按 Ctrl+Enter(macOS 按 Cmd+Enter)。结果区上方给出原文码点数、结果码点数、去掉数量,下方是字符频次对照表。
这个删重复字符工具能解决什么
从 PDF 或网页复制出来的词表、标签集常被重复粘贴,全局去重后按首次出现顺序留一份,顺序不乱,可以直接再贴回表格或数据库字段。
叠字、叠词、连打的分隔符(例如手写输入里连按出来的句号)用「仅压缩连续重复」处理最合适:它只合并相邻的,隔开的重复字一律不动,比全局去重保守得多。
要判断一段文本到底用了多少种字符,去重后的码点数就是字符集大小;下方频次表再补上每个字符出现几次、第一次出现在第几个码点,方便定位是哪一个字在反复出现。
给密码字典、验证码字符集做抽样预处理时,白名单能保住分隔符和必须保留的符号不被吃掉,避免把全部标点并成一个。
本页不去重「行」——按行去重请交给删除重复行;也不做全角半角合并——全角 A 与半角 A 是两个不同码点,合并属于另一类转换。想知道原文的码点总量,可以配合字符数统计工具核对。
原理与规则说明
核心是三个原生调用:Array.from(text) 按 Unicode 码点把字符串拆成数组,Set.prototype.has 与 Set.prototype.add 做常数时间查表,Array.prototype.join('') 拼回字符串。因为 Set 的迭代顺序就是插入顺序,遍历时「先查、未命中才插入」天然实现了保留首次出现。
为什么用 Set 而不用逐字符 indexOf:indexOf 是线性扫描,n 个字符最坏要比较 n(n−1)/2 次——实测 n=10 000 时是 49 995 000 次,n=100 000 时涨到 4 999 950 000 次;Set 方案固定只查表 n 次。这也是上限敢定在 20 万码点的原因:本机实测 20 万字去重耗时约 9.96 毫秒,内存占用约 0.38 MB。
为什么用 Array.from 而不用 split(''):split('') 按 UTF-16 码元切分,会把 U+1F600 这类增补平面字符切成两个孤代理项。实测 Array.from('😀😁') 得到 2 个码点,去重后仍是原样;'😀😁'.split('') 得到 4 个码元,去重后拼回会多出一个孤代理项 U+DE01,显示成乱码方块。所以本页全程按码点走。
「仅压缩连续重复」模式不走 Set,改用 String.prototype.replace(/(.)\1+/gus, '$1'):u 标志让 . 按码点匹配(否则一个 emoji 会被当成两个码元),s 标志让 . 也匹配换行,反向引用 \1 保证只合并紧挨着的相同字符。
忽略大小写时用 String.prototype.toLowerCase 生成比较键,而不用 toLocaleLowerCase——后者会套用地区规则处理土耳其语 İ 之类的字符,同一段文本在不同语言环境下会得到不同结果,这对一个去重工具是不可接受的。
完整示例(数值仅作演示)
默认示例共 91 个码点、52 种不同字符;默认参数带白名单「,。」,所以输出里会多保留两个标点。三种设置下的实测结果:
- 全局去重:结果 54 个码点,去掉 37 个,去重率 40.66%。结果开头是「删重复字符工具会把出现的只保留第一次位置」。
- 全局去重 + 忽略大小写:结果 51 个码点,比区分大小写再少 3 个——第二组的
a、b、c被第一组的A、B、C并掉,输出里保留的是先出现的大写。 - 仅压缩连续重复:结果 83 个码点,只去掉 8 个。拆开看是「人人人」去掉 2、「天天天」去掉 2、「AABBC」去掉 2、「aabbc」去掉 2,其余分散出现的重复字一个不动。
把白名单清空后再跑一次全局去重,结果是 52 个码点、去掉 39 个、去重率 42.86%——这 52 就是这段文本的字符集大小,正好等于频次表的行数。
频次表里排在最前面的是「重」「复」「字」「的」,各出现 4 次,分别首次出现在第 2、3、4、14 个码点;「会」「出」「现」「第」「一」「。」「人」「天」各 3 次。
输入范围与对照表
| 设置项 | 可选范围 | 本页默认值 | 作用 |
|---|---|---|---|
| 去重模式 | 全局去重 / 仅压缩连续重复 | 全局去重 | 决定是否只处理相邻重复 |
| 文本长度 | 1 ~ 200 000 码点 | 示例 91 码点 | 超过上限直接报错,不截断 |
| 白名单 | 0 ~ 64 个字符 | ,。 | 名单内的字符不做去重判断 |
| 忽略大小写 | 勾选 / 不勾选 | 不勾选 | 比较键小写化 |
| 忽略空白与换行 | 勾选 / 不勾选 | 不勾选 | 空白类码点整体剔除 |
字符频次对照表默认渲染 12 行,点「加载更多」每次再加 12 行,「导出 CSV」带 BOM 头,Excel 打开中文不会乱码。
输入范围、边界与精度说明
长度上限 200 000 码点,超过会给出「当前 X 码点,上限 200 000 码点」的具体提示并中止,不做静默截断——截断会让用户误以为全文都已处理。
白名单最多 64 个字符,写重复字符会提示「白名单里有重复字符『X』,同一个字符写一次即可」。白名单只按字面字符匹配,不支持正则、不支持字符类,填 [,。] 会被当成四个字面字符。
「忽略空白与换行」覆盖的码点是 U+0020 空格、U+0009 制表、U+000A 换行、U+000D 回车、U+00A0 不换行空格、U+3000 全角空格这六个。注意它们是被剔除而不是「合并成一个」:勾上之后结果里一个空格都不剩。
精度方面结果是原文的精确子序列,不引入任何新字符,也不改变保留字符的相对顺序;去重率按码点计算,与按「字」统计的口径不同——一个汉字、一个 emoji、一个组合音标都不算同一个单位。删重复字符工具的判断粒度就是单个码点,没有词级或行级的合并能力。
空输入不会返回空串了事,而是提示「待处理文本为空」;只勾规则但没填文本同理。这样避免用户点完按钮以为处理成功。
常见误用:把「去重后字数没变多少」当成故障。本页比较的是码点,全角 A(U+FF21)和半角 A(U+0041)是两个不同码点,繁体「國」(U+570B)和「中」(U+4E2D)也是;带音标的 é 可能是单个 U+00E9,也可能是 e 加 U+0301 两个码点,两者互不相等。要合并这些,得先做全角半角转换。
常见问题
为什么去重后长度几乎没变化?
最常见的原因是文本里本来就没有重复码点,或者重复的是全角/半角、简体/繁体这类看起来一样、码点不同的字符。把频次表按「出现次数」排序看一眼,如果最高次数就是 1,说明确实没有重复。
结果里出现了乱码方块是怎么回事?
说明原文里含有增补平面字符(emoji、部分生僻字)且被别的工具按 UTF-16 码元切过,留下了孤代理项。本页按码点读取不会制造孤代理项,但也不会替你修复已经存在的。
忽略大小写后留下的是大写还是小写?
保留首次出现的那个原形。示例中 A 先于 a 出现,所以留下大写 A;如果原文的第一次出现是小写,结果就是小写。
能不能只去掉第二次出现、保留第三次?
不能。全局去重是对每个字符的二值判断(首次保留、其余丢弃),没有「保留前 k 次」的参数。需要按出现次数筛选时请用频次表先定位字符,再用查找替换处理。
20 万字会不会把浏览器卡死?
本机实测 20 万字去重约 9.96 毫秒、约 0.38 MB 内存,瓶颈在渲染而不是计算。超过 20 万码点会被拒绝并提示切分,不会硬跑。
白名单能不能写正则表达式?
不能,白名单按字面逐字符匹配,最多 64 个字符。写 [,。] 会把方括号也算进去,等于四个字符。
结果里字符的顺序是按什么排的?
按原文里第一次出现的先后顺序,既不按频次排序也不按拼音或 Unicode 码位排序。想按频次看就用下方的对照表。
文本内容会被上传吗?
不会。本页只调用浏览器原生字符串与 Set 接口,没有任何 XHR 或 fetch;可以在开发者工具的 Network 面板自查,除了页面自身的 CSS 不会再发出其它请求。
相关工具
删重复字符工具只做字符级去重,下面这些兄弟页各管一类清理工作,搭配使用比反复切分文本更省事。