關於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