20、Java字符集编码与解码详解

Java字符集编码与解码详解

在Java编程中,字符集(Charset)的处理是一个重要的部分,它涉及到字符与字节流之间的转换。下面我们将详细探讨字符集的相关操作,包括编码和解码的过程。

1. Charset基础方法

在JDK 1.4中,Charset的实现对于所有映射到相同字符集的 forName() 调用都会返回相同的对象句柄。这意味着使用 == 运算符比较Charset对象引用似乎和使用 equals() 方法效果相同,但为了避免未来实现改变导致代码出错,建议始终使用 equals() 方法来测试相等性。

Charset实现了 Comparable 接口,提供了 compareTo() 方法。该方法基于Charset对象的规范名称进行比较,并且忽略大小写。如果对一组Charset对象进行排序,它们将按规范名称排序,忽略大小写。需要注意的是,如果向 compareTo() 方法传递非Charset对象,会抛出 ClassCastException

hashCode() 方法返回 name() 方法返回的字符串的哈希码,这意味着哈希码是区分大小写的。 toString() 方法返回规范名称。虽然大多数时候, hashCode() toString() 方法的实现并不那么重要,但由于Charset类重写了它们,在使用哈希映射或调试时可能会产生影响。

2. 字符集编码器(Charset Encoders)

字符集由编码字符集和相关的编码方案组成, CharsetEncoder CharsetDecoder 类实现了转码方案。

以下是Charset类的部分API:

public abstract class Charset implements Comparable {
    // 这是部分API列表
    public boolean canEncode();
    public abstract CharsetEncoder newEncoder();
    public final ByteBuffer encode (CharBuffer cb);
    public final ByteBuffer encode (String str);
}
  • canEncode() :该方法用于指示此字符集是否允许编码。几乎所有字符集都支持编码,但有些具有自动检测编码方案的解码器的字符集通常只支持解码,不支持创建自己的编码。如果此Charset对象能够对字符序列进行编码, canEncode() 方法返回 true ;否则返回 false 。如果返回 false ,则不应调用上述其他三个方法,否则会抛出 UnsupportedOperationException
  • newEncoder() :调用此方法会返回一个 CharsetEncoder 对象,该对象能够使用与字符集关联的编码方案将字符序列转换为字节序列。
  • encode() :Charset类的两个 encode() 方法是便利方法,它们使用与字符集关联的编码器的默认值进行编码。这两个方法都返回一个新的 ByteBuffer 对象,其中包含与给定 String CharBuffer 的字符对应的编码字节序列。

以下是 CharsetEncoder 类的部分API:

package java.nio.charset;

public abstract class CharsetEncoder {
    public final Charset charset();
    public final float averageBytesPerChar();
    public final float maxBytesPerChar();

    public final CharsetEncoder reset();
    public final ByteBuffer encode (CharBuffer in) throws CharacterCodingException;
    public final CoderResult encode (CharBuffer in, ByteBuffer out, boolean endOfInput);
    public final CoderResult flush (ByteBuffer out);

    public boolean canEncode (char c);
    public boolean canEncode (CharSequence cs);

    public CodingErrorAction malformedInputAction();
    public final CharsetEncoder onMalformedInput (CodingErrorAction newAction);
    public CodingErrorAction unmappableCharacterAction();
    public final CharsetEncoder onUnmappableCharacter (CodingErrorAction newAction);

    public final byte [] replacement();
    public boolean isLegalReplacement (byte[] repl);
    public final CharsetEncoder replaceWith (byte[] newReplacement);
}

CharsetEncoder 对象是一个有状态的转换引擎,字符输入,字节输出。完成一次转换可能需要多次调用编码器,编码器会在调用之间记住转换状态。

  • 获取编码器信息的方法
  • charset() :返回与编码器关联的Charset对象。
  • averageBytesPerChar() :返回表示对集合中的一个字符进行编码所需的平均字节数的浮点值。这个值可能是小数,因为编码算法在编码字符时可能跨越字节边界,或者某些字符可能比其他字符编码成更多的字节(如UTF - 8)。此方法可用于确定包含给定字符序列编码字节所需的 ByteBuffer 的大致大小。
  • maxBytesPerChar() :指示对集合中的单个字符进行编码所需的最大字节数,也是一个浮点值。该方法可用于确定最坏情况下的输出缓冲区大小。

  • 编码过程

  • 重置编码器 :调用 reset() 方法重置编码器的状态,为生成编码字节流做准备。新创建的 CharsetEncoder 对象不需要重置,但重置也不会有问题。
  • 提供字符 :调用 encode() 方法零次或多次,将 endOfInput 参数设置为 false ,表示可能还有更多字符。字符将从给定的 CharBuffer 中消耗,编码后的字节序列将追加到提供的 ByteBuffer 中。返回时,输入 CharBuffer 可能未完全清空,输出 ByteBuffer 可能已满,或者编码器可能需要更多输入才能完成多字符翻译。在重新填充输入缓冲区之前,需要对其进行压缩。
  • 结束输入 :最后一次调用 encode() 方法,将 endOfInput 参数设置为 true 。提供的 CharBuffer 可能包含要编码的额外字符或为空。重要的是最后一次调用时 endOfInput true ,这让编码引擎知道没有更多输入,从而能够检测格式错误的输入。
  • 刷新编码器 :调用 flush() 方法完成任何未完成的编码并输出所有剩余字节。如果输出 ByteBuffer 中空间不足,可能需要多次调用此方法。

encode() 方法在消耗完所有输入、输出 ByteBuffer 已满或检测到编码错误时返回,会返回一个 CoderResult 对象来指示发生了什么。 CoderResult 对象可能表示以下几种结果条件:
- Underflow :表示需要更多输入。可能是输入 CharBuffer 内容已耗尽,或者如果它不为空,剩余字符在没有额外输入的情况下无法处理。 CharBuffer 的位置会更新以记录编码器消耗的字符。可以向 CharBuffer 中填充更多要编码的字符,然后再次调用 encode() 方法继续编码。如果编码完成,使用空的 CharBuffer endOfInput true 调用 encode() 方法,然后调用 flush() 方法确保所有字节都已发送到 ByteBuffer 。Underflow条件总是返回相同的对象实例 CharsetEncoder.UNDERFLOW ,可以使用 == 运算符测试是否为Underflow。
- Overflow :表示编码器已填满输出 ByteBuffer ,需要生成更多编码输出。输入 CharBuffer 对象可能已耗尽,也可能未耗尽。这是正常情况,不表示错误。应该清空 ByteBuffer ,但不要干扰 CharBuffer ,因为其位置已更新,然后再次调用 encode() 方法,直到得到Underflow结果。Overflow返回统一实例 CharsetEncoder.OVERFLOW ,可直接用于相等性比较。
- Malformed input :编码时,通常表示字符包含无效的16位Unicode字符值;解码时,表示解码器遇到了无法识别的字节序列。返回的 CoderResult 实例不是单例引用。
- Unmappable character :表示编码器无法将字符或字符序列映射为字节。例如,使用ISO - 8859 - 1编码,但输入 CharBuffer 包含非拉丁Unicode字符。解码时,解码器理解输入字节序列,但不知道如何创建相应的字符。

以下是测试是否可以编码的方法:

package java.nio.charset;

public abstract class CharsetEncoder {
    // 这是部分API列表
    public boolean canEncode (char c);
    public boolean canEncode (CharSequence cs);
}

这两个 canEncode() 方法返回一个布尔结果,指示编码器是否能够对给定输入进行编码。这两个方法都会将输入编码到临时缓冲区中,会导致编码器内部状态发生变化,因此在编码过程中不应调用这些方法,建议在开始编码过程之前使用这些方法检查输入。第二个 canEncode() 方法接受 CharSequence 类型的参数,任何实现 CharSequence 接口的对象(如 CharBuffer String StringBuffer )都可以传递给该方法。

以下是处理编码错误的方法:

public abstract class CharsetEncoder {
    // 这是部分API列表
    public CodingErrorAction malformedInputAction();
    public final CharsetEncoder onMalformedInput (CodingErrorAction newAction);
    public CodingErrorAction unmappableCharacterAction();
    public final CharsetEncoder onUnmappableCharacter (CodingErrorAction newAction);
    public final byte [] replacement();
    public boolean isLegalReplacement (byte[] repl);
    public final CharsetEncoder replaceWith (byte[] newReplacement);
}

CoderResult 对象可以从 encode() 方法返回,指示编码字符序列时出现问题。有两种定义的编码错误条件:格式错误(Malformed)和不可映射(Unmappable)。编码器实例可以配置为对每种错误条件采取不同的操作。 CodingErrorAction 类封装了出现这些条件时可以采取的可能操作,它是一个简单的类型安全枚举,包含自身的静态命名实例,定义了三个公共字段:
- REPORT :创建 CharsetEncoder 时的默认操作,表示应通过返回 CoderResult 对象来报告编码错误。
- IGNORE :表示应忽略编码错误,将任何错误输入视为不存在。
- REPLACE :通过丢弃错误输入并输出当前为该 CharsetEncoder 定义的替换字节序列来处理编码错误。

malformedInputAction() 方法返回当前对格式错误输入采取的操作,调用 onMalformedInput() 方法可以设置从那时起要使用的 CodingErrorAction 值。类似地,有一对方法用于设置不可映射字符的错误操作,并返回 CharsetEncoder 对象句柄,允许链式调用。

replacement() 方法用于获取当前的替换字节序列,如果未设置自己的替换序列,将返回默认值。 isLegalReplacement() 方法用于测试替换序列的合法性,替换字节序列必须是该字符集的有效编码。 replaceWith() 方法用于设置新的替换序列,传递的字节序列必须是合法的替换值,否则会抛出 IllegalArgumentException

3. CoderResult类

CoderResult 对象由 CharsetEncoder CharsetDecoder 对象返回,以下是其部分API:

package java.nio.charset;

public class CoderResult {
    public static final CoderResult OVERFLOW;
    public static final CoderResult UNDERFLOW;

    public boolean isUnderflow();
    public boolean isOverflow();
    public boolean isError();
    public boolean isMalformed();
    public boolean isUnmappable();

    public int length();

    public static CoderResult malformedForLength (int length);
    public static CoderResult unmappableForLength (int length);
    public void throwException() throws CharacterCodingException;
}

对于每个Underflow和Overflow条件,都会返回 CoderResult 的统一实例,可以直接使用 == 运算符将 CoderResult 对象与这些公共字段进行比较。 isUnderflow() isOverflow() 方法不被视为错误,如果其中任何一个方法返回 true ,则无法从 CoderResult 对象获取更多信息。 isMalformed() isUnmappable() 方法表示错误条件, isError() 方法是一个便利方法,如果这两个方法中的任何一个返回 true ,则 isError() 返回 true

如果 CoderResult 实例表示错误条件, length() 方法会告诉你错误输入序列的长度。对于正常的Underflow/Overflow条件,没有关联的长度。如果在不表示错误的 CoderResult 实例上调用 length() 方法( isError() 返回 false ),会抛出 UnsupportedOperationException

CoderResult 类还提供了三个便利方法, malformedForLength() unmappableForLength() 方法分别返回一个 isMalformed() isUnmappable() 返回 true CoderResult 实例,并且其 length() 方法返回提供的值。这些工厂方法对于给定的长度总是返回相同的 CoderResult 实例。

在某些情况下,抛出异常比传递 CoderResult 对象更合适。例如, CharsetEncoder 类的一体化 encode() 方法在遇到编码错误时会抛出异常。 throwException() 方法是一个便利方法,会抛出相应的 CharacterCodingException 子类,具体如下表所示:
| Result type | Exception |
| — | — |
| isUnderflow() | BufferUnderflowException |
| isOverflow() | BufferOverflowException |
| isMalformed() | MalformedInputException |
| isUnmappable() | UnmappableCharacterException |

4. 字符集解码器(Charset Decoders)

字符集解码器是编码器的逆过程,它将由特定编码方案编码的字节序列转换为16位Unicode字符序列。和 CharsetEncoder 一样, CharsetDecoder 也是有状态的转换引擎,由于方法调用之间会记住状态,因此都不是线程安全的。

以下是 CharsetDecoder 类的部分API:

package java.nio.charset;

public abstract class CharsetDecoder {
    public final Charset charset();
    public final float averageCharsPerByte();
    public final float maxCharsPerByte();

    public boolean isAutoDetecting();
    public boolean isCharsetDetected();
    public Charset detectedCharset();

    public final CharsetDecoder reset();
    public final CharBuffer decode (ByteBuffer in) throws CharacterCodingException;
    public final CoderResult decode (ByteBuffer in, CharBuffer out, boolean endOfInput);
    public final CoderResult flush (CharBuffer out);

    public CodingErrorAction malformedInputAction();
    public final CharsetDecoder onMalformedInput (CodingErrorAction newAction);
    public CodingErrorAction unmappableCharacterAction();
    public final CharsetDecoder onUnmappableCharacter (CodingErrorAction newAction);

    public final String replacement();
    public final CharsetDecoder replaceWith (String newReplacement);
}

CharsetDecoder 的API与 CharsetEncoder 几乎是镜像关系。

  • 获取解码器信息的方法
  • charset() 方法用于获取关联的Charset对象。
  • averageCharsPerByte() maxCharsPerByte() 方法分别返回每个字节解码出的平均和最大字符数,这些值可用于确定接收解码字符的 CharBuffer 对象的大小。

  • 自动检测编码方案

  • isAutoDetecting() 方法返回一个布尔值,指示此解码器是否能够自动检测编码字节序列使用的编码方案。
  • 如果 isAutoDetecting() 返回 true isCharsetDetected() 方法在解码器从输入字节序列中读取足够的字节以确定使用的编码类型时返回 true 。该方法仅在解码过程开始后才有意义,调用 reset() 方法后,它总是返回 false 。默认实现总是抛出 UnsupportedOperationException
  • 如果检测到了字符集( isCharsetDetected() 返回 true ),可以调用 detectedCharset() 方法获取表示该字符集的Charset对象。在确定实际检测到字符集之前,不应调用此方法,否则会抛出 IllegalStateException 。该方法也是可选的,如果字符集不能自动检测,会抛出 UnsupportedOperationException

  • 解码过程

  • 重置解码器 :调用 reset() 方法将解码器置于已知状态,准备接受输入。
  • 解码字节 :调用 decode() 方法将字节序列解码为字符序列。有两种形式的 decode() 方法,一种是一体化的,另一种可以多次调用。
  • 刷新解码器 :调用 flush() 方法完成任何未完成的解码并输出所有剩余字符。

解码过程与编码过程类似,也包括重置、解码和刷新等基本步骤。

综上所述,字符集的编码和解码过程涉及多个步骤和方法,需要仔细处理各种可能的情况,以确保字符与字节流之间的正确转换。在实际编程中,要根据具体需求合理配置编码器和解码器,处理好各种错误情况,以提高程序的健壮性。

Java字符集编码与解码详解

5. 编码与解码的操作步骤总结

为了更清晰地理解和应用字符集的编码与解码,下面分别总结编码和解码的操作步骤。

编码操作步骤
graph LR
    classDef startend fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px
    classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px
    classDef decision fill:#FFF6CC,stroke:#FFBC52,stroke-width:2px

    A([开始]):::startend --> B{检查canEncode()}:::decision
    B -- 是 --> C(创建CharsetEncoder):::process
    B -- 否 --> D(抛出UnsupportedOperationException):::process
    C --> E(重置编码器: reset()):::process
    E --> F(提供字符: encode()多次调用):::process
    F --> G{是否还有字符?}:::decision
    G -- 是 --> F
    G -- 否 --> H(结束输入: encode(endOfInput=true)):::process
    H --> I(刷新编码器: flush()):::process
    I --> J([结束]):::startend
  1. 检查编码支持 :调用 Charset 类的 canEncode() 方法,确保字符集支持编码。如果返回 false ,调用其他编码方法会抛出 UnsupportedOperationException
  2. 创建编码器 :调用 Charset 类的 newEncoder() 方法,创建 CharsetEncoder 对象。
  3. 重置编码器 :调用 CharsetEncoder reset() 方法,为编码做准备。新创建的编码器也可进行此操作。
  4. 提供字符 :多次调用 CharsetEncoder encode() 方法,将 endOfInput 参数设为 false ,不断提供字符进行编码。每次调用后,根据 CoderResult 对象的结果处理,如Underflow时补充字符,Overflow时处理输出缓冲区。
  5. 结束输入 :最后一次调用 encode() 方法,将 endOfInput 参数设为 true ,告知编码器没有更多输入。
  6. 刷新编码器 :调用 flush() 方法,完成未完成的编码并输出剩余字节。
解码操作步骤
graph LR
    classDef startend fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px
    classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px
    classDef decision fill:#FFF6CC,stroke:#FFBC52,stroke-width:2px

    A([开始]):::startend --> B(检查isAutoDetecting()):::process
    B --> C(重置解码器: reset()):::process
    C --> D(解码字节: decode()多次调用):::process
    D --> E{是否还有字节?}:::decision
    E -- 是 --> D
    E -- 否 --> F(结束输入: decode(endOfInput=true)):::process
    F --> G(刷新解码器: flush()):::process
    G --> H([结束]):::startend
  1. 检查自动检测功能 :调用 CharsetDecoder isAutoDetecting() 方法,确定解码器是否能自动检测编码方案。
  2. 重置解码器 :调用 reset() 方法,将解码器置于准备接受输入的状态。
  3. 解码字节 :多次调用 decode() 方法,将字节序列解码为字符序列。根据 CoderResult 对象的结果处理,如Underflow时补充字节,Overflow时处理输出缓冲区。
  4. 结束输入 :最后一次调用 decode() 方法,将 endOfInput 参数设为 true
  5. 刷新解码器 :调用 flush() 方法,完成未完成的解码并输出剩余字符。
6. 错误处理与编码配置示例

在实际应用中,合理配置编码器和解码器的错误处理策略非常重要。以下是一个简单的示例,展示如何配置编码器的错误处理和替换序列。

import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder;
import java.nio.charset.CodingErrorAction;

public class EncodingExample {
    public static void main(String[] args) {
        // 获取字符集
        Charset charset = Charset.forName("UTF-8");
        // 创建编码器
        CharsetEncoder encoder = charset.newEncoder();

        // 配置错误处理策略
        encoder.onMalformedInput(CodingErrorAction.REPLACE)
               .onUnmappableCharacter(CodingErrorAction.REPLACE);

        // 设置替换序列
        byte[] replacement = "?".getBytes(charset);
        encoder.replaceWith(replacement);

        // 待编码的字符序列
        String input = "Hello, 世界!";
        CharBuffer charBuffer = CharBuffer.wrap(input);

        try {
            // 编码
            ByteBuffer byteBuffer = encoder.encode(charBuffer);
            // 输出编码后的字节数组
            byte[] bytes = new byte[byteBuffer.remaining()];
            byteBuffer.get(bytes);
            System.out.println(new String(bytes, charset));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

在上述示例中,我们首先获取了UTF - 8字符集的编码器,然后配置了格式错误输入和不可映射字符的处理策略为替换。接着设置了替换序列为问号,最后对包含中文字符的字符串进行编码。

7. 注意事项与最佳实践
  • 线程安全 CharsetEncoder CharsetDecoder 都是有状态的对象,不是线程安全的。在多线程环境中,每个线程应该使用独立的编码器和解码器实例,避免出现数据不一致的问题。
  • 错误处理 :在编码和解码过程中,可能会遇到各种错误,如格式错误输入和不可映射字符。要根据具体需求合理配置错误处理策略,如报告错误、忽略错误或替换错误输入。
  • 缓冲区管理 :在编码和解码过程中,要注意输入和输出缓冲区的管理。当出现Overflow或Underflow情况时,要正确处理缓冲区,如清空输出缓冲区、补充输入缓冲区等。
  • 字符集选择 :根据实际需求选择合适的字符集,如UTF - 8支持全球范围内的字符编码,ISO - 8859 - 1适用于拉丁字符。
8. 总结

Java中的字符集编码和解码是一个复杂但重要的过程,涉及多个类和方法。 Charset 类提供了基本的字符集操作, CharsetEncoder CharsetDecoder 类分别实现了编码和解码功能。 CoderResult 类用于表示编码和解码过程中的结果状态。

在实际应用中,要根据具体需求合理配置编码器和解码器,处理好各种错误情况,管理好缓冲区,确保字符与字节流之间的正确转换。同时,要注意线程安全问题,选择合适的字符集,以提高程序的健壮性和性能。通过深入理解和掌握这些知识,能够更好地处理Java中的字符编码和解码问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值