当前位置: 首页 > 面试题库 >

如何处理Java编码问题(尤其是xml)?

荀学文
2023-03-14
问题内容

我搜索了有关Java和编码的信息,但没有找到说明如何处理编码和解码字符串时Java出现的公共问题的资源。关于单个错误,有很多特定的问题,但是我没有找到有关该问题的广泛答复/参考指南。主要问题是:

什么是字符串编码?

为什么在Java中我可以读取带有错误字符的文件?

为什么在处理xml时出现y字节UTF-8序列异常的无效字节x?主要原因是什么,以及如何避免它们?


问题答案:

由于Stackoverflow鼓励自我回答,因此我尝试回应自己。

编码是将数据从一种格式转换为另一种格式的过程,此响应我详细介绍了String编码在Java中的工作方式(您可能需要阅读这篇文章,以更全面地了解文本结尾编码)。

介绍

字符串编码/解码是将byte []转换为String的过程,反之亦然。

乍一看,您可能会认为没有问题,但是如果我们对过程进行更深入的了解,可能会出现一些问题。 在最低级别上,信息以字节为单位存储/传输
:文件是字节序列,网络通信通过发送和接收字节来完成。因此,每次您想要读取或写入具有简单可读内容的文件时,或者每次您提交Web表单/阅读Web页面时,都会进行基础编码操作。让我们从Java中的基本String编码操作开始;从字节序列创建字符串。下面的代码将byte
[](字节可能来自文件或套接字)转换为String。

    byte[] stringInByte=new byte[]{104,101,108,108,111};
    String simple=new String(stringInByte);
    System.out.println("simple=" + simple);//prints simple=hello

到目前为止,一切都很“简单”。字节的值取自此处,它显示了一种将字母和数字映射到字节的方法。让我们通过一个简单的要求使样本复杂化:byte
[]包含€(欧元)符号;糟糕,ascii表中没有欧元符号。

这可以粗略地概括为问题的核心,人类可读的字符(以及其他一些必要的字符,例如回车符,换行符等)大于256,即不能仅用一个字节来表示。如果由于某种原因您必须坚持使用单字节表示形式(例如,由于历史原因,第一个编码表仅使用7个字节,则出于空间限制的原因,如果磁盘上的空间有限并且您只为英语人群编写文本文档,则不会需要包含带有重音符号(例如è,ì)的意大利语字母,那么您就很难选择要代表的字符。

选择一种编码就是选择字节和字符之间的映射。

回到欧元示例,并坚持一个字节->映射ISO8859-15编码表的一个字符具有€符号;代表字符串“ hello€”的字节序列如下

byte[] stringInByte1=new byte[]{104,101,108,108,111,32,(byte)164};

您如何“告诉” Java用于转换的编码?字符串具有构造函数

String(byte[] bytes, String charsetName)

这样就可以指定“映射”。如果使用不同的字符集,则会得到不同的输出结果,如下所示:

    byte[] stringInByte1=new byte[]{104,101,108,108,111,32,(byte)164};
    String simple1=new String(stringInByte1,"ISO8859-15");
    System.out.println("simple1=" + simple1);  //prints simple1=hello €

    String simple2=new String(stringInByte1,"ISO8859-1");
    System.out.println("simple2=" + simple2);   //prints simple1=hello ¤

因此,这解释了为什么您读取某些字符并读取不同的字符的原因,用于写入的编码(从String到byte [])与用于读取的编码(从byte
[]到String)不同。同一字节可能以不同的编码映射到不同的字符,因此某些字符可能“看起来很奇怪”。
这些是理解String编码所需的基本概念。让我们把事情复杂化一点。为了实现已创建的多字节编码,可能需要在一个文本文档中表示超过256个符号。

使用多字节编码时,不再有一个字节- >一个字符映射,但是有字节序列->一个字符映射

UTF-8是最著名的多字节编码之一;UTF-8是一种可变长度编码,有些字符用一个字节表示,而另一些字符用一个以上字节表示;

UTF-8与某个一字节编码(例如us7ascii或ISO8859-1)重叠; 它可以看作是一个字节编码的扩展。

让我们来看第一个示例的UTF-8实际应用

    byte[] stringInByte=new byte[]{104,101,108,108,111};
    String simple=new String(stringInByte);
    System.out.println("simple=" + simple);//prints simple=hello

    String simple3=new String(stringInByte, "UTF-8");
    System.out.println("simple3=" + simple3);//also this prints simple=hello

如您所见,尝试执行该代码将打印出问候,即,表示UTF-8和ISO8859-1中的问候的字节是相同的。

但是,如果您尝试使用带有€符号的样本,则会得到一个?

    byte[] stringInByte1=new byte[]{104,101,108,108,111,32,(byte)164};
    String simple1=new String(stringInByte1,"ISO8859-15");
    System.out.println("simple1=" + simple1);//prints simple1=hello

    String simple4=new String(stringInByte1, "UTF-8");
    System.out.println("simple4=" + simple4);//prints simple4=hello ?

表示无法识别字符,并且存在错误。 请注意,即使转换期间发生错误,您也不会例外。

不幸的是,在处理无效字符时,并非所有java类的行为都相同。让我们看看处理xml时会发生什么。

管理XML

在进行示例之前,值得记住的是,在Java中InputStream / OutputStream的读/写字节和Reader / Writer的读/写字符。

让我们尝试以一些不同的方式读取xml的字节序列,即读取文件以获取String与读取文件以获取DOM。

    //Create a xml file
    String xmlSample="<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<specialchars>àèìòù€</specialchars>";
    try(FileOutputStream fosXmlFileOutputStreame= new FileOutputStream("test.xml")) {
        //write the file with a wrong encoding
        fosXmlFileOutputStreame.write(xmlSample.getBytes("ISO8859-15"));
    }

    try (
            FileInputStream xmlFileInputStream= new FileInputStream("test.xml");
            //read the file with the encoding declared in the xml header
            InputStreamReader inputStreamReader= new InputStreamReader(xmlFileInputStream,"UTF-8");
    ) {
        char[] cbuf=new char[xmlSample.length()];
        inputStreamReader.read(cbuf);
        System.out.println("file read with UTF-8=" + new String(cbuf)); 
        //prints
        //file read with UTF-8=<?xml vershtml" target="_blank">ion="1.0" encoding="UTF-8"?>
        //<specialchars>������</specialchars>
    }


    File xmlFile = new File("test.xml");
    DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
    DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
    Document doc = dBuilder.parse(xmlFile);     
    //throws

com.sun.org.apache.xerces.internal.impl.io.MalformedByteSequenceException:3字节UTF-8序列的无效字节2

在第一种情况下,结果是一些奇怪的字符,但没有异常,在第二种情况下,您得到一个异常(无效序列…。)发生异常是因为您正在读取UTF-8序列的三个字节的字符字节的值无效(因为使用UTF-8编码字符的方式)。

棘手的部分是,由于UTF-8与其他编码重叠,所以3字节UTF-8序列的无效字节2出现了“随机”异常(即,仅对于字符由一个以上字节表示的消息),因此在生产中在环境中,错误可能很难跟踪和重现。

利用所有这些信息,我们可以尝试回答以下问题:

为什么在读取/处理xml文件时出现y字节UTF-8序列无效字节x异常?

因为用于写入的编码(上述测试案例中的ISO8859-15)与用于读取的编码(上述测试案例中的UTF-8)不匹配;不匹配可能有一些不同的原因:

  1. 您在字节和char之间进行了一些错误的转换:例如,如果您正在使用InputStream读取文件并将其转换为Reader并将Reader传递给xml库,则必须按照以下代码指定字符集名称(即,您必须知道用于保存文件的编码)

try ( FileInputStream xmlFileInputStream= new FileInputStream("test.xml"); //this is the reader for the xml library (DOM4J, JDOM for example) //UTF-8 is the file encoding if you specify a wrong encoding or you do not apsecify any encoding you may face Invalid byte x of y-byte UTF-8 sequence Exception InputStreamReader inputStreamReader= new InputStreamReader(xmlFileInputStream,"UTF-8"); )

  1. 您将InputStream直接传递到xml库,但是该文件的文件不正确(例如,在第一个管理xml的示例中,标头声明UTF-8,但实际编码为ISO8859-15。 只需将文件还不够;必须使用标头中使用的编码保存文件。

  2. 您正在使用创建时未指定编码器的阅读器读取文件,并且平台编码与文件编码不同:

    FileReader fileReader=new FileReader("text.xml");
    

这导致一个方面,至少对我来说,这是java中大多数String编码问题的根源:使用默认平台编码

您打电话的时候

"Hello €".getBytes();

在不同的操作系统上可以获得不同的结果;这是因为在Windows上,默认编码为Windows-1252,而在Linux上,默认编码为UTF-8;€char的编码方式不同,因此您不仅获得了不同的字节,而且还获得了不同的数组大小:

    String helloEuro="hello €";
    //prints hello euro byte[] size in iso8859-15 = 7
    System.out.println("hello euro byte[] size in iso8859-15 = " + helloEuro.getBytes("ISO8859-15").length);
    //prints hello euro byte[] size in utf-8 = 9
    System.out.println("hello euro byte[] size in utf-8 = " + helloEuro.getBytes("UTF-8").length);

在遇到编码问题时,首先要使用String.getBytes()或新的String(byte [] …)而不指定编码是首先要做的检查

第二个是检查您是使用FileReader还是FileWriter读取或写入文件;在这两种情况下,文档均指出:

此类的构造函数假定默认字符编码和默认字节缓冲区大小是可接受的

与String.getBytes()一样,使用读取器/写入器在不同平台上读取/写入相同文件且未指定字符集可能会由于不同的默认平台编码而导致字节序列不同

正如javadoc建议的那样,解决方案是使用OutputStreamReader / OutputStreamWriter,该输出将OutputStream
/ InputStream和一个字符集规范包装在一起。

关于某些xml库如何读取XML内容的最后说明:

  1. 如果您通过阅读器,则库将依赖阅读器进行编码(即,它不检查xml标头中的内容),并且与编码无关,因为它正在读取字符而不是字节。

  2. 如果传递InputStream或File库依赖于xml标头进行编码,则它可能会引发一些编码异常

数据库

处理数据库时可能会出现另一个问题。创建数据库时,它具有用于保存varchar和string列(作为clob)的编码属性。如果使用8位编码(例如ISO8859-15)创建数据库,则当您尝试插入编码不允许的字符时,可能会出现问题。db中保存的内容可能与Java级别指定的字符串不同,因为在Java中,字符串在UTF-16的内存中表示,比在数据库级别指定的字符串“宽”。最简单的解决方案是:使用UTF-8编码创建数据库。

网络, 这是一个很好的起点。

如果您觉得缺少什么,请随时在评论中要求更多。



 类似资料:
  • 问题内容: 如何从现有的Java代码生成UML图(尤其是序列图)? 问题答案: ObjectAid UML资源管理器 是我用的。它很容易从存储库安装: 并生成非常漂亮的UML图: 网站说明: ObjectAid UML Explorer与其他UML工具不同。它使用UML表示法来显示现有代码的图形表示,该图形表示与文本编辑器一样准确和最新,同时非常易于使用。几个独特的功能使之成为可能: 你的源代码和

  • 我遇到了这个问题: 您将获得一个由 n 个不同的整数 a0、a1、. . .an−1.在每次迭代中,您选择最大数字并将其删除,取消最大数字的成本是其左侧的数字数。重复此 n 次。给定ai实现一个O(n log n)算法来计算n次迭代的总成本。 我知道我们必须使用BST来解决这个问题,因为它是O(N log N),但是,我不知道该怎么办。 我想我可以在hashMap中存储值和索引,当删除完成后,我们

  • 为了理解我在某个地方读到的一句话,我真的在挠头:“我们在分形中放大的越多,我们最可能需要执行的迭代就越多”。 到目前为止,我还没有找到任何数学/学术论文证明这一说法。我还设法找到了一个计算mandelbrot集的小代码,取自这里:http://warp.povusers.org/Mandelbrot/但是,我们无法理解缩放是如何影响迭代的。 谢谢!

  • 本文向大家介绍如何处理Python3.4 使用pymssql 乱码问题,包括了如何处理Python3.4 使用pymssql 乱码问题的使用技巧和注意事项,需要的朋友参考一下 在项目中发现这样一个问题:sqlserver数据库编码为gbk,使用python3.4+pymssql 查询,中文乱码,经过一番思考问题解决,下面把解决办法分享给大家: 接下来给大家介绍python 使用pymssql连接s

  • 问题内容: 我尝试使用java.io.FileReader读取一些文本文件并将其转换为字符串,但是我发现结果编码错误并且根本不可读。 这是我的环境: Windows 2003,操作系统编码:CP1252 Java 5.0 我的文件是UTF-8编码或CP1252编码的,其中一些(UTF-8编码的文件)可能包含中文(非拉丁)字符。 我使用以下代码来完成我的工作: 上面的代码不起作用。我发现FileRe

  • 问题内容: 我正在使用Java Mail API,并且正在尝试通过Gmail的SMTP发送电子邮件。我的程序如何工作:java.util.Scanner类用于获取用户输入- 我正在询问用户要在邮件发送类中使用的各种参数;它执行以下操作: 只要我使用ASCII符号/字符,一切都可以正常工作。但是每当我想使用“国家特定”字符(例如[õäöü])时,我都会得到一堆看起来很奇怪的符号… 到目前为止,我使用