敏感词过滤/字符编码

第一部分:敏感词过滤

敏感词过滤就是词库匹配,你定义一个词库,里面有很多敏感词,匹配到了就说明这个词是敏感词。

所以最简单的办法就是建立一个list,先把所有的敏感词读进这个list,然后再利用list的contains方法,就可以判断某一句话中是否有敏感词,如果有就弹个提示,告诉用户语句中有敏感词,禁止用户发送,但是如果须要把把敏感词屏蔽掉(比如用” * “号代替)这个时候contains方法就不行了,得自己写算法判断敏感词所在的位置并屏蔽掉,实现起来并不那么简单。

敏感词过滤是一种阻止网络暴力和网络犯罪的非常有效的手段。

当数据是从文本文件读取情况时,还需要判断文件文件编码,以正确编码读取文本,是文本格式文件敏感词过滤的前提,所以本文主要分成2部分,一部分讲解敏感词过滤,一部分讲解编码相关。

一、敏感词过滤思路

1. 敏感词替换

敏感词替换为* 等字符。

2. 敏感词屏蔽

直接删掉检测的敏感词。

3. 用户端阻止发布

用户发布时,不让

4. 系统人工审核

系统可以允许用户首先发布自己的信息,再使用AI辅助方式找出存在敏感词风险的发布信息,由人工进行审核,符合条件或者被“误伤”者放行,属于恶意发布信息的则人工删除并通知发布者。

这是一种对用户体验伤害最小的,最人性化的敏感词过滤方式,但随之而来的可能也是冷人咋舌的人工成本。

二、敏感词过滤实现方案

基于分词过滤的思路:
借助 IKAnalyzer 进行分词,通过遍历分词集合进行敏感词过滤。

缺点:使用 IKAnalyzer 进行分词,有时候分词结果并不是很理想。如:发呆着,分词结果是 [“发”,“呆着”],而我们的敏感词是发呆,这种情况就会造成敏感词过滤不完整。
因此,推荐使用 Java实现敏感词过滤 - DFA算法

1. DFA算法方案

java 敏感词检测
参考URL: https://blog.csdn.net/qq_37753580/article/details/83182128
敏感词过滤与DFA算法Trie树
参考URL: https://blog.csdn.net/helloznan/article/details/51819632
DFA算法实现敏感词过滤
参考URL: https://blog.csdn.net/shengqianfeng/article/details/79572097
Java利用DFA算法实现敏感词过滤
参考URL: https://blog.csdn.net/lovelichao12/article/details/83013630

1.1 DFA性能测试
每行字符数 (包括字符、汉字、符号)行数文件大小待检测字数耗时
约180个字符50w192 MB9252419910670 ms
约180个字符100w384 MB18505096921053 ms
约180个字符200w769 MB37009694539795 ms
约180个字符300w1.12 GB55514711762863 ms
约180个字符600w2.25 GB1110305373129214 ms

测试其他条件,采用最小匹配规则,敏感词库98818个,转换为DFA敏感词的数量:3046。
耗时包括:打开源文件、加载敏感词库、替换字符串后写入新文件。

经过测试,如果string 很大,replaceSensitiveWord效率很低,因此,还是建议string按行处理,每行调用replaceSensitiveWord进行替换,写入数据。
String replaceLine = filter.replaceSensitiveWord(string,1,“xxx”);

1.2 Trie(前缀树/字典树)及其应用

Trie(前缀树/字典树)及其应用
参考URL: https://www.cnblogs.com/justinh/p/7716421.html
[推荐]Trie字典树(前缀树)
参考URL: https://www.jianshu.com/p/bd2a4cc4b77d

基本性质

1,根节点不包含字符,除根节点意外每个节点只包含一个字符。
2,从根节点到某一个节点,路径上经过的字符连接起来,为该节点对应的字符串。
3,每个节点的所有子节点包含的字符串不相同。

优点:
可以最大限度地减少无谓的字符串比较,故可以用于词频统计和大量字符串排序

跟哈希表比较:
    1,最坏情况时间复杂度比hash表好
    2,没有冲突,除非一个key对应多个值(除key外的其他信息)
    3,自带排序功能(类似Radix Sort),中序遍历trie可以得到排序。

trie树最大程度的压缩了内存
使用简单,轻量级, 满足应用前期需求

第二部分:字符编码

一、 java判断文件字符编码

几种字符串编码自动识别工具探讨
参考URL: https://blog.csdn.net/aflyeaglenku/article/details/50697243
mozilla官方参考A composite approach to language/encoding detection
https://www-archive.mozilla.org/projects/intl/UniversalCharsetDetection.html

事实上纯文本的编码检测是一个非常复杂的问题,甚至理论上根本不可能实现。确切地说,「检测」应该叫「探测」或者「推测」才更恰当。自动编码探测的实现原理主要是统计学的方法,每个编码会有一定的特征,首先检测特征是否符合,再使用常用的匹配,类似于蒙特卡罗法。具体方法可以参考Mozilla。

网上信息:
给定一个字节数组表示某些未知编码中的文本(通常是UTF-8或ISO-8859-1,但不一定如此),获得最可能使用的编码(在Java中)的最佳方法是什么?

值得注意:
没有其他元数据可用。字节数组实际上是唯一可用的输入。
检测算法显然不是100%正确。如果算法在80%以上的情况下是正确的,那就足够了。

要识别这些特征的话,比如中文GBK,根据中文的编码规则,进行匹配,如果匹配上了,可能是GBK。
还要继续匹配其他的格式,如果最终匹配到两个类型,那么就不好选了,当文档的字符很少的时候,会出现这样的情况。

mozilla在很多年前就做了一个非常优秀的编码检测工具,叫chardet,后来有发布了算法更加优秀的universalchardet,用于Firefox的自动编码识别。github查询,universalchardet是C++写的。

Mozilla有一个C++版的自动字符集探测算法代码,然后sourceforge上有人将其改成java版的。

Mozilla:
3种不同的检测文本数据编码的方法。它们分别是:1)编码方案方法;2)字符分布;3)2字符序列分布。每一个都有各自的优点和缺点,但是如果我们以一种互补的方式使用这三个,结果会非常令人满意。

1. 编码检测技术选型对比分析

stackoverflow上提问参考(现在是2019年,这个提问是 8年前的提问,最后一次活跃是4年前) What is the most accurate encoding detector? [closed]
https://stackoverflow.com/questions/3759356/what-is-the-most-accurate-encoding-detector

juniversalchardet
jchardet
cpdetector
ICU4J

  1. jchardet
    在maven仓库查询发现是2007年的版本,版本很老。
  2. juniversalchardet
    juniversalchardet 是mozilla universalchardet C++版本的字符编码检测的java移植版本,如下链接,universalchardet在mozilla中还在持续更新优化。(现在是2019)
    https://hg.mozilla.org/mozilla-central/file/tip/extensions/universalchardet/
    在maven仓库上是2011年的版本。不过发现github个人的juniversalchardet https://github.com/albfernandez/juniversalchardet 是2018年的。

juniversalchardet 和jchardet。我的总体印象是juniversalchardet提供了更好的检测准确性和两个库的更好的API。

Related Works

jchardet http://jchardet.sourceforge.net/

jchardet is another Java port of the Mozilla’s encoding dectection library. The main difference between jchardet and juniversalchardet is modules they are based on. jchardet is based on the “chardet” module that has long existed. juniversalchardet is based on the “universalchardet” module that is new and generally provides better accuracy on detection results.

juniversalchardet https://code.google.com/archive/p/juniversalchardet/

The original repository of this project

总结:技术选型juniversalchardet提供了更加准备的检测结果。
与其他应用程序相比,它更容易与我们的应用程序集成,并产生了巨大的结果。

  1. ICU4J.
    Apache Tika 的AutoDetectReader就是这样做的——首先尝试使用HTMLencodingDetector,然后尝试通用编码Detector(基于JuniversalCharDet),然后尝试ICU4JencodingDetector(基于ICU4J)。

maven仓库上版本很新,是2019年的。

  1. cpdetector
    字符编码检测器的优势在于它是否关注统计分析或HTML元和XML Prolog发现。如果您正在处理具有meta的HTML文件,请使用cpdetector。否则,您最好的选择是monq.stuff.encodingdetector或com.sun.syndication.io.xmlreader。

2. juniversalchardet 检测编码测试

检测不出来编码整理

  1. 用sublime把 文本保存为 UTF-16 LE,测试juniversalchardet检测不出来其编码。
  2. 用java程序生成GBK编码文本,它检测不出来编码。
  3. 用java程序生成GB2312编码文本,它检测不出来编码。
  4. 用java程序生成的BIG5编码文本,它检测出来是WINDOWS-1252,不对。
  5. 用java程序生成GB18030编码问题,它检测不出来。

查看官网,如下,对汉语的编码支持如下
Chinese

​ ISO-2022-CN

​ BIG-5

​ EUC-TW

​ HZ-GB-2312

Unicode

​ UTF-8

​ UTF-16BE / UTF-16LE

​ UTF-32BE / UTF-32LE / X-ISO-10646-UCS-4-3412 / X-ISO-10646-UCS-4-2143

juniversalchardet支持的编码检测,在下面常量类中定义:

All supported encodings are listed in org.mozilla.universalchardet.Constants.

3. ICU4J

官方参考
http://icu-project.org
https://github.com/unicode-org/icu/releases/tag/release-64-2
ICU4C: http://icu-project.org/apiref/icu4c/
ICU4J: http://icu-project.org/apiref/icu4j/

International Component for Unicode (以下简称 ICU) 使得开发人员在 C/C++ 和 Java 上开发全球化软件产品更容易,ICU 是由 IBM 发布和维护,并且是开放源代码的。

2018-07-16: The ICU source repository has also moved to GitHub. (Unicode blog post.)

3.1 ICU4J 认识

ICU4J 是一个广泛使用的开源 Java 库集合,为软件应用提供 Unicode 和全球化支持。

使用统计学和启发式方法对网页源码进行编码探测。ICU4J就是基于第二种方式的类库。由IBM提供。
文档全,大厂实力。http://userguide.icu-project.org/howtouseicu

ICU库是一个支持国际化,本地化的软件库。

3.2 ICU4J 检测编码测试

官方还给出了demo程序: http://www.icu-project.org/icu4jdemos.html

发现icu的字符编码识别有一套测评算法来针对待检测数据进行各种编码方式的评分,这个评分的变量就是confidence。待检测的数据在icu库按照其支持的所有编码方式挨个检测,每一种编码都有自己的记分值,经过理论的统计,如果不符合编码,其记分就是0,某个编码部分符合记分就会介于10-100之间,某个编码针对待检测数据的匹配度越高,记分值confidence也就是越高,当全部检测完成,把每个编码的计分值由大到小排列,记分值最大的字符编码就是待检测数据的编码方式,直接返回即可。

测试结果:

经过测试:BIG5识别出来了,GB2312没有识别出来,GBK识别成GB18030, GB18030识别正确。 UTF-8识别正确。
纯字母的UTF-8文件,也可以正确识别出UTF-8,但是juniversalchardet就显示不出来。
纯字母的GBK文件,识别出UTF-8, juniversalchardet识别不出。

字母加符号,就识别不很准确,如
{tel=15301666942} GBK保存,识别成ISO-8859-2

纯字母也会有识别不准情况,如下,UTF-8
fasdfasadfasfda 识别成 ISO-8859-9
fasdfasadfasfdaaaa 识别成 UTF-8
abc识别成 UTF-8
~!@#$%^&*() 识别成 ISO-8859-9

纯字母大部场景感觉识别正确,但是有时就是识别不对,如上面举例。

其他参考网上信息:
ICU在识别BIG5和GBK可能会识别错误。
台湾和香港、澳门采用的Big5繁体编码,发现两种编码都是区位吗,而且十六进制数值存在重叠,那当然可能会被识别错误。

3.2 ICU4C

ICU4C是ICU在C/C++平台下的版本, ICU(International Component for Unicode)是基于"IBM公共许可证"的,与开源组织合作研究的, 用于支持软件国际化的开源项目。ICU4C提供了C/C++平台强大的国际化开发能力,软件开发者几乎可以使用ICU4C解决任何国际化的问题,根据各地的风俗和语言习惯,实现对数字、货币、时间、日期、和消息的格式化、解析,对字符串进行大小写转换、整理、搜索和排序等功能,必须一提的是,ICU4C提供了强大的BIDI算法,对阿拉伯语等BIDI语言提供了完善的支持。

ICU首先是由Taligent公司开发的,Taligent公司现在被合并为IBM?公司全球化认证中心的Unicode研究组,然后ICU由IBM和开源组织合作继续开发,开源组织给与了ICU极大的帮助。
开始ICU只有Java平台的版本,后来这个平台下的ICU类被吸纳入SUN公司开发的JDK1.1,并在JDK以后的版本中不断改进。C++和C平台下的ICU是由JAVA平台下的ICU移植过来的,移植过的版本被称为ICU4C,来支持这C/C++两个平台下的国际化应用。
ICU4J和ICU4C区别不大,但由于ICU4C是开源的,并且紧密跟进Unicode标准,ICU4C支持的Unicode标准总是最新的;同时,因为JAVA平台的ICU4J的发布需要和JDK绑定,ICU4C支持Unicode标准改变的速度要比ICU4J快的多

二、语言检测工具

有两个开源的项目可以使用。一个是Apache Tika,一个是language-detection

三、Java实现将任何编码方式的txt文件以UTF-8编码方式转存

Java实现将任何编码方式的txt文件以UTF-8编码方式转存
参考URL: https://blog.csdn.net/u014116780/article/details/84308012

思路:首先判断txt文件的编码方式,然后按照其编码方式按行读取,再按行以UTF-8的编码写入。
识别出文件编码,然后 new String(str.getBytes(“识别出的文件编码,即文件真实编码”),“UTF-8”)

四、操作系统相关字符编码总结

  1. Windows上utf-8的编码都默认添加BOM头,但是也可以使用无BOM头的UTF-8来保存的,而在mac上默认的UTF-8都是无BOM的编码格式
  2. windows记事本,记事本保存格式为utf-8的输出格式为utf-8,保存格式为ansi输出为GB18030

五、常见的汉字字符集编码

以下是常见的汉字字符集编码:

GB2312编码:1981年5月1日发布的简体中文汉字编码国家标准。GB2312对汉字采用双字节编码,收录7445个图形字符,其中包括6763个汉字。

BIG5编码:台湾地区繁体中文标准字符集,采用双字节编码,共收录13053个中文字,1984年实施。

GBK编码:1995年12月发布的汉字编码国家标准,是对GB2312编码的扩充,对汉字采用双字节编码。GBK字符集共收录21003个汉字,包含国家标准GB13000-1中的全部中日韩汉字,和BIG5编码中的所有汉字。

GB18030编码:2000年3月17日发布的汉字编码国家标准,是对GBK编码的扩充,覆盖中文、日文、朝鲜语和中国少数民族文字,其中收录27484个汉字。GB18030字符集采用单字节、双字节和四字节三种方式对字符编码。兼容GBK和GB2312字符集。

Unicode编码:国际标准字符集,它将世界各种语言的每个字符定义一个唯一的编码,以满足跨语言、跨平台的文本信息转换。

六、编码格式基础

一图弄懂ASCII、GB2312、GBK、GB18030编码
参考URL: https://cloud.tencent.com/developer/article/1343240
[推荐]BIG5编码, GB编码(GB2312, GBK, …), Unicode编码, UTF8, WideChar, MultiByte, Char 说明与区别
参考URL: https://blog.csdn.net/bagboy_taobao_com/article/details/42294097
[强烈推荐-文章质量很高-必看]UNICODE,GBK,UTF-8区别
参考URL: https://www.cnblogs.com/gavin-num1/p/5170247.html
深入分析 Java 中的中文编码问题
参考URL: https://www.ibm.com/developerworks/cn/java/j-lo-chinesecoding/
如何理解java采用Unicode编码
参考URL: https://blog.csdn.net/gjb724332682/article/details/43229563
[推荐]目前那种语言能原生处理Unicode中四字节字符?
参考URL: https://exp.newsmth.net/topic/article/aebd3e7f030a2be9a5e09baba72922b2
Unicode了解一下:Emoji
参考URL: https://blog.csdn.net/oyji1992/article/details/80042374
2019 年最新 Emoji 来了,你最喜爱哪一个?
参考URL: https://baijiahao.baidu.com/s?id=1625264464044478336&wfr=spider&for=pc
为什么 Emoji 在不同平台长得不一样?
参考URL: http://www.ifanr.com/1062670

1. ISO8859编码

ISO 8859,全称ISO/IEC 8859,是国际标准化组织(ISO)及国际电工委员会(IEC)联合制定的一系列8位字符集的标准,现时定义了15个字符集。ASCII收录了空格及94个“可印刷字符”,足以给英语使用。但是,其他使用拉丁字母的语言(主要是欧洲国家的语言),都有一定数量的附加符号字母,故可以使用ASCII及控制字符以外的区域来储存及表示。除了使用拉丁字母的语言外,使用西里尔字母的东欧语言、希腊语、泰语、现代阿拉伯语、希伯来语等,都可以使用这个形式来储存及表示。

关于拉丁字母:
拉丁字母的传播和基督教的传播密不可分。西欧、美洲、大洋洲、非洲(除北非和埃塞俄比亚)诸语言和东欧的波兰语、捷克语、霍尔瓦特语、斯洛文尼亚语以及亚洲的越南语、马来语、印尼语和土耳其语均采用拉丁字母书写。
1958年中华人民共和国颁布了以拉丁字母为基础制定的汉语拼音。国家为少数民族创制的文字也均以拉丁字母书写。

按国家/地区分别编码。 ISO陆续语系)的扩充ASCII制定了十多个适用于不同国家和地区(均为拉丁字符集(高位为1的8位代码),称为ISO8859
又称为 扩充ASCII字符集

ISO/IEC 8859-1 (Latin-1) - 西欧语言
ISO/IEC 8859-2 (Latin-2) - 中欧语言
ISO/IEC 8859-3 (Latin-3) - 南欧语言。世界语也可用此字符集显示。
ISO/IEC 8859-4 (Latin-4) - 北欧语言
ISO/IEC 8859-5 (Cyrillic) - 斯拉夫语言
ISO/IEC 8859-6 (Arabic) - 阿拉伯语
ISO/IEC 8859-7 (Greek) - 希腊语
ISO/IEC 8859-8 (Hebrew) - 希伯来语(视觉顺序)
ISO 8859-8-I - 希伯来语(逻辑顺序)
ISO/IEC 8859-9 (Latin-5 或 Turkish) - 它把Latin-1的冰岛语字母换走,加入土耳其语字母。
。。。

2. ASCII、GB2312、GBK、GB18030编码

之所以把这几个放在一起介绍,是因为他们的相关性非常强。兼容性关系是GB18030兼容GBK,GBK兼容GB2312,GB2312兼容ASCII。所谓兼容,
你可以简单理解为子集、不冲突的关系。例如GB2312编码的文件中可以出现ASCII字符,GBK编码的文件中可以出现GB2312和ASCII字符,GB18030编码的文件可以出现GBK、GB2312、ASCII字符。

每种编码方式的特点:

【1】ASCII 每个字符占据1bytes,用二进制表示的话最高位必须为0(扩展的ASCII不在考虑范围内),因此ASCII只能表示128个字

【2】GB2312 最早一版的中文编码,每个字占据2bytes。由于要和ASCII兼容,那这2bytes最高位不可以为0了(否则和ASCII会有冲突)。
在GB2312中收录了6763个汉字以及682个特殊符号,已经囊括了生活中最常用的所有汉字。

【3】GBK 由于GB2312只有6763个汉字,我汉语博大精深,只有6763个字怎么够?于是GBK中在保证不和GB2312、ASCII冲突(即兼容GB2312和ASCII)
的前提下,也用每个字占据2bytes的方式又编码了许多汉字。经过GBK编码后,可以表示的汉字达到了20902个,另有984个汉语标点符号、部首等。
值得注意的是这20902个汉字还包含了繁体字。

【4】GB18030 然而,GBK的两万多字也已经无法满足我们的需求了,还有更多可能你自己从来没见过的汉字需要编码。
这时候显然只用2bytes表示一个字已经不够用了(2bytes最多只有65536种组合,然而为了和ASCII兼容,最高位不能为0就已经直接淘汰了一半的组合,
只剩下3万多种组合无法满足全部汉字要求)。因此GB18030多出来的汉字使用4bytes编码。当然,为了兼容GBK,
这个四字节的前两位显然不能与GBK冲突(实操中发现后两位也并没有和GBK冲突)。我国在2000年和2005年分别颁布的两次GB18030编码,
其中2005年的是在2000年基础上进一步补充。至此,GB18030编码的中文文件已经有七万多个汉字了,甚至包含了少数民族文字。

3. BIG5编码

BIG5字集是台湾繁体字集,共包括国标繁体汉字13053个

4. GB编码

GB2312字集是简体字集, 全称为GB2312(80)字集, 共包括国标简体汉字6763个;
GB2312是中国规定的汉字编码, 也可以说是简体中文的字符集编码;

GBK包含全部中文字符;
GBK字集是简繁字集, 包括了GB字集, BIG5字集和一些符号, 共包括21003个字符;
GBK是GB2312的扩展, 除了兼容GB2312外, 它还能显示繁体中文, 还有日文的假名;
GBK标准, 他兼容GB2312标准, 同时在GB2312标准的基础上扩展了GB13000包含的字;

注意:

  1. GBK字集是简繁字集, 包括了GB字集, BIG5字集和一些符号; 例如BIG5编码能够表示XX汉字, GBK编码也能够表示XX汉字, 但是XX汉字的BIG5编码与GBK编码是不同的.
  2. GBK编码兼容GB2312编码, 例如GB2312编码能够表示YY汉字, 那么GBK编码也能够表示YY汉字, 而且YY汉字的GB2312编码与GBK编码是相同的.
  3. 也就是说"包含"与"兼容"是两回事来的.

总结: big5和gbk虽然都包含繁体,但是编码是不同的,所以两个不兼容。
在html中, W3提倡用charset = “gbk”, 而不是用charset = “gb2312”;

5. Unicode编码

Unicode也是一种字符编码方法, 由国际组织设计, 可以容纳全世界所有语言文字的编码方案.
Unicode的学名是"UniversalMultiple-Octet Coded Character Set". 简称为UCS。UCS可以看作是"Unicode CharacterSet"的缩写

  1. ASCII, GB2312, GBK到GB18030的编码方法是向下兼容的. 而Unicode只与ASCII兼容, 与GB码不兼容.
    例如"汉"字的Unicode编码是6C49, 而GB码是BABA.

  2. 一般来说, 如果在简体中文操作系统中使用的繁体字, 选GBK码繁体中文; 如果在繁体中文操作系统使用繁体字, 选Big5码繁体中文;

  • Unicode编码与UTF编码
    Unicode编码只是规定如何编码, 例如"汉"字的Unicode编码是6C49, 那么如何把"汉"字保存到文件中, 你可以直接把6C49的数值保存, 你也可以吧6C49这4个字符来保存, 也就是说需要一种保存格式(一种格式协议). UTF-8, UTF-7, UTF-16就是被广泛接受的保存格式.

举例UTF-8编码

UTF-8编码分段范围:
编码范围 编码格式
A. 0000 - 007F 0XXXXXXX
B. 0080 - 07FF 110XXXXX 10XXXXXX
C. 0800 - FFFF 1110XXXX 10XXXXXX 10XXXXXX

例如“汉”字的Unicode编码是6C49, 6C49在编码范围范围C之内, 所以使用编码格式C(1110xxxx 10xxxxxx 10xxxxxx).
将6C49写成二进制是:0110 110001 001001, 依次代替模板中的X,得到:11100110 10110001 10001001, 即E6 B1 89.
所以"汉"字的Unicode编码是6C49, UTF-8的编码是E6B189.
现在把"汉"字的UTF-8的编码保存在文本中, 程序知道这是UTF-8编码, 提取E6B189中的信息得到6C49, 因为6C49必然是Unicode编码,
所以知道这个是"汉"字.(因为这是一个格式协议, 程序就是按照这个格式协议来解析这个文件的).

总结: Unicode 编码是一套编码标准,囊括世界上所有字符,一个字符对应一个编码。而UTF-8、UTF-16等,就是 Unicode 编码的在计算机存储实现,是对unicode编码的一种再编码映射。

6. 计算机内存中,java统一使用Unicode编码

在计算机内存中,统一使用Unicode编码,当需要保存到硬盘或者需要传输的时候,就转换为UTF-8编码。

用记事本编辑的时候,从文件读取的UTF-8字符被转换为Unicode字符到内存里,编辑完成后,保存的时候再把Unicode转换为UTF-8保存到文件

浏览网页的时候,服务器会把动态生成的Unicode内容转换为UTF-8再传输到浏览器。

java虚拟机采用UTF-16编码格式对字符进行编码
java虚拟机采用UCS2(通用字符集)标准保存字符,所有字符再内存中都是2个字节,这样在字符串截取、长度判断等都非常容易,其他语言如python,运行时也是采用
固定长度存储字符。
相对应编译后的class,java规定采用UTF-8存储,因为大部分是英文字符、只有一个字节、可以大量节省空间。

Java中字符仅以一种形式存在,那就是Unicode。由于java采用unicode编码,char 在java中占2个字节。2个字节(16位)来表示一个字符。
这里的Java中是指在JVM中、在内存中、在代码里声明的每一个char、String类型的变量中。

JVM的折中约定使得一个字符分为两部分:JVM内部和OS的文件系统。在JVM内部,统一使用Unicode表示,当这个字符被从JVM内部移到外部
即保存为文件系统中的一个文件的内容时),就进行了编码转换,使用了具体的编码方案。因此可以说所有的编码转换只发生在边界的地方,
JVM和OS的交界处,也就是各种输入/输出流(或者Reader,Writer类)起作用的地方。

从这个意义上可以看出,面向字符的I/O类,也就是Reader和Writer类,实际上隐式做了编码转换,
在输出时,将内存中的Unicode字符使用系统默认编码方式进行了编码,而在输入时,将文件系统中已经编码过的字符使用默认编码方案进行了还原。
这里要注意,Reader和Writer只会使用这个默认的编码来做转换,而不能为一个Reader和Writer指定转换时使用的编码。
这也意味着,如果使用中文版WindowsXP系统,其中存放了一个UTF8编码的文件,当采用Reader类来读入的时候,它还会用GBK来转换,转换后的内容当然不对。
这其实是一种傻瓜式的功能提供方式,对大多数初级用户(以及不需要跨平台的高级用户,windows一般采用GBK,linux一般采用UTF8)来说反而是一件好事。

如果用到GBK编码以外的文件,就必须采用编码转换:一个字符与字节之间的转换。因此,Java的I/O系统中能够指定转换编码的地方,
也就是在字符与字节转换的地方,那就是InputStremReader与OutputStreamWriter。这两个类是字节流和字符流的适配器类,它们承担编码转换的任务。
既然java是用unicode来编码字符,"我"这个中文字符的unicode就是2个字节。
String.getBytes(encoding)方法是获取指定编码的byte数组表示,通常gbk/gb2312是2个字节,utf-8是3个字节。
如果不指定encoding则取系统默认的encoding。

由于JDK是国际版的,在编译的时候,如果我们没有用-encoding参数指定我们的JAVA源程序的编码格式,
则javac.exe首先获得我们操作系统默认采用的编码格式,也即在编译java程序时,若我们不指定源程序文件的编码格式,
JDK首先获得操作系统的file.encoding参数(它保存的就是操作系统默认的编码格式,如WIN2k,它的值为GBK),
然后JDK就把我们的java源程序从file.encoding编码格式转化为JAVA内部默认的UNICODE格式放入内存中。

System.getProperty(“file.encoding”)
注意,file.encoding在ide和直接用命令行输出可能不一致,ide比如eclipse,idea等可以配置这个编码。如IDEA中的Project Encoding配置。

然后,javac把转换后的unicode格式的文件进行编译成.class类文件,此时.class文件是UNICODE编码的,它暂放在内存中,
紧接着,JDK将此以UNICODE编码的编译后的class文件保存到我们的操作系统中形成我们见到的.class文件。
对我们来说,我们最终获得的.class文件是内容以UNICODE编码格式保存的类文件,它内部包含我们源程序中的中文字符串,
只不过此时它己经由file.encoding格式转化为UNICODE格式了。
如果IDE设置了file.encoding为UTF-8,那么保存这些代码的文件编码就是UTF-8,
如果这时候,在编码为GBK的系统上用javac命令编译这个类,并执行,那么结果会输出乱码。如果在IDE里编译执行,就不会输出乱码。

总结: java字符串由char值序列组成,char数据类型是一个采用UTF-16编码标识Unicode马甸的代码单元。大多数常用Unicode字符使用一个代码单元就可以白送hi,
而一些辅助字符需要一对代码单元表示。length方法将返回UTF-16编码表示的给定字符串所需要的代码单元数量。
UTF-16编码采用不同长度的编码表示所有Unicode代码点。在基本的多语言级别中,每个字符用16位表示,通常被称为代码单元(code unit);而辅助字符采用对连续的代码单元进行编码。
在Java中,char类型用UTF-16编码描述一个代码单元。

Unicode字符编码有两种方案:16位编码与32位编码,对应的字符集分别称为USC-2和USC-4。Java语言采用USC-2字符集,
即16位Unicode字符编码,其前128个字符与ASCII字符集完全一致,之后是其他语言文字,如拉丁语、希腊语、汉字等。

char 在java中是2个字节。java采用unicode,2个字节(16位)来表示一个字符。

可以总结,基本所有字符(只有很生僻的字符才会被编码成4-6个字节)在java内存中都占2个字节。经过测试,即使大部分你搜到的特殊字符,也都是占2个字节。

经过测试,你粘贴到代码的 一些特殊字符,如 ? length为2 , 你粘贴到字符串的双引号中,IDEA会自动帮你转换成"\uD801\uDC00",在双引号中你粘贴不出这个
特殊字符。在非双引号的地方却可以粘出。一些特殊字符如☯,可以粘贴出来String xxx = “☯”;。

经过测试字符串双引号里面输入 特殊字符,也可以在输入unicode编码。如 String xxx1 = “❆”;或String xxx1 = “\u2746”;
两者效果一样,length都是1。

最开始我以为在字符串双引号中粘贴不出?,是因为它的unicode编码占了4字节,但是测试Emoji Unicode占4字节的 #⃣ 却可以,粘贴的双引号里。
推测分析: Emoji Unicode和特殊字符不一样, IDEA在对2字节的特殊字符,以及Emoji的都可以粘贴到字符串双引号里面,并且\u unicode也行。
IDEA对于一些占4字节的特殊字符,字符串双引号只能输入\u unicode形式。
目前现象是这样,至于为什么要这样,我估计是为了一些兼容考虑吧,比如虽然IDEA编辑能识别你这个特殊字符,并且能正常显示,但是IDEA编译的时候,
IDEA编译器它识别不了,那么编译成class文件的时候,它需要把你的特殊字符存储为unicode,它不知道应该怎么存储你这个特殊字符,所以用\u形式,
你怎么写的它怎么存储。对于编译器和编辑器都知道的,那么IDEA字符串双引号中2中形式都执行。

网上查看,许多特殊字符,其实大部分编码都是2字节,如下
http://caibaojian.com/unicode.html

length()函数返回采用UTF-16编码标识的给定字符串所需要的代码单元的数量。
codePointCount()函数返回采用UTF-16编码标识的给定字符串所需要的代码点的数量。

7. Emoji

统一码联盟(The Unicode Consortium)每年会对来自全球的 emoji 提案进行选拔和投票,定期公布新的 emoji。
每一个 emoji,就是一个 Unicode 字符。

版权才是让 Emoji 有诸多变脸的原因
让同一个编码的 emoji 拥有诸多变脸,除了是各个平台有着自己的价值观和设计风格,更重要的是 emoji 设计的版权问题。

emoji 的官方名字版权属于 Unicode 规范,即统一码联盟;
emoji 的描述文字版权,属于网站 emojipedia.org
emoji 对应的图形设计版权,属于它的创作者或公司。

苹果在设计和推广 emoji 上,走在时代前列因此也风靡全球,它的这套 emoji 被称为「Apple Color Emoji」,
版权属于苹果。作为苹果用户,我们可以在系统中使用这一套 emoji 字体,但是没有权限在其他非苹果设备上安装使用这套字体。

Google 的 Android Emoji 倒是遵守 Apache Licene 2.0 的开源协议, 但是很多人并不认可这一套表情符号的设计,
人们依然是对苹果那套 emoji 比较熟悉。

所以不少有着一定规模用户量的厂商,为了避免版权风险,都会设计自己的 emoji 版本,比如 Twitter、Facebook、微软、三星等。

因此,我们能看到每家厂商都沉浸在自己设计的 emoji 版本里无法自拔,还会与时俱进地不断修改迭代。导致我们这些用户的体验是:
在跨应用、跨系统的沟通中,一些原本能够让人们更好沟通表达的 emoji,就成了不可识别的乱码。

截止至2018年 6 月,通过官方审核的 emoji 已经达到 2823 个,距离 1998 年第一批共 177 个 emoji 被设计出来,已经过去 20 年了。
它们本应是全球社交媒体上通用的语言符号,如今看来,倒像是被摧毁的巴别塔。

七、其他参考

如何解决猜测Java中表示为byte []的文本的编码?
参考URL: https://cloud.tencent.com/developer/ask/195806/answer/305369
网站敏感词过滤的实现(附敏感词库)
参考URL: https://www.jianshu.com/p/7b74d664ddc3
文本语言检测之language-detection
参考URL: https://www.jianshu.com/p/9611a048f970
language-detection是一个用于语言检测的开源库,可以根据字符串检测出其所属的国家语言,该项目已经在GitHub上开源
ICU4J开源字符编解码库识别出错问题小结_源于解决android WiFi SSID中文乱码问题
参考URL: https://blog.csdn.net/shawhay518/article/details/80352237
对于敏感词过滤,我们只能选择让用户“痛不欲生”吗?
参考URL: https://www.jianshu.com/p/ba3883e9b7ff

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

西京刀客

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值