字符编码方案:Unicode

摘要:

Unicode(统一码、万国码、单一码)是一种在计算机上广泛使用的字符编码,旨在解决传统字符编码方案的局限,为每种语言中的每个字符设定了统一且唯一的二进制编码,以满足跨语言、跨平台进行文本转换和处理的需求。以下是关于Unicode编码的详细解析:

一、历史背景

  • 起源与发展:Unicode编码的历史可以追溯到20世纪60年代,当时计算机科学家们意识到不同计算机系统使用不同的字符编码方式,导致文本和数据在不同系统间传输时出现混乱和错误。为解决这一问题,国际组织开始致力于制定统一的字符编码标准。1987年,Unicode联盟成立,开始制定Unicode编码标准。
  • 正式发布:Unicode标准于1990年开始研发,1994年正式公布。随着计算机和互联网的发展,Unicode编码逐渐成为全球通用的字符编码标准。

二、特点与优势

  • 统一性:Unicode为世界上几乎所有的字符集和符号提供了唯一的数字标识符,确保了字符在不同计算机系统和编程环境中的一致性和互操作性。
  • 扩展性:Unicode编码体系庞大,能够容纳超过110万个码位,涵盖了全球范围内的绝大多数语言字符,为未来的字符扩展提供了充足的空间。
  • 兼容性:Unicode与多种传统字符编码方案(如ISO 8859、GB2312等)保持兼容,使得旧有编码方式可以平滑过渡到Unicode编码。

三、编码方式

  • 固定长度与可变长度:虽然Unicode为每个字符分配了唯一的码位,但其具体实现方式(如UTF-8、UTF-16、UTF-32等)在编码长度上有所不同。例如,UTF-8是一种可变长度的编码方式,能够根据字符的不同使用不同数量的字节进行编码;而UTF-16和UTF-32则是固定长度的编码方式,分别使用16位和32位来表示一个字符。
  • 编码效率与兼容性:不同的编码方式在编码效率和兼容性方面各有优劣。例如,UTF-8在编码ASCII字符时非常高效,且能够兼容传统的ASCII编码方式;而UTF-16和UTF-32则在处理复杂字符集时更为方便,但可能会占用更多的存储空间。

四、应用场景

  • 跨语言文本处理:Unicode编码使得不同语言的文本可以在同一平台上进行处理和交换,为跨语言的信息交流提供了便利。
  • 国际化软件开发:在软件开发过程中,使用Unicode编码可以确保软件支持多种语言,满足全球化市场的需求。
  • 网络通信:在网络通信中,使用Unicode编码可以确保各种语言的字符能够正确传输和显示,提高了通信的效率和准确性。
  • 数据库存储:数据库使用Unicode编码可以存储各种语言的字符数据,为数据分析和挖掘提供了更广泛的数据来源。

五、使用方法

  • 文本编辑器:在文档或文本文件中插入Unicode字符时,可以直接使用文本编辑器进行输入或粘贴。
  • HTML和网页:在HTML中,可以使用实体编码或直接插入Unicode字符来表示特殊字符。
  • 编程语言:在编程中,可以使用编程语言的字符串处理功能来插入Unicode字符。通常,需要使用特定的语法(如\u后跟字符的十六进制编码值)来表示Unicode字符。

综上所述,Unicode编码作为一种重要的字符编码标准,为全球范围内的信息交流和处理提供了统一的基础。随着技术的不断发展和全球化的推进,Unicode编码将在更多领域发挥重要作用。

正文:

一、UTF-8 和 Unicode

UTF-8 和 Unicode 是两个在字符编码领域经常提到的概念,它们之间有着紧密的联系,但也有着明显的区别。

Unicode

Unicode 是一个国际标准,旨在为世界上的每一种书写系统中的每一个字符、符号以及表情符号提供一个独一无二的数字标识符(称为码点或代码点)。Unicode 并不直接定义字符的存储方式,而是定义了字符的编码方式。Unicode 编码空间非常大,理论上可以容纳超过一百万个字符。

UTF-8

UTF-8(Unicode Transformation Format-8 bits)是一种针对Unicode的可变长度字符编码方式。它使用1到4个字节来表示每个Unicode字符,根据字符的Unicode码点大小来决定使用多少个字节。UTF-8的编码方式使得它对于ASCII字符(即Unicode码点在U+0000到U+007F之间的字符)是兼容的,即ASCII字符在UTF-8中的编码与其在ASCII编码中的编码完全相同,都是单字节。

区别与联系

  • 区别
    • Unicode 是一个字符集,它定义了世界上所有字符的编码。
    • UTF-8 是一种字符编码方式,用于将Unicode字符编码为字节序列,以便在计算机中存储和传输。
  • 联系
    • UTF-8 是实现Unicode编码的一种具体方式。
    • Unicode 定义了字符的编码,而UTF-8 定义了如何将这些编码转换为字节序列。

使用场景

由于UTF-8的广泛兼容性和高效性(对于ASCII字符只使用单字节),它已成为互联网上最流行的字符编码方式之一。几乎所有的现代操作系统、网页浏览器、编程语言等都支持UTF-8编码。

在开发过程中,推荐使用UTF-8编码来处理文本数据,以确保应用程序能够正确处理来自世界各地的字符和符号。

二、GBK Unicode

在Java中,并没有直接从GBK编码直接转换为Unicode编码的内置方法,因为Unicode并不是一种具体的编码格式,而是一种字符集的标准。然而,我们通常所说的“将字符串从GBK转换为Unicode”实际上是指将GBK编码的字节序列转换为Java内部使用的Unicode字符(在Java中,String 类就是基于Unicode的)。

在Java 8中,你可以使用String类的构造函数或new String(byte[] bytes, Charset charset)方法来将GBK编码的字节数组转换为Unicode字符。这里的关键是指定正确的字符集(Charset)为GBK。

以下是一个示例代码,展示了如何将GBK编码的字节数组转换为Unicode字符串(实际上,在Java中,这个转换是隐式的,因为String内部就是使用Unicode):

import java.nio.charset.Charset;  
import java.nio.charset.StandardCharsets;  
  
public class GbkToUnicode {  
    public static void main(String[] args) {  
        // 假设这是GBK编码的字节数组  
        byte[] gbkBytes = "你好世界".getBytes(StandardCharsets.GBK); // 注意:这里为了示例,我们实际上是从字符串转换到了GBK字节数组  
  
        // 将GBK编码的字节数组转换为Unicode字符串(在Java中,String就是基于Unicode的)  
        String unicodeString = new String(gbkBytes, StandardCharsets.GBK);  
  
        // 输出结果,验证转换是否正确  
        System.out.println(unicodeString); // 输出:你好世界  
  
        // 注意:这里并没有显式地“转换为Unicode”,因为String在Java中已经是Unicode了  
        // 如果你想要看到每个字符的Unicode码点,你需要遍历字符串并打印每个字符的码点  
        for (int i = 0; i < unicodeString.length(); i++) {  
            System.out.printf("\\u%04X ", (int) unicodeString.charAt(i));  
        }  
        // 注意:上面的循环对于补充字符(即码点大于U+FFFF的字符)可能无法正确工作  
        // 因为charAt方法返回的是char类型,它只能表示基本多语言平面(BMP)内的字符  
        // 对于补充字符,你应该使用codePointAt方法  
    }  
}

上面的代码示例中,String对象unicodeString实际上已经包含了以Unicode编码的字符。在Java中,当你处理字符串时,你不需要关心它们是如何在内部表示的(即,你不需要关心它们是如何被编码为字节的),因为Java的String类为你处理了这些细节。

如果你想要以某种方式(比如\uXXXX格式)查看字符串中每个字符的Unicode码点,你需要遍历字符串并使用Character.forDigit()方法(对于十六进制表示)或直接格式化输出来实现这一点,如上面的代码示例所示。但是,请注意,对于补充字符(即那些码点大于U+FFFF的字符),你需要使用Character.codePointAt(String s, int index)方法来获取完整的码点值。

三、UTF-8 Unicode

在Java中,将UTF-8编码的字节序列转换为Unicode字符串(在Java中,String类是基于Unicode的)是一个直接且隐式的过程。因为当你使用String类的构造函数或new String(byte[] bytes, Charset charset)方法时,并指定字符集为UTF-8(或省略字符集参数,因为Java的默认字符集通常是UTF-8,但这取决于JVM的默认设置和操作系统),Java就会将这些字节解码为Unicode字符。

下面是一个示例代码,展示了如何将UTF-8编码的字节数组转换为Unicode字符串(在Java中,这实际上是转换为一个String对象,该对象内部以Unicode表示):

import java.nio.charset.StandardCharsets;  
  
public class Utf8ToUnicode {  
    public static void main(String[] args) {  
        // 假设这是UTF-8编码的字节数组  
        // 注意:这里为了示例,我们实际上是从一个包含Unicode字符的字符串转换到了UTF-8字节数组  
        byte[] utf8Bytes = "你好世界".getBytes(StandardCharsets.UTF_8);  
  
        // 将UTF-8编码的字节数组转换为Unicode字符串(在Java中,String就是基于Unicode的)  
        String unicodeString = new String(utf8Bytes, StandardCharsets.UTF_8);  
  
        // 输出结果,验证转换是否正确  
        System.out.println(unicodeString); // 输出:你好世界  
  
        // 如果你想要看到每个字符的Unicode码点,可以遍历字符串并打印  
        for (int i = 0; i < unicodeString.length(); ) {  
            int codePoint = unicodeString.codePointAt(i);  
            System.out.printf("\\u%04X ", codePoint);  
            i += Character.charCount(codePoint); // 对于补充字符,需要增加索引  
        }  
    }  
}

在这个例子中,unicodeString已经是一个String对象,它内部以Unicode表示。当你遍历这个字符串并打印每个字符的Unicode码点时,你实际上是在查看这些字符在Unicode字符集中的编码。

请注意,codePointAt(int index)方法用于获取指定索引处的字符的码点,并且它能够正确处理Unicode中的补充字符(即码点大于U+FFFF的字符)。同时,Character.charCount(int codePoint)方法用于获取给定码点对应的char数组的长度(对于大多数BMP字符,这个长度是1,但对于补充字符,这个长度是2)。在遍历字符串时,你需要使用这个方法来正确地更新索引,以便能够遍历字符串中的所有字符。然而,在上面的例子中,由于我们假设字符串只包含BMP字符,所以也可以简单地递增索引(即i++),但这在处理包含补充字符的字符串时可能会出错。为了更健壮的代码,我使用了i += Character.charCount(codePoint);来更新索引。

四、GBK UTF-8

在Java 8中,处理GBK和UTF-8编码之间的转换主要涉及字节数组(byte[])和字符串(String)之间的转换,并明确指定字符集(Charset)。下面分别展示了如何将字符串从GBK编码转换为UTF-8编码的字节数组,以及如何将UTF-8编码的字节数组转换回字符串(在这个过程中,也可以认为是在进行反编码或解码操作)。

从GBK编码的字符串转换到UTF-8编码的字节数组

首先,你需要有一个GBK编码的字符串。然后,你可以先将这个字符串转换为GBK编码的字节数组,再将这个字节数组转换为UTF-8编码的字节数组。这里使用了String.getBytes(Charset charset)方法和new String(byte[] bytes, Charset charset)方法的组合。

import java.nio.charset.StandardCharsets;  
  
public class GbkToUtf8 {  
    public static void main(String[] args) {  
        // 假设这是GBK编码的字符串  
        String gbkString = "你好世界"; // 注意:这里的字符串实际上是以Unicode存储的,但我们可以假设它来源于GBK编码的源  
  
        // 将GBK编码的字符串转换为GBK编码的字节数组  
        byte[] gbkBytes = gbkString.getBytes(StandardCharsets.GBK);  
  
        // 将GBK编码的字节数组转换为UTF-8编码的字节数组  
        // 注意:这里我们不能直接将字节数组“转换”为另一种编码的字节数组,  
        // 但我们可以先将字节数组解码为字符串(使用原始编码),然后再将字符串编码为另一种编码的字节数组  
        byte[] utf8Bytes = new String(gbkBytes, StandardCharsets.GBK).getBytes(StandardCharsets.UTF_8);  
  
        // 由于我们不能直接看到字节数组的内容(除非转换为十六进制字符串),  
        // 所以这里只是展示了如何进行转换。通常,你不会直接打印字节数组,而是将其用于文件写入、网络传输等。  
  
        // 如果你想要验证转换结果,可以将UTF-8编码的字节数组再次解码为字符串,并查看结果  
        String utf8String = new String(utf8Bytes, StandardCharsets.UTF_8);  
        System.out.println(utf8String); // 输出:你好世界(如果GBK到UTF-8的转换没有数据丢失的话)  
  
        // 注意:在这个特定的例子中,由于“你好世界”在GBK和UTF-8中都有相同的字节表示(对于非ASCII字符可能不同),  
        // 所以输出看起来和原始字符串一样。但这并不意味着转换没有发生。  
    }  
}

重要说明:上面的代码示例在逻辑上可能有些误导,因为gbkString本身就是一个String对象,它在Java内部是以Unicode编码的。但是,为了演示GBK到UTF-8的转换过程,我们假设gbkString的原始内容是以GBK编码的字节序列转换而来的。在真实场景中,你可能会从文件、数据库或网络接收GBK编码的字节数据,并使用类似的方法进行处理。

从UTF-8编码的字节数组转换回字符串

这个过程相对简单,因为你只需要使用new String(byte[] bytes, Charset charset)方法,并指定字符集为UTF-8即可。

// 假设utf8Bytes是从某处获取的UTF-8编码的字节数组  
byte[] utf8Bytes = ...; // 这里应该是从文件、网络等获取的UTF-8编码的字节数组  
  
// 将UTF-8编码的字节数组转换为字符串  
String utf8String = new String(utf8Bytes, StandardCharsets.UTF_8);  
  
// 现在utf8String是一个包含原始文本内容的字符串,它是基于Unicode编码的  
System.out.println(utf8String);

在这个过程中,并没有显式地将字符串“转换”为Unicode,因为Java的String类内部就是基于Unicode的。相反,这个过程是将字节序列(根据指定的字符集)解码为Unicode字符。

五、jdk源码

关于字符集,jdk里给出的如下

/*
 * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */
package java.nio.charset;

/**
 * Constant definitions for the standard {@link Charset Charsets}. These
 * charsets are guaranteed to be available on every implementation of the Java
 * platform.
 *
 * @see Standard Charsets
 * @since 1.7
 */
public final class StandardCharsets {

    private StandardCharsets() {
        throw new AssertionError("No java.nio.charset.StandardCharsets instances for you!");
    }
    /**
     * Seven-bit ASCII, a.k.a. ISO646-US, a.k.a. the Basic Latin block of the
     * Unicode character set
     */
    public static final Charset US_ASCII = Charset.forName("US-ASCII");
    /**
     * ISO Latin Alphabet No. 1, a.k.a. ISO-LATIN-1
     */
    public static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1");
    /**
     * Eight-bit UCS Transformation Format
     */
    public static final Charset UTF_8 = Charset.forName("UTF-8");
    /**
     * Sixteen-bit UCS Transformation Format, big-endian byte order
     */
    public static final Charset UTF_16BE = Charset.forName("UTF-16BE");
    /**
     * Sixteen-bit UCS Transformation Format, little-endian byte order
     */
    public static final Charset UTF_16LE = Charset.forName("UTF-16LE");
    /**
     * Sixteen-bit UCS Transformation Format, byte order identified by an
     * optional byte-order mark
     */
    public static final Charset UTF_16 = Charset.forName("UTF-16");
}

    /**
     * Returns the character (Unicode code point) at the specified
     * index. The index refers to {@code char} values
     * (Unicode code units) and ranges from {@code 0} to
     * {@link #length()}{@code  - 1}.
     *
     * 

If the {@code char} value specified at the given index * is in the high-surrogate range, the following index is less * than the length of this {@code String}, and the * {@code char} value at the following index is in the * low-surrogate range, then the supplementary code point * corresponding to this surrogate pair is returned. Otherwise, * the {@code char} value at the given index is returned. * * @param index the index to the {@code char} values * @return the code point value of the character at the * {@code index} * @exception IndexOutOfBoundsException if the {@code index} * argument is negative or not less than the length of this * string. * @since 1.5 */ public int codePointAt(int index) { if ((index < 0) || (index >= value.length)) { throw new StringIndexOutOfBoundsException(index); } return Character.codePointAtImpl(value, index, value.length); }

 


    /**
     * Returns the code point at the given index of the
     * {@code char} array, where only array elements with
     * {@code index} less than {@code limit} can be used. If
     * the {@code char} value at the given index in the
     * {@code char} array is in the high-surrogate range, the
     * following index is less than the {@code limit}, and the
     * {@code char} value at the following index is in the
     * low-surrogate range, then the supplementary code point
     * corresponding to this surrogate pair is returned. Otherwise,
     * the {@code char} value at the given index is returned.
     *
     * @param a the {@code char} array
     * @param index the index to the {@code char} values (Unicode
     * code units) in the {@code char} array to be converted
     * @param limit the index after the last array element that
     * can be used in the {@code char} array
     * @return the Unicode code point at the given index
     * @exception NullPointerException if {@code a} is null.
     * @exception IndexOutOfBoundsException if the {@code index}
     * argument is negative or not less than the {@code limit}
     * argument, or if the {@code limit} argument is negative or
     * greater than the length of the {@code char} array.
     * @since  1.5
     */
    public static int codePointAt(char[] a, int index, int limit) {
        if (index >= limit || limit < 0 || limit > a.length) {
            throw new IndexOutOfBoundsException();
        }
        return codePointAtImpl(a, index, limit);
    }

    // throws ArrayIndexOutOfBoundsException if index out of bounds
    static int codePointAtImpl(char[] a, int index, int limit) {
        char c1 = a[index];
        if (isHighSurrogate(c1) && ++index < limit) {
            char c2 = a[index];
            if (isLowSurrogate(c2)) {
                return toCodePoint(c1, c2);
            }
        }
        return c1;
    }

 


    /**
     * Converts the specified surrogate pair to its supplementary code
     * point value. This method does not validate the specified
     * surrogate pair. The caller must validate it using {@link
     * #isSurrogatePair(char, char) isSurrogatePair} if necessary.
     *
     * @param  high the high-surrogate code unit
     * @param  low the low-surrogate code unit
     * @return the supplementary code point composed from the
     *         specified surrogate pair.
     * @since  1.5
     */
    public static int toCodePoint(char high, char low) {
        // Optimized form of:
        // return ((high - MIN_HIGH_SURROGATE) << 10)
        //         + (low - MIN_LOW_SURROGATE)
        //         + MIN_SUPPLEMENTARY_CODE_POINT;
        return ((high << 10) + low) + (MIN_SUPPLEMENTARY_CODE_POINT
                                       - (MIN_HIGH_SURROGATE << 10)
                                       - MIN_LOW_SURROGATE);
    }

根据jdk源码,实现 


    /**
     * 将字符串转成unicode
     *
     * @param str 待转字符串
     *
     * @return unicode字符串
     */
    public static String convert(String str) {
        str = (str == null ? "" : str);
        String tmp;
        StringBuffer sb = new StringBuffer(1000);
        char c;
        int i;
        int j;
        sb.setLength(0);
        for (i = 0; i < str.length(); i++) {
            c = str.charAt(i);
            sb.append("\\u");
            j = (c >>> 8); // 取出高8位
            tmp = Integer.toHexString(j);
            if (tmp.length() == 1) {
                sb.append("0");
            }
            sb.append(tmp);
            j = (c & 0xFF); // 取出低8位
            tmp = Integer.toHexString(j);
            if (tmp.length() == 1) {
                sb.append("0");
            }
            sb.append(tmp);

        }
        return (new String(sb));
    }

    /**
     * 将unicode 字符串
     *
     * @param str 待转字符串
     *
     * @return 普通字符串
     */
    public static String revert(String str) {
        char aChar;
        int len = str.length();
        StringBuffer outBuffer = new StringBuffer(len);
        for (int x = 0; x < len;) {
            aChar = str.charAt(x++);
            if (aChar == '\\') {
                aChar = str.charAt(x++);
                if (aChar == 'u') {
                    // Read the xxxx
                    int value = 0;
                    for (int i = 0; i < 4; i++) {
                        aChar = str.charAt(x++);
                        switch (aChar) {
                            case '0':
                            case '1':
                            case '2':
                            case '3':
                            case '4':
                            case '5':
                            case '6':
                            case '7':
                            case '8':
                            case '9':
                                value = (value << 4) + aChar - '0';
                                break;
                            case 'a':
                            case 'b':
                            case 'c':
                            case 'd':
                            case 'e':
                            case 'f':
                                value = (value << 4) + 10 + aChar - 'a';
                                break;
                            case 'A':
                            case 'B':
                            case 'C':
                            case 'D':
                            case 'E':
                            case 'F':
                                value = (value << 4) + 10 + aChar - 'A';
                                break;
                            default:
                                throw new IllegalArgumentException("Malformed   \\uxxxx   encoding.");
                        }
                    }
                    outBuffer.append((char) value);
                } else {
                    if (aChar == 't') {
                        aChar = '\t';
                    } else if (aChar == 'r') {
                        aChar = '\r';
                    } else if (aChar == 'n') {
                        aChar = '\n';
                    } else if (aChar == 'f') {
                        aChar = '\f';
                    }
                    outBuffer.append(aChar);
                }
            } else {
                outBuffer.append(aChar);
            }

        }
        return outBuffer.toString();
    }

--end-- 

你可能感兴趣的:(快速开发,java,前端,开发语言)