Java語言中字符串類型和字節(jié)數(shù)組類型相互之間的轉(zhuǎn)換經(jīng)常發(fā)生,網(wǎng)上的分析及代碼也比較多,本文將分析總結(jié)常規(guī)的byte[]和String間的轉(zhuǎn)換以及十六進(jìn)制String和byte[]間相互轉(zhuǎn)換的原理及實現(xiàn)。
首先我們來分析一下常規(guī)的String轉(zhuǎn)byte[]的方法,代碼如下:
public static byte[] strToByteArray(String str) {
if (str == null) {
return null;
}
byte[] byteArray = str.getBytes();
return byteArray;
}
很簡單,就是調(diào)用String類的getBytes()方法??碕DK源碼可以發(fā)現(xiàn)該方法最終調(diào)用了String類如下的方法。
/**
* JDK source code
*/
public byte[] getBytes(Charset charset) {
String canonicalCharsetName = charset.name();
if (canonicalCharsetName.equals("UTF-8")) {
return Charsets.toUtf8Bytes(value, offset, count);
} else if (canonicalCharsetName.equals("ISO-8859-1")) {
return Charsets.toIsoLatin1Bytes(value, offset, count);
} else if (canonicalCharsetName.equals("US-ASCII")) {
return Charsets.toAsciiBytes(value, offset, count);
} else if (canonicalCharsetName.equals("UTF-16BE")) {
return Charsets.toBigEndianUtf16Bytes(value, offset, count);
} else {
CharBuffer chars = CharBuffer.wrap(this.value, this.offset, this.count);
ByteBuffer buffer = charset.encode(chars.asReadOnlyBuffer());
byte[] bytes = new byte[buffer.limit()];
buffer.get(bytes);
return bytes;
}
}
上述代碼其實就是根據(jù)給定的編碼方式進(jìn)行編碼。如果調(diào)用的是不帶參數(shù)的getBytes()方法,則使用默認(rèn)的編碼方式,如下代碼所示:
/**
* JDK source code
*/
private static Charset getDefaultCharset() {
String encoding = System.getProperty("file.encoding", "UTF-8");
try {
return Charset.forName(encoding);
} catch (UnsupportedCharsetException e) {
return Charset.forName("UTF-8");
}
}
關(guān)于默認(rèn)的編碼方式,Java API是這樣說的:
The default charset is determined during virtual-machine startup and typically depends upon the locale and charset of the underlying operating system.
同樣,由上述代碼可以看出,默認(rèn)編碼方式是由System類的"file.encoding"屬性決定的,經(jīng)過測試,在簡體中文Windows操作系統(tǒng)下,默認(rèn)編碼方式為"GBK",在Android平臺上,默認(rèn)編碼方式為"UTF-8"。
接下來分析一下常規(guī)的byte[]轉(zhuǎn)為String的方法,代碼如下:
public static String byteArrayToStr(byte[] byteArray) {
if (byteArray == null) {
return null;
}
String str = new String(byteArray);
return str;
}
很簡單,就是String的構(gòu)造方法之一。那我們分析Java中String的源碼,可以看出所有以byte[]為參數(shù)的構(gòu)造方法最終都調(diào)用了如下代碼所示的構(gòu)造方法。需要注意的是Java中String類的數(shù)據(jù)是Unicode類型的,因此上述的getBytes()方法是把Unicode類型轉(zhuǎn)化為指定編碼方式的byte數(shù)組;而這里的Charset為讀取該byte數(shù)組時所使用的編碼方式。
/**
* JDK source code
*/
public String(byte[] data, int offset, int byteCount, Charset charset) {
if ((offset | byteCount) < 0 || byteCount > data.length - offset) {
throw failedBoundsCheck(data.length, offset, byteCount);
}
// We inline UTF-8, ISO-8859-1, and US-ASCII decoders for speed and because
// 'count' and 'value' are final.
String canonicalCharsetName = charset.name();
if (canonicalCharsetName.equals("UTF-8")) {
byte[] d = data;
char[] v = new char[byteCount];
int idx = offset;
int last = offset + byteCount;
int s = 0;
outer:
while (idx < last) {
byte b0 = d[idx++];
if ((b0 & 0x80) == 0) {
// 0xxxxxxx
// Range: U-00000000 - U-0000007F
int val = b0 & 0xff;
v[s++] = (char) val;
} else if (((b0 & 0xe0) == 0xc0) || ((b0 & 0xf0) == 0xe0) ||
((b0 & 0xf8) == 0xf0) || ((b0 & 0xfc) == 0xf8) || ((b0 & 0xfe)
== 0xfc)) {
int utfCount = 1;
if ((b0 & 0xf0) == 0xe0) utfCount = 2;
else if ((b0 & 0xf8) == 0xf0) utfCount = 3;
else if ((b0 & 0xfc) == 0xf8) utfCount = 4;
else if ((b0 & 0xfe) == 0xfc) utfCount = 5;
// 110xxxxx (10xxxxxx)+
// Range: U-00000080 - U-000007FF (count == 1)
// Range: U-00000800 - U-0000FFFF (count == 2)
// Range: U-00010000 - U-001FFFFF (count == 3)
// Range: U-00200000 - U-03FFFFFF (count == 4)
// Range: U-04000000 - U-7FFFFFFF (count == 5)
if (idx + utfCount > last) {
v[s++] = REPLACEMENT_CHAR;
continue;
}
// Extract usable bits from b0
int val = b0 & (0x1f >> (utfCount - 1));
for (int i = 0; i < utfCount; ++i) {
byte b = d[idx++];
if ((b & 0xc0) != 0x80) {
v[s++] = REPLACEMENT_CHAR;
idx--; // Put the input char back
continue outer;
}
// Push new bits in from the right side
val <<= 6;
val |= b & 0x3f;
}
// Note: Java allows overlong char
// specifications To disallow, check that val
// is greater than or equal to the minimum
// value for each count:
//
// count min value
// ----- ----------
// 1 0x80
// 2 0x800
// 3 0x10000
// 4 0x200000
// 5 0x4000000
// Allow surrogate values (0xD800 - 0xDFFF) to
// be specified using 3-byte UTF values only
if ((utfCount != 2) && (val >= 0xD800) && (val <= 0xDFFF)) {
v[s++] = REPLACEMENT_CHAR;
continue;
}
// Reject chars greater than the Unicode maximum of U+10FFFF.
if (val > 0x10FFFF) {
v[s++] = REPLACEMENT_CHAR;
continue;
}
// Encode chars from U+10000 up as surrogate pairs
if (val < 0x10000) {
v[s++] = (char) val;
} else {
int x = val & 0xffff;
int u = (val >> 16) & 0x1f;
int w = (u - 1) & 0xffff;
int hi = 0xd800 | (w << 6) | (x >> 10);
int lo = 0xdc00 | (x & 0x3ff);
v[s++] = (char) hi;
v[s++] = (char) lo;
}
} else {
// Illegal values 0x8*, 0x9*, 0xa*, 0xb*, 0xfd-0xff
v[s++] = REPLACEMENT_CHAR;
}
}
if (s == byteCount) {
// We guessed right, so we can use our temporary array as-is.
this.offset = 0;
this.value = v;
this.count = s;
} else {
// Our temporary array was too big, so reallocate and copy.
this.offset = 0;
this.value = new char[s];
this.count = s;
System.arraycopy(v, 0, value, 0, s);
}
} else if (canonicalCharsetName.equals("ISO-8859-1")) {
this.offset = 0;
this.value = new char[byteCount];
this.count = byteCount;
Charsets.isoLatin1BytesToChars(data, offset, byteCount, value);
} else if (canonicalCharsetName.equals("US-ASCII")) {
this.offset = 0;
this.value = new char[byteCount];
this.count = byteCount;
Charsets.asciiBytesToChars(data, offset, byteCount, value);
} else {
CharBuffer cb = charset.decode(ByteBuffer.wrap(data, offset, byteCount));
this.offset = 0;
this.count = cb.length();
if (count > 0) {
// We could use cb.array() directly, but that would mean we'd have to trust
// the CharsetDecoder doesn't hang on to the CharBuffer and mutate it later,
// which would break String's immutability guarantee. It would also tend to
// mean that we'd be wasting memory because CharsetDecoder doesn't trim the
// array. So we copy.
this.value = new char[count];
System.arraycopy(cb.array(), 0, value, 0, count);
} else {
this.value = EmptyArray.CHAR;
}
}
}
具體的轉(zhuǎn)換過程較為復(fù)雜,其實就是將byte數(shù)組的一個或多個元素按指定的Charset類型讀取并轉(zhuǎn)換為char類型(char本身就是以Unicode編碼方式存儲的),因為String類的核心是其內(nèi)部維護(hù)的char數(shù)組。因此有興趣的同學(xué)可以研究下各種編碼方式的編碼規(guī)則,然后才能看懂具體的轉(zhuǎn)換過程。
所謂十六進(jìn)制String,就是字符串里面的字符都是十六進(jìn)制形式,因為一個byte是八位,可以用兩個十六進(jìn)制位來表示,因此,byte數(shù)組中的每個元素可以轉(zhuǎn)換為兩個十六進(jìn)制形式的char,所以最終的HexString的長度是byte數(shù)組長度的兩倍。閑話少說上代碼:
public static String byteArrayToHexStr(byte[] byteArray) {
if (byteArray == null){
return null;
}
char[] hexArray = "0123456789ABCDEF".toCharArray();
char[] hexChars = new char[byteArray.length * 2];
for (int j = 0; j < byteArray.length; j++) {
int v = byteArray[j] & 0xFF;
hexChars[j * 2] = hexArray[v >>> 4];
hexChars[j * 2 + 1] = hexArray[v & 0x0F];
}
return new String(hexChars);
}
上述代碼中,之所以要將byte數(shù)值和0xFF按位與,是因為我們?yōu)榱朔奖愫竺娴臒o符號移位操作(無符號右移運算符>>>只對32位和64位的值有意義),要將byte數(shù)據(jù)轉(zhuǎn)換為int類型,而如果直接轉(zhuǎn)換就會出現(xiàn)問題。因為java里面二進(jìn)制是以補碼形式存在的,如果直接轉(zhuǎn)換,位擴展會產(chǎn)生問題,如值為-1的byte存儲的二進(jìn)制形式為其補碼11111111,而轉(zhuǎn)換為int后為11111111111111111111111111111111,直接使用該值結(jié)果就不對了。而0xFF默認(rèn)是int類型,即0x000000FF,一個byte值跟0xFF相與會先將那個byte值轉(zhuǎn)化成int類型運算,這樣,相與的結(jié)果中高的24個比特就總會被清0,后面的運算才會正確。
沒什么好說的了,就是byte[]轉(zhuǎn)十六進(jìn)制String的逆過程,放代碼:
public static byte[] hexStrToByteArray(String str)
{
if (str == null) {
return null;
}
if (str.length() == 0) {
return new byte[0];
}
byte[] byteArray = new byte[str.length() / 2];
for (int i = 0; i < byteArray.length; i++){
String subStr = str.substring(2 * i, 2 * i + 2);
byteArray[i] = ((byte)Integer.parseInt(subStr, 16));
}
return byteArray;
}
如對本文有疑問,請?zhí)峤坏浇涣髡搲?,廣大熱心網(wǎng)友會為你解答!! 點擊進(jìn)入論壇