zoukankan      html  css  js  c++  java
  • Java 字符编码(二)Java 中的编解码

    Java 字符编码(二)Java 中的编解码

    java.nio.charset 包中提供了一套处理字符编码的工具类,主要有 Charset、CharsetDecoder、CharsetEncoder、CoderResult、StandardCharsets 这几个类。

    Java 中的字符使用 Unicode 编码,每个字符占用两个字节,16 个二进制位,向 ByteBuffer 中存放数据的时候需要考虑字符的编码,从中读取的时候也需要考虑字符的编码方式,也就是编码和解码。

    一、CharSet

    1.1 获取字符集

    // 返回指定的字符集 CharSet
    Charset charset = Charset.forName("utf8");
    // 返回虚拟机默认的字符集 CharSet
    Charset charset = Charset.defaultCharset();
    

    forName -> lookup -> lookup2,其中 lookup 会缓存正在使用的 Charset 编码方式,lookup2 则真正用于获取对应的编码方式。

    private static Charset lookup2(String charsetName) {
        ...
        Charset cs;
        if ((cs = standardProvider.charsetForName(charsetName)) != null ||
            (cs = lookupExtendedCharset(charsetName))           != null ||
            (cs = lookupViaProviders(charsetName))              != null) {
            return cs;
        }
        return null;
    }
    

    可以看到 Charset 有三种定义方式:

    • standardProvider JDK 定义的标准格式,如 UTF-8,UTF-16
    • extendedProvider JDK 扩展的标准格式
    • CharsetProvider SPI,通过 java.nio.charset.spi.CharsetProvider 自定义的格式

    1.2 编码和解码

    // ThreadLocalCoders.decoderFor(this) 调用 newDecoder 缓存了正在使用的缓解码器
    public final CharBuffer decode(ByteBuffer bb) {
        try {
            return ThreadLocalCoders.decoderFor(this)
                .onMalformedInput(CodingErrorAction.REPLACE)
                .onUnmappableCharacter(CodingErrorAction.REPLACE)
                .decode(bb);
        } catch (CharacterCodingException x) {
            throw new Error(x);         // Can't happen
        }
    }
    
    public final ByteBuffer encode(CharBuffer cb) {
        try {
            return ThreadLocalCoders.encoderFor(this)
                .onMalformedInput(CodingErrorAction.REPLACE)
                .onUnmappableCharacter(CodingErrorAction.REPLACE)
                .encode(cb);
        } catch (CharacterCodingException x) {
            throw new Error(x);         // Can't happen
        }
    }
    

    1.3 编码器和解码器

    newEncoder 和 newDecoder 是 Charset 中最重要的两个抽象方法:

    //编码器
    CharsetEncoder encoder = charset.newEncoder();
    //解码器
    CharsetDecoder decoder = charset.newDecoder();
    

    CharsetDecoder 将 ByteBuffer 解码为 CharBuffer,而 CharsetEncoder 则相反,将 CharBuffer 编码为 ByteBuffer。

    在讲解 CharsetDecoder 前先了解一下 CoderResult,这个类封装了编解码的结果,主要用于处理异常。

    二、CoderResult

    2.1 CoderResult 有以下几种状态

    # ByteBuffer 解码完成
    private static final int CR_UNDERFLOW  = 0;
    # CharBuffer 空间不足,解码完成
    private static final int CR_OVERFLOW   = 1;
    private static final int CR_ERROR_MIN  = 2;
    # ByteBuffer 解码异常
    private static final int CR_MALFORMED  = 2;
    # ByteBuffer 超出了 Unicode 定义的范围,eg: 0xD800-0xDFFF 或 >0x10FFFF
    # Unicode 在编写的时候已经考虑了编解码的问题
    private static final int CR_UNMAPPABLE = 3;
    

    其中 CR_UNDERFLOW(解码完成) 和 CR_OVERFLOW(CharBuffer 空间不足) 属于正常状态,CR_MALFORMED(字节码出错) 和 CR_UNMAPPABLE(不能映射到 Unicode 上) 属性异常状态。

    2.2 CoderResult 重要属性及方法

    // 对应 CR_UNDERFLOW、CR_OVERFLOW、CR_MALFORMED、CR_UNMAPPABLE
    private final int type;
    // 如果解码正常(也就是前两种状态)为 0,如果异常(后两种状态)对应解码出错的长度
    private final int length;
    

    isUnderflow()、isOverflow()、isError()、isMalformed()、isUnmappable() 等方法用于判断解码的状态。下面四个静态方法则用于构建解码的结果。

    // 解码正常
    public static final CoderResult UNDERFLOW = new CoderResult(CR_UNDERFLOW, 0);
    public static final CoderResult OVERFLOW = new CoderResult(CR_OVERFLOW, 0);
    
    // 解码异常
    public static CoderResult malformedForLength(int length) {
        return malformedCache.get(length);
    }
    public static CoderResult unmappableForLength(int length) {
        return unmappableCache.get(length);
    }
    

    三、CharsetDecoder

    CharsetDecoder 最重要的方法是 decode(in, out, endOfInput) ,控制了解码的流程,最重要的模板方法是 decodeLoop(in, out) ,由子类实现。CharsetDecoder 的实现类位于 sun.nio.cs 下,如 UTF_8、UTF_16、UTF_32 等。

    CharsetDecoder 解码的流程如下,包含了四种状态:

    1. 首先执行 reset() 方法,将 state=ST_RESET
    2. (可选)多次执行 decode(in, out, false) 方法,注意此时传入 false
    3. 执行 decode(in, out, true) 方法,注意此时传入 true,此时解码完成
    4. (可选)执行 flush(out) 方法,更新内部状态

    3.1 decode(in, out, endOfInput)

    这个方法有 3 个参数,前两个参数不用解释,第三个参数用来控制解码是否完成,如按 UTF-8 解码时最后只剩下一下字节,这时可能是流读取还未结束,需要继续读取,此时不能算做异常,这时就需要有 endOfInput 来控制。针对流的编解码见 StreamDecoder。

    public final CoderResult decode(ByteBuffer in, CharBuffer out,
    		boolean endOfInput) {
    	// 1. state 只能为 ST_RESET、ST_CODING 或 ST_END(endOfInput=true)
        int newState = endOfInput ? ST_END : ST_CODING;
        if ((state != ST_RESET) && (state != ST_CODING)
            && !(endOfInput && (state == ST_END)))
            throwIllegalStateException(state, newState);
        state = newState;
    
        for (;;) {
    
            CoderResult cr;
            try {
            	// 2. decodeLoop 完成解码
                cr = decodeLoop(in, out);
            } catch (BufferUnderflowException x) {
                throw new CoderMalfunctionError(x);
            } catch (BufferOverflowException x) {
                throw new CoderMalfunctionError(x);
            }
    
            // 2. CharBuffer 空间不足
            if (cr.isOverflow())
                return cr;
    
            // 3. 解码完成,如果 endOfInput=false 则会先返回,由调用者继续调用该方法解码
            if (cr.isUnderflow()) {
                if (endOfInput && in.hasRemaining()) {
                    cr = CoderResult.malformedForLength(in.remaining());
                    // Fall through to malformed-input case
                } else {
                    return cr;
                }
            }
    
            // 3. 解码出现异常时的处理,默认为 REPORT,即由上层处理异常
            CodingErrorAction action = null;
            if (cr.isMalformed())
                action = malformedInputAction;
            else if (cr.isUnmappable())
                action = unmappableCharacterAction;
            else
                assert false : cr.toString();
    
            // 3.1 REPORT 由上层处理异常
            if (action == CodingErrorAction.REPORT)
                return cr;
            // 3.2 REPLACE 追加了 replacement 字符 
            if (action == CodingErrorAction.REPLACE) {
                if (out.remaining() < replacement.length())
                    return CoderResult.OVERFLOW;
                out.put(replacement);
            }
            // 3.3 IGNORE 和 REPLACE 都忽略这种异常继续解码
            if ((action == CodingErrorAction.IGNORE)
                || (action == CodingErrorAction.REPLACE)) {
                // Skip erroneous input either way
                in.position(in.position() + cr.length());
                continue;
            }
    
            assert false;
        }
    
    }
    

    3.2 decode(ByteBuffer in)

    CharsetDecoder 的 decode(ByteBuffer in) 方法将 ByteBuffer 解码为 CharBuffer,这个方法调用了上面的方法,传入的 endOfInput=true。但需要注意的是如果数据是从文件流等读取时,ByteBuffer 数据可能并不完整,出现半包的情况,进而导致解码异常,这时就需要使用上面的方法(参数 endOfInput)精确控制解码的流程了。

    public final CharBuffer decode(ByteBuffer in) throws CharacterCodingException {
        int n = (int)(in.remaining() * averageCharsPerByte());
        CharBuffer out = CharBuffer.allocate(n);
    
        if ((n == 0) && (in.remaining() == 0))
            return out;
        reset();
        for (;;) {
            CoderResult cr = in.hasRemaining() ?
                decode(in, out, true) : CoderResult.UNDERFLOW;
    
            // 1. 解码完成,调用 flush 并结束
            if (cr.isUnderflow())
                cr = flush(out);
            if (cr.isUnderflow())
                break;
    
            // 2. CharBuffer 空间不足,扩容,继续解码
            if (cr.isOverflow()) {
                n = 2*n + 1;    // Ensure progress; n might be 0!
                CharBuffer o = CharBuffer.allocate(n);
                out.flip();
                o.put(out);
                out = o;
                continue;
            }
    
            // 3. 解码异常,直接抛出
            cr.throwException();
        }
        out.flip();
        return out;
    }
    

    每天用心记录一点点。内容也许不重要,但习惯很重要!

  • 相关阅读:
    转:va_list、va_start、va_arg、va_end的原理与使用
    学习Docker的记录
    Google Code 优秀的开源工具
    转载(程序在内存中运行的奥秘)
    C# 和 Java 之争之我见
    揭秘ASP.NET 2.0的Eval方法(转)
    IIS6.0 架构(二)
    IE6 position:fixed bug (固定窗口方法)(转载)
    用FileStream上传图片转换成二进制,在本地用行,传到服务器上去出现如下错误
    异常处理
  • 原文地址:https://www.cnblogs.com/binarylei/p/10760239.html
Copyright © 2011-2022 走看看