1. 程式人生 > >Java中String和byte[]間的轉換淺析

Java中String和byte[]間的轉換淺析

Java語言中字串型別和位元組陣列型別相互之間的轉換經常發生,網上的分析及程式碼也比較多,本文將分析總結常規的byte[]和String間的轉換以及十六進位制String和byte[]間相互轉換的原理及實現。

1. String轉byte[]

首先我們來分析一下常規的String轉byte[]的方法,程式碼如下:

public static byte[] strToByteArray(String str) {
    if (str == null) {
        return null;
    }
    byte[] byteArray = str.getBytes();
    
return byteArray; } 很簡單,就是呼叫String類的getBytes()方法。看JDK原始碼可以發現該方法最終呼叫了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; } } 上述程式碼其實就是根據給定的編碼方式進行編碼。如果呼叫的是不帶引數的getBytes()方法,則使用預設的編碼方式,如下程式碼所示: /** * 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"); } } 關於預設的編碼方式,Java API是這樣說的: The default charset is determined during virtual-machine startup and typically depends upon the locale and charset of the underlying operating system. 同樣,由上述程式碼可以看出,預設編碼方式是由System類的"file.encoding"屬性決定的,經過測試,在簡體中文Windows作業系統下,預設編碼方式為"GBK",在Android平臺上,預設編碼方式為"UTF-8"2. byte[]轉String 接下來分析一下常規的byte[]轉為String的方法,程式碼如下: public static String byteArrayToStr(byte[] byteArray) { if (byteArray == null) { return null; } String str = new String(byteArray); return str; } 很簡單,就是String的構造方法之一。那我們分析Java中String的原始碼,可以看出所有以byte[]為引數的構造方法最終都呼叫瞭如下程式碼所示的構造方法。需要注意的是Java中String類的資料是Unicode型別的,因此上述的getBytes()方法是把Unicode型別轉化為指定編碼方式的byte陣列;而這裡的Charset為讀取該byte陣列時所使用的編碼方式。 /** * 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; } } } 具體的轉換過程較為複雜,其實就是將byte陣列的一個或多個元素按指定的Charset型別讀取並轉換為char型別(char本身就是以Unicode編碼方式儲存的),因為String類的核心是其內部維護的char陣列。因此有興趣的同學可以研究下各種編碼方式的編碼規則,然後才能看懂具體的轉換過程。 3. byte[]轉十六進位制String 所謂十六進位制String,就是字串裡面的字元都是十六進位制形式,因為一個byte是八位,可以用兩個十六進位制位來表示,因此,byte陣列中的每個元素可以轉換為兩個十六進位制形式的char,所以最終的HexString的長度是byte陣列長度的兩倍。閒話少說上程式碼: 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數值和0xFF按位與,是因為我們為了方便後面的無符號移位操作(無符號右移運算子>>>只對32位和64位的值有意義),要將byte資料轉換為int型別,而如果直接轉換就會出現問題。因為java裡面二進位制是以補碼形式存在的,如果直接轉換,位擴充套件會產生問題,如值為-1的byte儲存的二進位制形式為其補碼11111111,而轉換為int後為11111111111111111111111111111111,直接使用該值結果就不對了。而0xFF預設是int型別,即0x000000FF,一個byte值跟0xFF相與會先將那個byte值轉化成int型別運算,這樣,相與的結果中高的24個位元就總會被清0,後面的運算才會正確。 4. 十六進位制String轉byte[] 沒什麼好說的了,就是byte[]轉十六進位制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; } 文中所有程式碼可以在個人github主頁檢視和下載。

 


另,個人技術部落格,同步更新,歡迎關注!轉載請註明出處!文中若有什麼錯誤希望大家探討指正!