Linux环境下Java中⽂乱码解决⽅案
相信很多朋友遇到过Java的乱码问题,最近我也在解决⼀个“使⽤⽂本⽣成图⽚过程中中⽂以及特殊字符乱码”的问题;花了我⼤量时
间,Debug了sun.font、sun.awt下⾯的各种源码,终于搞懂了其机制,解决了⽬前次问题;现在把问题解决过程给写下来,做个记录,以免以后再次遇到。
遇到的问题
下⾯是我想要执⾏的代码(经过极度简化,但是意思没变):
1 public static void main(String[] args) throws IOException {
2    File file = new File("test.png");
3    Font font = new Font("宋体", Font.PLAIN, 10);
4    BufferedImage bi = new BufferedImage(400, 200, BufferedImage.TYPE_INT_ARGB);
5    Graphics2D g2 = (Graphics2D) bi.getGraphics();
6    g2.setBackground(Color.WHITE);
7    g2.clearRect(0, 0, 400, 200);
8    g2.setFont(font);
9    g2.setColor(Color.BLACK);
10    g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
11    g2.drawString("为什么没有(ꐚꌒꑿꆺ)(ꐚꌒꑿꆺ)这名字特殊不?@¥¥¥为什么没有(ꐚꌒꑿꆺ)(ꐚꌒꑿꆺ)这名字特 ", 0, 10);
12    g2.dispose();
13    ImageIO.write(bi, PNG, file);
14 }
⽬标当然是想在打开test.png的时候看到如下场景:
在本地调试没问题之后,就放到了测试机(Linux)上⾯去执⾏了,执⾏结果简直扑街:
jdk1.8的sun源码下载
奉⾏程序员⼀贯作风:既然有问题,那就Debug!
坑爹的是现在的源码包已经不包含sun包的代码了!
幸好java官⽅确认OpenJDK的代码基本和JVM源码⼀致,可以直接从OpenJDK8u进⾏下载:
⾄于如何使⽤源码debug,这个就不写了··· 这都不会基本也就别看这⽂章了
定位问题
直接下载好源码,远程断点,服务器执⾏,在debug中先发现了第⼀个产⽣本地和测试服务器不⼀致的代码:
原来JVM创建Font的时候会使⽤FontManagerFactory获取FontManager,⽽不同的系统使⽤的FontManager是不同的!Mac⽤的是CFontManager,⽽Linux⽤的是X11FontManager!
那么这两个FontManager的不同会导致什么不同呢?
CFontManager会创建CFont作为Font2D,这个CFont是JVM专门为mac创建的类,看类和⽅法的注释可以知道在mac环境下有时候物理字体会被CFont包装,⽽这是在native代码中完成的:
X11FontManager创建的Font2D是包含了逻辑字体和物理字体的集合。X11FontManager继承了FcFontManager,FcFontManager继承了SunFontManager;我们看⼀下X11FontManager的loadFonts()⽅法,直接使⽤了SunFontManager的
loadFonts(),SunFontManager的loadFonts()⽅法加载了物理字体,SunFontManager实现了FontManager的preferLocaleFonts()⽅法,加载了逻辑字体:
逻辑字体与物理字体
代码debug到这边基本已经确认了是不同环境的字体加载问题,那么在debug linux环境的时候发现的逻辑字体和物理字体是什么东西呢?物理字体
物理字体是实际的字体库,包含字形数据和表,这些数据和表使⽤字体技术(如 TrueType 或 PostScript Type 1)将字符序列映射到字形序列。Java Platform 的所有实现都⽀持 TrueType 字体;对其他字体技术的⽀持是与实现相关的。物理字体可以使⽤字体名称,如Helvetica、Palatino、HonMincho 或任意数量的其他字体名称。通常,每种物理字体只⽀持有限的书写系统集合,例如,只⽀
持拉丁⽂字符,或者只⽀持⽇⽂和基本拉丁⽂。可⽤的物理字体集合随配置的不同⽽有所不同。要求特定字体的应⽤程序可以使⽤ createFont ⽅法来捆绑这些字体,并对其进⾏实例化。
逻辑字体
逻辑字体是由必须受所有 Java 运⾏时环境⽀持的 Java 平台所定义的五种字体系列:Serif、SansSerif、Monospaced、Dialog 和DialogInput。这些逻辑字体不是实际的字体库。此外,由 Java 运⾏时环境将逻辑字体名称映射到物理字体。映射关系与实现和通常语⾔环境相关,因此它们提供的外观和规格各不相同。通常,为了覆盖庞⼤的字符范围,每种逻辑字体名称都映射到⼏种物理字体。
问题解决
debug的源码很多,但是此次问题的关键点就在这⾥了,其它debug内容就不贴了。
既然已经确认了本地(mac环境)是native的代码帮我们做了物理字体的封装,转换成了CFont进⾏渲染,⽽Linux环境的X11FontManager 只是帮我们加载了物理字体和逻辑字体,但是却需要我们⾃⼰进⾏选择,那么解决问题的第⼀步就显⽽易见了:将Font的创建从物理字体改为逻辑字体
1 //  Serif、SansSerif、Monospaced、Dialog 和 DialogInput 随意选择
2 Font font = new Font("Serif", Font.PLAIN, 10);
改完以后执⾏代码,仍然是乱码!继续Debug,发现是Linux上逻辑字体Serif映射的物理字体没有中⽂字体和对应的特殊符号字体,这就很简单了,直接在Linux上安装中⽂字体(f),再安装特殊符号“ꐚꌒꑿꆺ”可显⽰的字体(f),将这两个字体也放到了jdk的fonts⽬录(JAVA_HOME/jre/lib/fonts)下。⽂章后⾯有Linux字体安装⽅法。
完成上⾯的改动之后,重启服务,再次执⾏成功显⽰!热烈庆祝~~~~
JVM逻辑字体映射配置
以上的改动已经可以解决中⽂和特殊字符乱码问题,但是我在Debug过程中发现在逻辑字体加载过程中,JVM会参考⼀个配置⽂件,代码在sun.awt.FontConfiguration中,这个配置类完成了逻辑字体和物理字体的映射,也指导了SunFontManager创建逻辑字体,⽽这个FontConfiguration读取的配置⽂件就是fontconfig.properties,这个配置⽂件⽬录是JAVA_HOME/jre/lib
查阅了⼀下资料,JVM字体配置⽂件的加载顺序如下:
JAVA_HOME/jre/lib/fontconfig.OS.Version.properties
JAVA_HOME/jre/lib/fontconfig.OS.Version.bfc
JAVA_HOME/jre/lib/fontconfig.OS.properties
JAVA_HOME/jre/lib/fontconfig.OS.bfc
JAVA_HOME/jre/lib/fontconfig.Version.properties
JAVA_HOME/jre/lib/fontconfig.Version.bfc
JAVA_HOME/jre/lib/fontconfig.properties
JAVA_HOME/jre/lib/fontconfig.bfc
OS是系统,例如:Linux、CentOs、RedHat等;Version是版本号
在这个配置⽂件中可以修改逻辑字体与物理字体的对应关系,也就是说可以⼿动的修改Serif、SansSerif、Monospaced、Dialog 和DialogInput这五个逻辑字体在不同场景下所使⽤的真正物理字体。
举个栗⼦,下⾯的配置将serif.plain逻辑字体的中⽂使⽤f,拉丁⽂使⽤java⾃带字体:
linux中文名
1 # @(#)linux.fontconfig.SuSE.properties 1.
2 03/10/17
2 #
3 # Copyright 2003 Sun Microsystems, Inc. All rights reserved.
4 #
5
6 # Version
7 version=1
8
9 # Component Font Mappings
10 serif.plain.chinese=-misc-simsun-medium-r-normal--*-%d-*-*-c-*-iso10646-1
11 serif.plain.latin-1=-b&h-lucidabright-medium-r-normal--*-%d-*-*-p-*-iso8859-1
12
13 # Search Sequences
14 sequence.allfonts=latin-1,chinese
15
16 # Exclusion Ranges
17
18 # Font File Names
19 filename.-misc-simsun-medium-r-normal--*-%d-*-*-c-*-iso10646-1=/usr/share/fonts/f
Linux安装字体
Linux字体⽬录:/usr/share/fonts
在fonts下⾯新建⼀个⽬录,例如:mkdir myfonts
将需要安装的字体放到新建⽬录下⾯,例如:cp ~/f /usr/share/fonts/myfonts 进⼊到myfonts⽬录:cd /usr/share/fonts/myfonts
执⾏如下命令:
mkfontscale
mkfontdir
fc-cache -fv
查看是否已经安装对应的字体:fc-list
fc-cache -fv 命令⽤来刷新linux的字体缓存,使其⽴刻⽣效
PS:以上所有操作基本都需要root权限

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。