關於java字符的編碼問題學習

關於java字符串相關的字符集和編碼方式不再解釋,可以參見該篇文章[Java與字符編碼問題詳談](http://hxraid.iteye.com/blog/559607),今天要說的是在java字符串轉字節數組時的方式: String.getBytes()和Charset.encode(string)的區別;
問題: 
    String str = "我";
    byte[] ba1 = str.getBytes("UTF-16");
    printBytes(ba1);// 編碼:0xfe 0xff 0x62 0x11; 前兩個爲BOM
    -------------華麗的分割線--------------------------------
    Charset cs = CharSet.forName("UTF-16"); 
    byte[] ba2 = cs.encode(str).array();
    printBytes(ba2);//編碼:0xfe 0xff 0x62 0x11 0x00; 最後多個0x00
困擾我的問題來了,爲什麼會多了一個字節呢?   
先來看看源碼:
一。String.getBytes()方法:
    public byte[] getBytes(String charsetName)
            throws UnsupportedEncodingException {
            if (charsetName == null) throw new NullPointerException();
           return StringCoding.encode(charsetName, value, 0, value.length);
    }
在這裏是直接調用StringCoding類的encode()方法。
    static byte[] encode(String charsetName, char[] ca, int off, int len)
        throws UnsupportedEncodingException
    {
        StringEncoder se = deref(encoder);
        String csn = (charsetName == null) ? "ISO-8859-1" : charsetName;
        if ((se == null) || !(csn.equals(se.requestedCharsetName())
                              || csn.equals(se.charsetName()))) {
            se = null;
            try {
                Charset cs = lookupCharset(csn);
                if (cs != null)
                    se = new StringEncoder(cs, csn);
            } catch (IllegalCharsetNameException x) {}
            if (se == null)
                throw new UnsupportedEncodingException (csn);
            set(encoder, se);
        }
        return se.encode(ca, off, len);
    }
StringCoding類先獲取當前項目平臺的編碼, 如果和要求的指定編碼方式不同,則通過制定編碼方式獲取字符集CharSet,進而生成StringEncode類(StringCoding的私有成員類), 而我們來看StringEncode類:
 private static class StringEncoder {
    private Charset cs;
    private CharsetEncoder ce;
    private final String requestedCharsetName;
    private final boolean isTrusted;
    ...........
     byte[] encode(char[] ca, int off, int len) {
        int en = scale(len, ce.maxBytesPerChar());
        byte[] ba = new byte[en];
        if (len == 0)
            return ba;
        if (ce instanceof ArrayEncoder) {
            int blen = ((ArrayEncoder)ce).encode(ca, off, len, ba);
            return safeTrim(ba, blen, cs, isTrusted);
        } else {
            ce.reset();
            ByteBuffer bb = ByteBuffer.wrap(ba);
            CharBuffer cb = CharBuffer.wrap(ca, off, len);
            try {
                CoderResult cr = ce.encode(cb, bb, true);
                if (!cr.isUnderflow())
                    cr.throwException();
                cr = ce.flush(bb);
                if (!cr.isUnderflow())
                    cr.throwException();
            } catch (CharacterCodingException x) {
                // Substitution is always enabled,
                // so this shouldn't happen
                throw new Error(x);
            }
            return safeTrim(ba, bb.position(), cs, isTrusted);
        }
可以看到StringEncode類的編碼事實上也是調用了CharEncoder類來編碼; 但注意的是StringCoding類的encode()方法中最後的這個safeTrim(ba, bb.position(), cs, isTrusted);這裏會將ByteBuffer類中的值放到byte[]中,而這裏的參數bb.position是將ByteBuffer中的有效的值放入了。 詳情可參見[java.nio.ByteBuffer用法小結](http://blog.csdn.net/zhoujiaxq/article/details/22822289);

二. CharSet.encode(string)
首先CharSet.encode(),實際上是先生成CharsetEncode類,如下

    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
        }
    }

看這裏如何編碼, 如下:

    public final ByteBuffer encode(CharBuffer in)
        throws CharacterCodingException
    {
    int n = (int)(in.remaining() * averageBytesPerChar());
    ByteBuffer out = ByteBuffer.allocate(n);
    if ((n == 0) && (in.remaining() == 0))
        return out;
    reset();
    for (;;) {
        CoderResult cr = in.hasRemaining() ?
            encode(in, out, true) : CoderResult.UNDERFLOW;
        if (cr.isUnderflow())
            cr = flush(out);
        if (cr.isUnderflow())
            break;
        if (cr.isOverflow()) {
            n = 2*n + 1;    // Ensure progress; n might be 0!
            ByteBuffer o = ByteBuffer.allocate(n);
            out.flip();
            o.put(out);
            out = o;
            continue;
        }
        cr.throwException();
    }
    out.flip();
    return out;
}

這兩種方式的最終的編碼其實都是調用了CharsetEncode類的方法:
CoderResult encode(CharBuffer in, ByteBuffer out,boolean endOfInput); 這裏區別在於:
1>在調用方法前的參數上, 同一字符串調用時,參數ByteBuffer的會不同。 這取決於對傳入的ByteBuffer的capacity屬性的不同賦值,StringCoding的encode方法爲:

int len = string.length;
byte[] encode(char[] ca, int off, int len) {
    int en = scale(len, ce.maxBytesPerChar()); 
    ...................

對ce(CharsetEncoder)的maxBytesPerChar;
而在CharSet類的encode方法中爲:

CharBuffer in = CharBuffer.wrap(string);
int n = (int)(in.remaining() * averageBytesPerChar());
//注:string.length() = CharBuffer.wrap(string).remaining();
ByteBuffer out = ByteBuffer.allocate(n);
..............
for (;;) {
    CoderResult cr = in.hasRemaining() ?encode(in, out, true) : CoderResult.UNDERFLOW;                
    if (cr.isUnderflow())
        cr = flush(out);
    if (cr.isUnderflow())
        break;
    if (cr.isOverflow()) {   
        n = 2*n + 1;    // Ensure progress; n might be 0!
        //這裏爲了確保程序正常,當初始的ByteBuffer容量不足時,newCapacity = 2*oldCapacity+1;
        ByteBuffer o = ByteBuffer.allocate(n);
        out.flip();
        o.put(out);
        out = o;
        continue;
    }
        cr.throwException();
    }
 .....................   

2>StringCoding對返回的結果ByteBuffer進行了處理。

    byte[] encode(char[] ca, int off, int len) {
        int en = scale(len, ce.maxBytesPerChar()); 
        if (len == 0)
            ................... 
        if (ce instanceof ArrayEncoder) {
            ...................
        } else {
            ce.reset();
            ByteBuffer bb = ByteBuffer.wrap(ba);
            CharBuffer cb = CharBuffer.wrap(ca, off, len);
            try {
                CoderResult cr = ce.encode(cb, bb, true);
                ...................
             } catch (CharacterCodingException x) {
                ................... 
             }  
         return safeTrim(ba, bb.position(), cs, isTrusted);

調用了safeTrim()方法,將ByteBuffer中的值複製到byte[]中。
而CharSet的encode方法直接就返回了ByteBuffer方法,而ByteBuffer中有的方法爲array();如下:

    Charset cs = CharSet.forName("UTF-16"); 
    ByteBuffer bb = cs.encode(str);
    byte[] ba = bb.array();
    printBytes(ba2);//編碼:0xfe 0xff 0x62 0x11 0x00; 最後多個0x00

而ByteBuffer.array()方法是將ByteBuffer容器的每個元素都轉變爲數組中的值,這其中編碼時,ByteBuffer容器沒有被填滿->容器中的某些值爲0(因爲ByteBuffer.allocate(n)初始化時會默認賦值);所有在字符編碼時會,調用CharSet.encode()方法後, 對返回的結果ByteBuffer直接調用.array()方法會造成編碼後的字節數組多出一個字節。
正確的處理CharSet.encode()返回結果的方式應如下:

String str = "我們";
CharSet cs = CharSet.forName("UTF-16");//或'UTF-8'/'GBK'/'ASCII'等
ByteBuffer bb = cs.encode(str);
byte[] ba = new byte[bb.limit()];
for(int i = 0;;i++){
    if(bb.hasRemaining())
        ba[i] = bb.get();
    else{
        break;
    }   
}
printBytes(ba);  //結果0xFE  0xff  0x62  0x11  0x4E  0xEC 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章