Opencc4j 支持中文繁简体转换,考虑到词组级别。
-
严格区分「一简对多繁」和「一简对多异」。
-
完全兼容异体字,可以实现动态替换。
-
严格审校一简对多繁词条,原则为「能分则不合」。
-
词库和函数库完全分离,可以自由修改、导入、扩展。
-
兼容 Windows、Linux、Mac 平台。
-
支持自定义分词、引导类,优雅实现
-
支持判断单个字(词)是否为简体/繁体
-
支持返回字符串中简体/繁体的列表信息
-
支持中国台湾地区和大陆简体繁简体转换
-
支持中国香港地区和大陆简体繁简体转换
-
支持日文新字和大陆简体繁简体转换
-
兼容双字符汉字,【𨦟】【𪡃】等
- OpenCC
OpenCC 的思想非常优秀,做的也特别棒。但是没有特别为 java 提供的工具。
<dependency>
<groupId>com.github.houbb</groupId>
<artifactId>opencc4j</artifactId>
<version>1.13.0</version>
</dependency>
核心工具列表如下:
序号 | 工具类 | 简介 |
---|---|---|
1 | ZhConverterUtil | 基础的繁简体转换 |
2 | ZhTwConverterUtil | 中国台湾地区与大陆的繁简体转换 |
3 | ZhHkConverterUtil | 中国香港地区与大陆的繁简体转换 |
4 | ZhJpConverterUtil | 日文新字与大陆的繁简体转换 |
所有的工具类方法具有相同的方法设计,便于记忆。
核心方法如下:
序号 | api 方法 | 简介 |
---|---|---|
1 | toSimple(String) | 转为简体 |
2 | toTraditional(String) | 转为繁体 |
3 | simpleList(String) | 返回包含的简体列表 |
4 | traditionalList(String) | 返回包含的繁体列表 |
5 | toSimple(char) | 返回单个汉字对应的所有简体字列表 |
6 | toTraditional(char) | 返回单个汉字对应的所有繁体字列表 |
7 | isSimple(String) | 是否全部为简体 |
8 | isSimple(char) | 单个字符是否为简体 |
9 | containsSimple(String) | 字符中是否为包含简体 |
10 | isTraditional(String) | 是否全部为繁体 |
11 | isTraditional(char) | 单个字符是否为繁体 |
12 | containsTraditional(String) | 字符中是否为包含繁体 |
13 | isChinese(String) | 是否全部为中文 |
14 | isChinese(char) | 单个字符是否为中文 |
15 | containsChinese(char) | 字符串中是否包含中文 |
String original = "生命不息,奮鬥不止";
String result = ZhConverterUtil.toSimple(original);
Assert.assertEquals("生命不息,奋斗不止", result);
String original = "生命不息,奋斗不止";
String result = ZhConverterUtil.toTraditional(original);
Assert.assertEquals("生命不息,奮鬥不止", result);
对单个字符或者词组进行繁简体判断。
Assert.assertTrue(ZhConverterUtil.isSimple('奋'));
Assert.assertTrue(ZhConverterUtil.isSimple("奋"));
Assert.assertTrue(ZhConverterUtil.isSimple("奋斗"));
Assert.assertFalse(ZhConverterUtil.isSimple('奮'));
Assert.assertFalse(ZhConverterUtil.isSimple("奮"));
Assert.assertFalse(ZhConverterUtil.isSimple("奮鬥"));
Assert.assertFalse(ZhConverterUtil.isSimple("奮斗"));
Assert.assertFalse(ZhConverterUtil.isSimple("beef"));
Assert.assertTrue(ZhConverterUtil.containsSimple("奋"));
Assert.assertTrue(ZhConverterUtil.containsSimple("奋斗"));
Assert.assertTrue(ZhConverterUtil.containsSimple("奋斗2023"));
Assert.assertFalse(ZhConverterUtil.containsSimple("編"));
Assert.assertFalse(ZhConverterUtil.containsSimple("編號"));
Assert.assertTrue(ZhConverterUtil.isTraditional('編'));
Assert.assertTrue(ZhConverterUtil.isTraditional("編"));
Assert.assertTrue(ZhConverterUtil.isTraditional("編號"));
Assert.assertFalse(ZhConverterUtil.isTraditional('编'));
Assert.assertFalse(ZhConverterUtil.isTraditional("编"));
Assert.assertFalse(ZhConverterUtil.isTraditional("编号"));
Assert.assertFalse(ZhConverterUtil.isTraditional("编號"));
Assert.assertTrue(ZhConverterUtil.containsTraditional("編"));
Assert.assertTrue(ZhConverterUtil.containsTraditional("編號"));
Assert.assertTrue(ZhConverterUtil.containsTraditional("編號2023"));
Assert.assertFalse(ZhConverterUtil.containsTraditional("号"));
Assert.assertFalse(ZhConverterUtil.containsTraditional("编号"));
返回字符串中繁简体对应的词、字列表,默认支持中文分词。
繁简体列表返回的词组和分词策略紧密相关。
final String original = "生命不息奋斗不止";
final List<String> resultList = ZhConverterUtil.simpleList(original);
Assert.assertEquals("[生, 命, 不, 息, 奋斗, 不, 止]", resultList.toString());
PS: 很多字是同体字。
final String original = "生命不息奮鬥不止";
final List<String> resultList = ZhConverterUtil.traditionalList(original);
Assert.assertEquals("[生, 命, 不, 息, 奮, 鬥, 不, 止]", resultList.toString());
Assert.assertEquals("[幹, 乾, 干]", ZhConverterUtil.toTraditional('干').toString());
Assert.assertEquals("[發, 髮]", ZhConverterUtil.toTraditional('发').toString());
Assert.assertEquals("[测]", ZhConverterUtil.toSimple('測').toString());
Assert.assertTrue(ZhConverterUtil.isChinese("你"));
Assert.assertTrue(ZhConverterUtil.isChinese("你好"));
Assert.assertTrue(ZhConverterUtil.isChinese('你'));
Assert.assertFalse(ZhConverterUtil.isChinese("你0"));
Assert.assertFalse(ZhConverterUtil.isChinese("10"));
Assert.assertFalse(ZhConverterUtil.isChinese('0'));
Assert.assertFalse(ZhConverterUtil.isChinese(""));
Assert.assertFalse(ZhConverterUtil.isChinese(null));
Assert.assertTrue(ZhConverterUtil.containsChinese("你"));
Assert.assertTrue(ZhConverterUtil.containsChinese("你好"));
Assert.assertTrue(ZhConverterUtil.containsChinese("你0"));
Assert.assertFalse(ZhConverterUtil.containsChinese("10"));
Assert.assertFalse(ZhConverterUtil.containsChinese(""));
Assert.assertFalse(ZhConverterUtil.containsChinese(null));
为保证方法的一致性,引入 ZhTwConverterUtil
工具类,支持方法和 ZhConverterUtil
保持一致。
简体到繁体:
String original = "使用互联网";
String result = ZhTwConverterUtil.toTraditional(original);
Assert.assertEquals("使用網際網路", result);
繁体到简体:
String original = "使用網際網路";
String result = ZhTwConverterUtil.toSimple(original);
Assert.assertEquals("使用互联网", result);
v1.12.0 版本支持。
为保证方法的一致性,引入 ZhHkConverterUtil
工具类,支持方法和 ZhConverterUtil
保持一致。
/**
* 大陆简体==>香港正體
* @since 1.12.0
*/
@Test
public void testHkTraditional() {
String original = "千家万户瞳瞳日 总把新桃换旧符";
String result = ZhHkConverterUtil.toTraditional(original);
Assert.assertEquals("千家萬户瞳瞳日 總把新桃換舊符", result);
}
/**
* 香港正體==>大陆简体
*/
@Test
public void testHkSimple() {
String original = "千家萬户瞳瞳日 總把新桃換舊符";
String result = ZhHkConverterUtil.toSimple(original);
Assert.assertEquals("千家万户瞳瞳日 总把新桃换旧符", result);
}
v1.13.0 版本支持。
为保证方法的一致性,引入 ZhJpConverterUtil
工具类,支持方法和 ZhConverterUtil
保持一致。
实际流程:简体==》标准繁体==》日文新字
/**
* 大陆简体==>标准繁体=》日文
*/
@Test
public void testJpTraditional() {
String original = "我在日本学习音乐,并学习了龙的字。";
String result = ZhJpConverterUtil.toTraditional(original);
Assert.assertEquals("我在日本学習音楽,並学習了竜的字。", result);
}
/**
* 日文=>标准繁体=>简体
*/
@Test
public void testJpSimple() {
String original = "我在日本学習音楽,並学習了竜的字。";
String result = ZhJpConverterUtil.toSimple(original);
Assert.assertEquals("我在日本学习音乐,并学习了龙的字。", result);
}
主要的可配置项包含了分词和数据集合。
二者都是可以配置,并且支持自定义的。
默认工具类等价于如下:
ZhConvertBootstrap.newInstance()
.segment(Segments.defaults())
.dataMap(DataMaps.defaults()).init();
中国台湾地区配置等价于:
ZhConvertBootstrap.newInstance()
.segment(Segments.twFastForward())
.dataMap(DataMaps.taiwan()).init();
你可以通过 Segments
工具类获取系统内置的分词实现。
序号 | 方法 | 准确性 | 性能 | 备注 |
---|---|---|---|---|
1 | defaults() | 高 | 高 | 默认分词形式,暂时为 fastForward 策略 |
2 | fastForward() | 较高 | 高 | fast-forward 分词策略 |
3 | chars() | 低 | 高 | 将字符串转换为单个字符列表,一般不建议使用 |
4 | huaBan() | 高 | 一般 | 花瓣的结巴分词策略 |
花瓣结巴分词在使用时,需要自行引入结巴分词依赖。
<dependency>
<groupId>com.huaban</groupId>
<artifactId>jieba-analysis</artifactId>
<version>1.0.2</version>
</dependency>
你有时候可能除了上述的两种分词方式,会有更加适合自己业务的分词实现。
Opencc4j 支持自定义分词实现,只需要实现分词接口 Segment
- 接口内容
public interface Segment {
/**
* 分词
* @param original 原始信息
* @return 分词后的列表
*/
List<String> seg(final String original);
}
/**
* 一个最简单的分词实现。
* 注意:仅仅做演示,不可实际使用。
*/
public class FooSegment implements Segment {
@Override
public List<String> seg(String original) {
return Arrays.asList(original, "测试");
}
}
我们自定义的分词,直接在默认添加“测试”这样的信息。
final String original = "寥落古行宫,宫花寂寞红。白头宫女在,闲坐说玄宗。";
final Segment segment = new FooSegment();
final String result = ZhConvertBootstrap.newInstance()
.segment(segment)
.init()
.toTraditional(original);
Assert.assertEquals("寥落古行宮,宮花寂寞紅。白頭宮女在,閒坐說玄宗。測試", result);
不同的地区,对应的转换规则是不同的。
具体参考一下台湾地区的使用方式即可。
IDataMap 的接口如下。
/**
* 数据 map 接口
* @author binbin.hou
* @since 1.5.2
*/
public interface IDataMap {
/**
* 繁体=》简体 词组
* @return 结果
* @since 1.5.2
*/
Map<String, List<String>> tsPhrase();
/**
* 繁体=》简体 单个字
* @return 结果
* @since 1.5.2
*/
Map<String, List<String>> tsChar();
/**
* 简体=》繁体 词组
* @return 结果
* @since 1.5.2
*/
Map<String, List<String>> stPhrase();
/**
* 简体=》繁体 单个字
* @return 结果
* @since 1.5.2
*/
Map<String, List<String>> stChar();
/**
* 繁体字所有字符
* @return 繁体字所有字符
* @since 1.6.2
*/
Set<String> tChars();
/**
* 简体字所有字符
* @return 繁体字所有字符
* @since 1.8.0
*/
Set<String> sChars();
}
如果需要拓展对应的数据,建议继承原始的实现,然后添加额外的数据信息即可。
可以参考 中国台湾地区实现
ps: 后续考虑引入更加简单的实现方式,比如基于文本拓展,不过可扩展性没有接口灵活。
用于用户参考,可以自定义实现自己的实现,更加符合实际业务。
v1.9.0 开始支持,方便用户拓展。
比如首先自定义一个 dataMap 类,集成 AbstractDataMapExtra 可以在指定的 IDataMap 基础上额外指定词组等信息。
public class MyFooDataMapExtra extends AbstractDataMapExtra {
public MyFooDataMapExtra(IDataMap baseDataMap) {
super(baseDataMap);
}
// 默认在基本的繁简体基础上拓展
public MyFooDataMapExtra() {
this(DataMaps.defaults());
}
private Map<String, List<String>> of(String key, List<String> list) {
Map<String, List<String>> map = new HashMap<>();
map.put(key, list);
return map;
}
@Override
protected Map<String, List<String>> tsPhraseExtra() {
// 估计写了个错的,用来演示
return of("風玥", Arrays.asList("风月"));
}
@Override
protected Map<String, List<String>> tsCharExtra() {
return null;
}
@Override
protected Map<String, List<String>> stPhraseExtra() {
// 估计写了个错的
return of("风月", Arrays.asList("風玥"));
}
@Override
protected Map<String, List<String>> stCharExtra() {
return null;
}
}
我们只需要在引导类 ZhConvertBootstrap 指定我们的 dataMap 和对应的分词策略,即可。
// 1.1自定义的额外数据集
final IDataMap dataMap = new MyFooDataMapExtra();
// 1.2 指定分词策略
final Segment segment = new DataMapFastForwardSegment(dataMap);
// 2. 指定
ZhConvertBootstrap bs = ZhConvertBootstrap.newInstance()
.dataMap(dataMap)
.segment(segment)
.init()
;
// 3. 使用
Assert.assertEquals(bs.toTraditional("人生自是有情痴,此恨不关风月"), "人生自是有情癡,此恨不關風玥");
Assert.assertEquals(bs.toSimple("人生自是有情癡,此恨不關風玥"), "人生自是有情痴,此恨不关风月");
V1.11.0 版本支持。
有时候我们需要判断一个字符串是否为繁简体,isSimple()、isTraditional() 默认要求全部匹配,无法满足全部场景。
这里允许用户自定义相关的策略。
内置策略见 ZhMatches
工具方法。
策略 | 说明 | 备注 |
---|---|---|
simpleAll() | 满足全部简体 | isSimple 判断时,默认策略 |
simpleAny() | 满足任一简体 | - |
simpleOverHalf() | 满足超过一半简体 | - |
traditionalAll() | 满足全部繁体 | isTraditional 判断时,默认策略 |
traditionalAny() | 满足任一繁体 | - |
traditionalOverHalf() | 满足超过一半繁体 | - |
// 全部
ZhConvertBootstrap bs = ZhConvertBootstrap.newInstance()
.isSimpleMatch(ZhMatches.simpleAll())
.init();
String text = "123我456";
Assert.assertFalse(bs.isSimple(text));
// 任一
bs.isSimpleMatch(ZhMatches.simpleAny()).init();
Assert.assertTrue(bs.isSimple(text));
// 全部
ZhConvertBootstrap bs = ZhConvertBootstrap.newInstance()
.isTraditionalMatch(ZhMatches.traditionalAll())
.init();
String text = "123俺們456";
Assert.assertFalse(bs.isTraditional(text));
// 任一
bs.isTraditionalMatch(ZhMatches.traditionalAny()).init();
Assert.assertTrue(bs.isTraditional(text));;
如果系统内置的策略不满足,你可以自定义。
实现 ZhMatch 接口,引导类中指定即可使用。
OpenCC 提供的原始数据信息。
jieba-analysis 提供中文分词
需求和 BUG 在这里,欢迎提供宝贵的建议。
如果对您有帮助,欢迎 Star 鼓励作者。
-
数据字典插件化
-
考虑长文本分段,并行转换