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主页查看和下载。

另,个人技术博客,同步更新,欢迎关注!转载请注明出处!文中若有什么错误希望大家探讨指正!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容