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

0%