@liayun
2016-06-27T20:16:09.000000Z
字数 3893
阅读 4666
JavaWeb
dom4j对xml文档进行增删改查后,将内存中的Document对象保存到持久化设备生成XML文件后,XML文件无法正常打开,出现乱码。也有可能会报异常:Xml:org.dom4j.DocumentException: 2字节的UTF-8序列的2无效。
book.xml如下:
<?xml version="1.0" encoding="UTF-8"?>
<书架>
<书>
<书名>Java就业培训教程</书名>
<作者>张孝祥</作者>
<售价>109元</售价>
</书>
<书>
<书名 name="xxxx">JavaScript网页开发</书名>
<作者>黎活明</作者>
<售价>28.00元</售价>
</书>
</书架>
需求:在第一本书中添加一个新的售价:<售价>209元</售价>
。
使用Dom4j API将内存中的Document对象写入到XML文件中,代码如下:
XMLWriter writer = new XMLWriter(new FileWriter("book.xml"));
writer.write(document);
writer.close();
在上面的代码中输出使用的是FileWriter对象进行文件的输出。这就是不能正确进行文件编码的原因所在,Java中由Writer类继承下来的子类没有提供编码格式处理,所以dom4j也就无法对输出的文件进行正确的格式处理。这时候所保存的文件会以系统的默认编码对文件进行保存,在中文版的window下Java的默认的编码为GBK,也就是说虽然我们标识了要将xml保存为UTF-8格式,但实际上文件是以GBK格式来保存的,即把内存中的Document对象(UTF-8编码,encoding=“UTF-8”)转换成GBK编码的字符流存入持久化设备,此时文件的真正格式为GBK,encoding依旧为UTF-8,故出现乱码。
图示:
解决办法:将Document对象写入到XML文件中使用的FileWriter改为转换流写入。
XMLWriter writer = new XMLWriter(new OutputStreamWriter(new FileOutputStream("src/book.xml"), "UTF-8"));
writer.write(document);
writer.close();
转换流指定了编码格式把内存中的Document对象(UTF-8编码,encoding=“UTF-8”)转换成字节流,如果指定的编码格式是GBK,则此时文件的真正格式为GBK,encoding依旧为UTF-8,故出现乱码。如果将指定的编码格式指定为UTF-8格式,不会出现乱码。
缺点:虽然解决了乱码问题,但是这样会改变原xml文档的编码模式,即改为:encoding="UTF-8"。
假设此时xml文档的编码格式是GBK,写回文件的编码格式也是GBK。代码如下:
XMLWriter writer = new XMLWriter(new OutputStreamWriter(new FileOutputStream("src/book.xml"), "GBK"));
writer.write(document);
writer.close();
xml文档仍然出现乱码,原因:写入数据时使用的是GBK编码表,写入后xml文件头的编码格式被改为了UTF-8——这是因为,文档在写回时,文档的头编码格式是根据内存中Document的编码(UTF-8)设定的,则文档解码时使用的是UTF-8来解码,会出现乱码。
解决方法:使用dom4j提供方的输出格式化类OutputFormat。代码如下:
OutputFormat format = OutputFormat.createPrettyPrint();
format.setEncoding("GBK");
XMLWriter writer = new XMLWriter(new FileOutputStream("src/book.xml"), format); // 该方法是直接通过字节流来处理,所以不会存在编码解码问题,因为本身就是字节。
writer.write(document);
writer.close();
结论一:通过上述分析,可以说明数据写入时使用的码表和解码时使用的码表不同就会造成乱码问题。所以只有输出流编码方式为UTF-8 的方式下才不会出现乱码,但所有方式都会改变原xml文档的编码表。改为:encoding="UTF-8"。
为了解决保持xml文档编码表不变,dom4j引入了格式化输出器,通过格式化输入器,能够实现按指定的编码表来编码,也可以把xml文档的码表改为指定的码表。
OutputFormat format = OutputFormat.createPrettyPrint();
format.setEncoding("GBK"); // 给格式化输出器指定一个码表,xml文档什么编码,格式化输出器就是什么编码
XMLWriter writer = new XMLWriter(new FileOutputStream("src/book.xml"), format);
writer.write(document);
writer.close();
生成一个漂亮的格式化输出器(紧缩的格式化输出器也可以)对象format,设置其编码格式为自己想要的任意格式。输出时,format会先把内存中的Document对象的encoding属性值改为设置的编码格式,然后再把Document对象按设置的编码格式格式化字节流。此时XML文件的真正格式和encoding属性值一致,不会出现乱码。
注:构造XMLWriter对象的输出流对象必须是字节流对象,如果是字符流对象又会导致Document对象多次按不同编码转换,可能又会出现乱码问题。分两种情况讨论:
情况一:格式化输出器指定编码与xml文档编码模式相同时(都是GBK的情况下)。
OutputFormat format = OutputFormat.createPrettyPrint();
format.setEncoding("GBK");
使用字符输出流写入:
XMLWriter writer = new XMLWriter(new FileWriter("src/book.xml"),format);
使用字节输出流写入:
XMLWriter writer = new XMLWriter(new FileOutputStream("src/book.xml"), format);
这两种写入方式都正确,但有区别:前者是根本就不会使用格式化输出器指定的GBK编码表编码,而是查找平台默认的编码表来编码;后者会按格式化输出器指定的编码表来编码,更新后xml文档的编码表不变。
情况二:xml文档的编码表(GBK)和格式化输出器指定码表(UTF-8)不相同时:
OutputFormat format = OutputFormat.createPrettyPrint();
format.setEncoding("UTF-8");
使用字符输出流写入:
XMLWriter writer = new XMLWriter(new FileWriter("src/book.xml"),format);
使用字节输出流写入:
XMLWriter writer = new XMLWriter(new FileOutputStream("src/book.xml"), format);
使用字节输入流写入正确,使用字符输入流会出现乱码,但两者都会将原book.xml文档的编码表改为encoding="UTF-8"。
结论二:通过格式化输出器来指定码表的方式写入数据后,都会将原xml文档的编码表更改为指定的码表。因此只要我们指定的码表与xml文档的码表相同,在写入数据时都不会更改xml文档的默认码表。但是如果使用字符输入流写入数据时,一定要保证平台默认码表与xml文档码表一致,这样写入数据时不会出现乱码。在任何情况下使用字节输入流写入数据都不会出现乱码。
这里我参考如下: