最近需要从Java中输出UTF-8编码的XML文件,遇到了两次中文乱码问题。一是奇数个汉字出现乱码,二是写入文件的实际编码与XML声明的编码不符。经过几番折腾,终于解决这两个问题,也对Java的字符编码加深了了解。
问题重现
以下面XML为例:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<test>一一一</test>
1
2
<?xmlversion="1.0"encoding="UTF-8"standalone="no"?>
<test>一一一</test>
生成XML代码如下:
Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
Element elem = doc.createElement("test");
elem.setTextContent("一一一");
doc.appendChild(elem);
DOMSource source = new DOMSource(doc);
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
Transformer transformer = TransformerFactory.newInstance().newTransformer();
StreamResult result = new StreamResult(bytes);
Properties properties = transformer.getOutputProperties();
properties.setProperty(OutputKeys.INDENT, "yes");
properties.setProperty(OutputKeys.ENCODING, "UTF-8");
properties.setProperty("{http://xml.apache.org/xslt}indent-amount", "2");
transformer.setOutputProperties(properties);
transformer.transform(source, result);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Documentdoc=DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
Elementelem=doc.createElement("test");
elem.setTextContent("一一一");
doc.appendChild(elem);
DOMSourcesource=newDOMSource(doc);
ByteArrayOutputStreambytes=newByteArrayOutputStream();
Transformertransformer=TransformerFactory.newInstance().newTransformer();
StreamResultresult=newStreamResult(bytes);
Propertiesproperties=transformer.getOutputProperties();
properties.setProperty(OutputKeys.INDENT,"yes");
properties.setProperty(OutputKeys.ENCODING,"UTF-8");
properties.setProperty("{http://xml.apache.org/xslt}indent-amount","2");
transformer.setOutputProperties(properties);
transformer.transform(source,result);
至此,XML已存储至bytes中。
接下来我想XML保存至文件,想当然地写了以下代码:
String xmlStr = bytes.toString();
FileWriter writer = new FileWriter("test.xml");
writer.write(xmlStr);
writer.close();
1
2
3
4
5
StringxmlStr=bytes.toString();
FileWriterwriter=newFileWriter("test.xml");
writer.write(xmlStr);
writer.close();
不料得到的XML文件中文部分出现乱码,且XML标签的一个字符'
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<test>丿丿/test>
1
2
<?xmlversion="1.0"encoding="UTF-8"standalone="no"?>
<test>丿丿/test>
该问题只出现在中文字符为奇数个时,原因见参考链接1。
经过一番修改,写出如下代码:
String xmlStr = bytes.toString("UTF-8");
FileWriter writer = new FileWriter("test.xml");
writer.write(xmlStr);
writer.close();
1
2
3
4
5
StringxmlStr=bytes.toString("UTF-8");
FileWriterwriter=newFileWriter("test.xml");
writer.write(xmlStr);
writer.close();
得到另一种乱码:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<test>һһһ</test>
1
2
<?xmlversion="1.0"encoding="UTF-8"standalone="no"?>
<test>һһһ</test>
经观察,该文件实际为GB编码,因Notepad++根据XML的encoding属性自动识别并显示为UTF-8造成乱码,将该文件以GB编码显示正常。
搜索得到原因,Java的FileWriter以平台默认编码写入文件。于是修改代码如下:
String xmlStr = bytes.toString("UTF-8");
FileOutputStream fos = new FileOutputStream("test.xml");
OutputStreamWriter writer = new OutputStreamWriter(fos, "UTF-8");
writer.write(xmlStr);
writer.close();
1
2
3
4
5
StringxmlStr=bytes.toString("UTF-8");
FileOutputStreamfos=newFileOutputStream("test.xml");
OutputStreamWriterwriter=newOutputStreamWriter(fos,"UTF-8");
writer.write(xmlStr);
writer.close();
或
String xmlStr = bytes.toString("UTF-8");
FileOutputStream fos = new FileOutputStream("test.xml");
fos.write(bytes.toByteArray());
fos.close();
1
2
3
4
5
StringxmlStr=bytes.toString("UTF-8");
FileOutputStreamfos=newFileOutputStream("test.xml");
fos.write(bytes.toByteArray());
fos.close();
至此得到无乱码的XML文件。
问题分析
Transformer得到的bytes为UTF-8的二进制数据,若使用toString()会使用系统默认编码对此数据进行编码并转为字符串(Unicode存储),造成奇数个汉字乱码问题。使用toString(“UTF-8”)指定编码格式即可解决此问题。
使用FileWriter将字符串写入文件时仍会将字符串(Unicode存储)转换为系统默认编码写入文件,这就造成文件实际编码格式与XML中声明的编码格式不同。使用OutputStreamWriter指定编码格式,或者将UTF-8的二进制数据用FileOutputStream直接写入文件,即可解决此问题。