【问题标题】:Understanding character encoding in typical Java web app了解典型 Java Web 应用程序中的字符编码
【发布时间】:2011-02-01 19:50:02
【问题描述】:

一些伪代码:

String a = "A bunch of text"; //UTF-16
saveTextInDb(a); //Write to Oracle VARCHAR(15) column
String b = readTextFromDb(); //UTF-16
out.write(b); //Write to http response

当您将 Java String (UTF-16) 保存到 Oracle VARCHAR(15) 时,Oracle 是否也将其存储为 UTF-16? Oracle VARCHAR 的长度是指 Unicode 字符数(而不是字节数)吗?

当我们将b 写入ServletResponse 时,这是写成UTF-16 还是我们默认转换为另一种编码,如UTF-8?

【问题讨论】:

    标签: java oracle unicode character-encoding


    【解决方案1】:

    Oracle 从数据库中存储(以及稍后检索)Unicode 文本的能力仅依赖于数据库的字符集(通常在数据库创建期间指定)。建议选择 AL32UTF8 作为字符集来存储 CHAR 数据类型(包括 VARCHAR/VARCHAR2)中的 Unicode 文本,因为与 AL16UTF16/ 等其他编码相比,它可以让您访问所有 Unicode 代码点,同时不会占用大量存储空间AL32UTF32。

    假设这已完成,则负责将 UTF-16 编码数据转换为 AL32UTF8 的是 Oracle JDBC 驱动程序。当从数据库读取数据时,也会发生编码之间的这种“自动”转换。为了回答关于 VARCHAR 字节长度的查询,Oracle 中 VARCHAR2 列的定义涉及字节语义 - VARCHAR2(n) 用于定义可以存储 n 个字节的列(这是默认行为,由 NLS_LENGTH_SEMANTICS 参数指定数据库);如果您需要根据字符定义大小,则使用 VARCHAR2(n CHAR)。

    写入 ServletResponse 对象的数据的编码取决于默认字符编码,除非这是通过 ServletResponse.setCharacterEncoding()ServletResponse.setContentType() API 调用指定的。总而言之,对于一个涉及 Oracle 数据库的完整 Unicode 解决方案,人们必须了解

    1. 传入数据的编码(即通过 ServletRequest 对象读取的数据的编码)。这可以通过accept-charset attribute 在 HTML 表单中指定接受的编码来完成。如果编码未知,应用程序可以尝试通过ServletRequest.setCharacterEncoding() 方法将其设置为已知值。此方法不会更改流中现有的字符编码。如果输入流采用 ISO-Latin1,则指定不同的编码很可能会导致引发异常。了解编码很重要,因为如果要将流的内容视为字符基元或字符串,Java 运行时库将需要了解流的原始编码。显然,当您调用 ServletRequest.getParameter 或将处理流并返回 String 对象的类似方法时,这是必需的。解码过程将导致以平台编码(这是 UTF-16)创建字符。
    2. 从流中读取的数据的编码,与在 JVM 中创建的数据相反。这一点非常重要,因为从流中读取的数据的编码无法更改。然而,有一个解码过程将支持编码的字符转换为 UTF-16 字符,只要此类数据作为字符基元或字符串访问。另一方面,可以使用定义的编码创建新的 String 对象。当您将流的内容写入另一个流(例如 HttpServletResponse 对象的输出流)时,这很重要。如果输入流的内容被视为字节序列,而不是字符或字符串,则 JVM 将不进行任何解码操作。这意味着如果未创建中间字符或字符串对象,则不得更改写入输出流的字节。否则,输出流的内容很可能会被相应的解码器错误地解析和解析。简而言之,

      • 如果将字符串对象或字符写入 servlet 的输出流,则必须指定浏览器必须用于解码响应的编码。可以使用适当的编码器对所需响应中指定的字符序列进行编码。
      • 如果正在编写将被解释为字符的字节序列,则必须事先知道要在 HTTP 标头中指定的编码
      • 如果编写的字节序列将被解析为字节序列(用于图像和其他二进制数据),那么编码的概念就无关紧要了。
    3. Oracle 实例的数据库字符集。如前所述,数据将以定义的字符集(对于 CHAR 数据类型)存储在 Oracle 数据库中。 Oracle JDBC 驱动程序负责 CHAR 和 NCHAR 数据类型在 UTF-16 和 AL32UTF8(本例中为数据库字符集)之间的数据转换。当您调用 resultSet.getString() 时,JDBC 驱动程序将返回一个带有 UTF-16 字符的字符串。当您也将数据发送到数据库时,反之亦然。如果使用另一个数据库字符集,则 JDBC 驱动程序会透明地执行附加级别的转换(从 UTF-16 到 UTF-8 再到数据库字符集)。

    【讨论】:

      【解决方案2】:

      ServletResponse 默认使用 ISO 8859-1 (Latin 1)。 UTF-8 是用于需要 Unicode 的 HTTP 响应的最常用编码,但您必须专门设置该编码。

      根据this document,Oracle 可以在数据库中支持 UTF-8 或 UTF-16。您读取/写入 Oracle 的方法将需要使用与数据库设置方式相匹配的适当编码,并将其转换为 Java 内部表示。

      【讨论】:

        【解决方案3】:

        请考虑字符串的“内部表示”,而不是 UTF-16。 Java中的字符串是某种字符,您不关心内部使用哪种编码。如果您与程序外部进行交互,则编码变得相关。在您的示例 saveTextInDb、readTextFromDb 和 write 中执行此操作。每次与外部交换字符串时,都会使用一种编码进行转换。 saveTextInDb(和读取)看起来像自制的方法,至少我不知道它们。所以你应该查一下,这个方法使用了哪种编码。 Writer 的方法 write 总是创建字节,表示与 writer 关联的编码。如果您从 HttpServletResponse 获取 Writer,则关联的编码是用于输出响应的编码(将在标头中发送)。

        response.setEncoding("UTF-8");
        Writer out = response.getWriter();
        

        此代码在没有 Writer 的情况下返回,它将字符串转换为 UTF-8 编码。如果您写入文件,则类似:

        Writer fileout = new OutputStreamWriter(new FileOutputStream(myfile), "ISO8859-1");
        

        如果您访问数据库,您使用的框架应确保与数据库进行一致的字符串交换。

        【讨论】:

          猜你喜欢
          • 2011-09-14
          • 1970-01-01
          • 1970-01-01
          • 2016-08-12
          • 1970-01-01
          • 2016-03-22
          • 2011-06-25
          • 1970-01-01
          • 2015-07-02
          相关资源
          最近更新 更多