首页>>程序代码>>JDom 和dom4解析XML输出的中文乱码的解决方案
JDom 和dom4解析XML输出的中文乱码的解决方案
来源: 时间: 2014-11-05 16:53:40


 Dom 输出 Xml 文件,当使用字符编码 GBK 时正常,而输出 UTF-8 时乱码。

  完 美的 解决方法从辟谣开始:

   1)JDOM 是否生成 UTF-8 的文件与 Format 是否设置无关,只有输出其他字符编码才需要设置,见下面的注释。

   2)JDOM 输出 UTF-8 文件乱码的根本原因并非在 JDOMAPI ,而是在 JDK 。

  具体描述:

   JDOM 的输出类 XMLOutputter 有两个 output 接口,除了都具有一个 Document 参数外,分别接受 Writer 和 OutputStream 参数。

  这给我们一个错觉,两个接口可以任意使用。

  首先我们用 output(doc,System.out) 来做测试,此时得到乱码,

  然后我们改为 output(doc,new PrintWriter(System.out)) 来测试,输出不是乱码,

  也就是说在控制台的时候一定要用一个 Writer 接口包装一下。

  然后我们用 output(doc,new FileWriter(path)) 来做测试,结果却得到乱码,

  然后我们改为 output(doc,new FileOutputStream(path)) 来测试,输出不是乱码,

  也就是说在输出文件的时候一定要用一个 OutputStream 接口包装一下。

  疯了吧 ? 呵呵,很搞笑是吧。经过到 JDOM 的源码中调试,发现没有任何问题,问题出在了 JDK 里面。

   JDK 内的对应接口处理:

   1)PrintWriter 类有参数为 OutputStream 的构造方法,因此可以从 System.out 包装到 PrintWriter

   2)FileWriter 类没有参数为 OutputStream 的构造方法,因此不能从 FileOutputStream 包装到 FileWriter

   3) 如果 PrintWriter 类用了参数为 Writer 的构造方法 (Writer 实现为 FileWriter) ,最后输出也是乱码

   4) 如果用一个 FileOutputStream 来包装一个控制台输出,也是乱码

  因此,对于 JDK 内的各种输出体系,各种 InputStream 、 OutputStream 、 reader 和 writer 要充分认识,否则极容易出现一些意想不到的问题。

  测试的 JDOM 版本: 1.0 、 1.1

  测试代码:

      import java.io.File;
   import java.io.FileOutputStream;
   import java.io.FileWriter;
   import java.io.PrintWriter;
   import java.util.HashMap;
   import org.jdom.Document;
   import org.jdom.Element;
   import org.jdom.output.Format;
   import org.jdom.output.XMLOutputter;
   public class BuildXML {
   public static void main(String[] args) throws Exception{
   File xmlfile=new File("C:\\EditTemp\\xml\\abc.xml");
   // 中文问题  //GBK  是没有问题的,但 UTF-8 就是有问题的
   // 原因:
   //1) 对于磁盘文件,必须使用输出流  FileOutputStream
   // FileWriter out=new FileWriter(xmlfile); 会导致乱码
   //2) 对于控制台输出,则必须使用 PrintWriter ,如果直接使用 System.out 也会出现乱码
   // PrintWriter out=new PrintWriter(System.out);
   FileOutputStream out=new FileOutputStream(xmlfile);
   Element eroot=new Element("root");
   eroot.addContent((new Element("code")).addContent(" 代码 "));
   eroot.addContent((new Element("ds")).addContent(" 数据源 "));
   eroot.addContent((new Element("sql")).addContent(" 检索 sql"));
   eroot.addContent((new Element("order")).addContent(" 排序 "));
   Document doc=new Document(eroot);
   XMLOutputter outputter = new XMLOutputter();
   // 如果不设置 format ,仅仅是没有缩进, xml 还是 utf-8 的,因此 format 不是必要的
   Format f = Format.getPrettyFormat();
   //f.setEncoding("UTF-8");//default=UTF-8
   outputter.setFormat(f);
   outputter.output(doc, out);
   out.close();
   }
   }

  Dom4j 编码问题彻底解决

   这几天开始学习 dom4j ,在网上找了篇文章就开干了,上手非常的快,但是发现了个问题就是无法以 UTF-8 保存 xml 文件,保存后再次读出的时候会报 “Invalid byte 2 of 2-byte UTF-8 sequence.” 这样一个错误,检查发现由 dom4j 生成的这个文件,在使用可正确处理 XML 编码的任何的编辑器中中文成乱码,从记事本查看并不会出现乱码会正确显示中文。让我很是头痛。试着使用 GBK 、 gb2312 编码来生成的 xml 文件却可以正常的被解析。因此怀疑的 dom4j 没有对 utf-8 编码进行处理。便开始查看 dom4j 的原代码。终于发现的问题所在,是自己程序的问题。
   在 dom4j 的范例和网上流行的《 DOM4J 使用简介》这篇教程中新建一个 xml 文档的代码都类似如下

public void createXML(String fileName) {  
       Document doc = org.dom4j.DocumentHelper.createDocument();  
       Element root = doc.addElement("book");  
       root.addAttribute("name", "我的图书");  
       Element childTmp;  
       childTmp = root.addElement("price");  
       childTmp.setText("21.22");  
       Element writer = root.addElement("author");  
       writer.setText("李四");  
       writer.addAttribute("ID", "001");  
       try {  
           org.dom4j.io.XMLWriter xmlWriter = new org.dom4j.io.XMLWriter(  
                   new FileWriter(fileName));  
           xmlWriter.write(doc);  
           xmlWriter.close();  
       }  
       catch (Exception e) {  
           System.out.println(e);  
       }  
   }  
 

   在上面的代码中输出使用的是 FileWriter 对象进行文件的输出。这就是不能正确进行文件编码的原因所在, java 中由 Writer 类继承下来的子类没有提供编码格式处理,所以 dom4j 也就无法对输出的文件进行正确的格式处理。这时候所保存的文件会以系统的默认编码对文件进行保存,在中文版的 window 下 java 的默认的编码为 GBK ,也就是所虽然我们标识了要将 xml 保存为 utf-8 格式但实际上文件是以 GBK 格式来保存的,所以这也就是为什么能够我们使用 GBK 、 GB2312 编码来生成 xml 文件能正确的被解析,而以 UTF-8 格式生成的文件不能被 xml 解析器所解析的原因。
   好了现在我们找到了原因所在了,我们来找解决办法吧。首先我们看看 dom4j 是如何实现编码处理的

public XMLWriter(OutputStream out) throws UnsupportedEncodingException {  
        //System.out.println("In OutputStream");  
        this.format = DEFAULT_FORMAT;  
        this.writer = createWriter(out, format.getEncoding());  
        this.autoFlush = true;  
       namespaceStack.push(Namespace.NO_NAMESPACE);  
    }  
    public XMLWriter(OutputStream out, OutputFormat format) throws UnsupportedEncodingException {  
        //System.out.println("In OutputStream,OutputFormat");  
        this.format = format;  
        this.writer = createWriter(out, format.getEncoding());  
        this.autoFlush = true;  
       namespaceStack.push(Namespace.NO_NAMESPACE);  
    }  
    /**
     * Get an OutputStreamWriter, use preferred encoding.
     */  
    protected Writer createWriter(OutputStream outStream, String encoding) throws UnsupportedEncodingException {  
        return new BufferedWriter(  
            new OutputStreamWriter( outStream, encoding )  
        );  
    }  
 

由上面的代码我们可以看出 dom4j 对编码并没有进行什么很复杂的处理,完全通过 java 本身的功能来完成。所以我们在使用 dom4j 的来生成我们的 XML 文件时不应该直接为在构建 XMLWriter 时,不应该直接为其赋一个 Writer 对象,而应该通过一个 OutputStream 的子类对象来构建。也就是说在我们上面的代码中,不应该用 FileWriter 对象来构建 xml 文档,而应该使用 FileOutputStream 对象来构建所以将代码修改入下:


public void createXML(String fileName) {  
        Document doc = org.dom4j.DocumentHelper.createDocument();  
        Element root = doc.addElement("book");  
        root.addAttribute("name", "我的图书");  
        Element childTmp;  
        childTmp = root.addElement("price");  
        childTmp.setText("21.22");  
        Element writer = root.addElement("author");  
        writer.setText("李四");  
        writer.addAttribute("ID", "001");  
        try {  
            //注意这里的修改  
            org.dom4j.io.XMLWriter xmlWriter = new org.dom4j.io.XMLWriter(  
                    new FileOutputStream(fileName));  
            xmlWriter.write(doc);  
            xmlWriter.close();  
        }  
        catch (Exception e) {  
            System.out.println(e);  
        }  
    }  

匹配xml中非法字符的正则表达式:[\\x00-\\x08\\x0b-\\x0c\\x0e-\\x1f]

例子:str.replaceAll("[\\x00-\\x08\\x0b-\\x0c\\x0e-\\x1f]", "");

上一篇:Spring 中Velocity模板applicationContext.xml配置和使用实例 下一篇:HttpURLConnection发送post请求 模拟http请求