Java 判断字节流是否是 UTF8 编码

Java 判断字节流是否是 UTF8 编码

遇到本来设计时使用 GBK 编码处理的地方,在实际使用过程导入了 UTF8 编码,造成了显示文本为乱码的现象,在了解 UTF8,GBK 编码和 Unicode 标准之后,编写了 Java 判断字节流是否是 UTF8 编码的程序,如果是 UTF8 编码,则转换成 GBK 编码。

编码的基础知识

Unicode 是一种标准,GBK 和 UTF8 是具体是编码格式。Java 的字符都是以 Unicode 进行存储的,占两或四个字节(看版本,且 Unicode 编码中对应关系是存在 0x00 的编码的)。Java 中的 getBytes() 方法是和平台(编码)相关的,在中文系统中返回的可能是 GBK 或 GBK2312,在英文系统中返回的可能是 ISO-8859-1。

  • Unicode 标准:是计算机科学领域里的一项业界标准,包括字符集、编码方案等,它为每种语言中的每个字符设定了统一并且唯一的二进制编码,以满足跨语言、跨平台进行文本转换、处理的要求。
  • GBK 编码:汉字内码扩展规范,国标,汉字占两个字节。
  • UTF8 编码:针对 Unicode 的可变长度字符编码,用 1 到 6 个字节编码 Unicode 字符,汉字一般占 3 个字节。

UTF8 编码格式

如果 Unicode 字符由 2 个字节表示,则编码成 UTF8 很可能需要 3 个字节。而如果 Unicode 字符由 4 个字节表示,则编码成 UTF8 可能需要 6个字节。用 4 个或 6 个字节去编码一个 Unicode 字符可能太多了,但很少会遇到那样的 Unicode 字符。

UTF8 编码规则:如果只有一个字节则其最高二进制位为 0,如果是多字节,其第一个字节从最高位开始,连续的二进制位值为 1,1 的个数决定了其编码的字节数,其余各字节均以 10 开头。

// Unicode6.1定义范围:0~10 FFFF
// 20 0000 ~ 3FF FFFF 和 400 0000 ~ 7FFF FFFF 属于 UCS-4,UTF8 现在已经弃用了这部分内容

---------------------------------------------------------------------------------
n | Unicode (十六进制)    | UTF - 8 (二进制)
--+-----------------------+------------------------------------------------------
1 | 0000 0000 - 0000 007F | 0xxxxxxx
2 | 0000 0080 - 0000 07FF | 110xxxxx 10xxxxxx
3 | 0000 0800 - 0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx
4 | 0001 0000 - 0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
---------------------------------------------------------------------------------
// 以下部分弃用
5 | 0020 0000 - 03FF FFFF | 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
6 | 0400 0000 - 7FFF FFFF | 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
---------------------------------------------------------------------------------

Java 如何判断单个字符编码是否是 UTF8

假设当前需要判定一个 byte[] 数组内的编码是否是 UTF8 编码,这个 byte[] 是 String 通过 getBytes() 方法获取的,判断单个字符的编码步骤如下:

  • 从 byte[] 数组中获取一个 byte 并将它转换成无符号类型的 int 变量 value
  • 判断 value 是否是 ASCII 字符(小于 0x80)
  • 判断 value 是否是无效字符(大于 0x80,小于 0xC0,参照 UTF8 编码规则)
  • 确认该字符编码的是几字节 UTF8
  • 确认该字符编码的除第一个字节外的字节是否满足 10xxxxxx 格式

PS:

Java getBytes() 获取的是带符号的十六进制,实际处理时需要使用无符号十六进制。

GBK 和 UTF8 中 ASCII 字符的值是一样的。

具体程序

将十六进制流中的所有编码按照单个判定的方式便利一遍,如果有不符合 UTF8 编码规则的字符出现,则该十六进制流就不是 UTF8 编码格式的字串。

public static int byteToUnsignedInt(byte data) {
    return data & 0xff;
}

public boolean isUTF8(byte[] pBuffer) {
    boolean IsUTF8 = true;
    boolean IsASCII = true;
    int size = pBuffer.length;
    int i = 0;
    while (i < size) {
        int value = byteToUnsignedInt(pBuffer[i]);
        if (value < 0x80) {
            // (10000000): 值小于 0x80 的为 ASCII 字符
            if (i >= size - 1) {
                if (IsASCII) {
                    // 假设纯 ASCII 字符不是 UTF 格式
                    IsUTF8 = false;
                }
                break;
            }
            i++;
        } else if (value < 0xC0) {
            // (11000000): 值介于 0x80 与 0xC0 之间的为无效 UTF-8 字符
            IsASCII = false;
            IsUTF8 = false;
            break;
        } else if (value < 0xE0) {
            // (11100000): 此范围内为 2 字节 UTF-8 字符
            IsASCII = false;
            if (i >= size - 1) {
                break;
            }

            int value1 = byteToUnsignedInt(pBuffer[i + 1]);
            if ((value1 & (0xC0)) != 0x80) {
                IsUTF8 = false;
                break;
            }

            i += 2;
        } else if (value < 0xF0) {
            IsASCII = false;
            // (11110000): 此范围内为 3 字节 UTF-8 字符
            if (i >= size - 2) {
                break;
            }

            int value1 = byteToUnsignedInt(pBuffer[i + 1]);
            int value2 = byteToUnsignedInt(pBuffer[i + 2]);
            if ((value1 & (0xC0)) != 0x80 || (value2 & (0xC0)) != 0x80) {
                IsUTF8 = false;
                break;
            }

            i += 3;
        }  else if (value < 0xF8) {
            IsASCII = false;
            // (11111000): 此范围内为 4 字节 UTF-8 字符
            if (i >= size - 3) {
                break;
            }

            int value1 = byteToUnsignedInt(pBuffer[i + 1]);
            int value2 = byteToUnsignedInt(pBuffer[i + 2]);
            int value3 = byteToUnsignedInt(pBuffer[i + 3]);
            if ((value1 & (0xC0)) != 0x80
                || (value2 & (0xC0)) != 0x80
                || (value3 & (0xC0)) != 0x80) {
                IsUTF8 = false;
                break;
            }

            i += 3;
        } else {
            IsUTF8 = false;
            IsASCII = false;
            break;
        }
    }

    return IsUTF8;
}

本文转自:https://bearzpy.github.io/2017/11/03/Java/Java%20%E5%88%A4%E6%96%AD%E5%AD%97%E8%8A%82%E6%B5%81%E6%98%AF%E5%90%A6%E6%98%AF%20UTF8%20%E7%BC%96%E7%A0%81/index.html

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

推荐阅读更多精彩内容