删重复字符工具

按 Unicode 码点扫描整段文本,把重复出现的字符只保留第一次出现的位置,也可以只压缩紧挨着的连续重复,并给出每个字符的出现次数。

附加规则

等待处理。

字符频次对照表(默认 12 行)
字符出现次数首次出现位置
重4第 2 个码点
复4第 3 个码点
字4第 4 个码点
的4第 14 个码点
会3第 8 个码点
出3第 12 个码点
现3第 13 个码点
第3第 20 个码点
一3第 21 个码点
。3第 28 个码点
人3第 29 个码点
天3第 33 个码点

使用方法

  1. 在「待处理文本」框里粘贴内容。默认示例是一段 91 个码点的中文,里面有连续叠写的「人人人」「天天天」,也有隔开的重复字,还有两组大小写不同的 AABBC 与 aabbc。
  2. 在「去重模式」里选一种:全局去重把整段里第二次及以后出现的字符全部去掉;仅压缩连续重复只把紧挨着的相同字符并成一个。同一个示例,前者去掉 37 个码点,后者只去掉 8 个。
  3. 要把 A 和 a 当成同一个字符就勾「忽略大小写」——比较时用小写形式当键,但保留首次出现的那个原形;要把空格、制表符、换行一起去重就勾「忽略空白与换行」;要保留所有数字就勾「忽略数字」。
  4. 「白名单」填不参与去重的字符,例如填 ,。 就保留每一个逗号和句号,最多 64 个字符。
  5. 点「开始去重」,或在文本框里按 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 种不同字符;默认参数带白名单「,。」,所以输出里会多保留两个标点。三种设置下的实测结果:

把白名单清空后再跑一次全局去重,结果是 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 不会再发出其它请求。

相关工具

删重复字符工具只做字符级去重,下面这些兄弟页各管一类清理工作,搭配使用比反复切分文本更省事。