字符编码和字符集有什么区别?Unicode是什么,和UTF-8是什么关系?你想知道的都在这篇文章了

2023-11-17

前言

想必大家编写代码时肯定和我一样,也遇到过汉字乱码的问题。特别是,有时候和上下游对接接口,不能统一编码格式的话,一堆乱码问题,让人头皮发麻。

那么为什么会有这么多的乱码问题?

什么是字符编码?什么是字符集?他们之间有什么区别和联系?

什么是 Unicode ? Unicode 和我们常说的 UTF-8 又有什么关系?

字符编码和解码

要想搞清楚上面的问题,首先我们要知道,在计算机中,不管是一段文字、一张图片还是一段视频,最终都是以二进制的方式来存储。也就是最终都会转化为 0001 1011 0010 0110 这样的格式。

换句话说,计算机只认识 0 和 1 这样的数字,并不能直接存储字符。所以我们需要告诉它什么样的字符对应的是什么数字。

例如,我们的业务中有记录客户端的客户行为日志,然后导出文件来分析,字段间会以 ESC 来分隔。

我在编写代码的时候,就需要定义一下这个ESC 字符应该对应什么数字,这样计算机才能识别并存储。

比如我把它定为 0001 1011,这样计算机就把 ESC 这个字符存了下来。等我下次需要查看的时候,根据对应关系把它解出来就可以了。

上边的两个过程就对应字符的编码和解码过程。

字符编码就是把字符按一定的规则,转换成数字。字符解码是编码的逆过程,即把数字按规则转换成字符。

这样看来,貌似没有什么问题。

但是,这是我自己定义的编码规则,我同桌阿霄就不乐意了。他非要认为 ESC 应该定义为 1101 1000,好家伙正好和我定义的二进制数字顺序相反。

那结果肯定不用说了,我把 0001 1011 这串数字给他之后,按照他的编码规则来解,肯定是 &$#!这样的东西。

所以,乱码问题说到底,就是编码和解码的规则对应不上导致的。

ASCII 码

为了避免我和阿霄因为编码问题打起来,美国国家标准学会(AMERICAN NATIONAL STANDARDS INSTITUTE) ANSI 组织发话了。

停、停、停。不就是个编码问题吗,这种小事犯不着动手,我定义一个统一的规则,大家都按照我的规则来编码和解码不就好了嘛。

于是,ASCII 码出现了,它定义了一个常用字符集,用来表示字符和数字的对应关系,如下表。

ASCII 码全称:美国信息交换标准代码 (American Standard Code for Information Interchange)

图片来自百度图片

我一查表,ESC 字符不就对应 27 吗,对应的二进制就是 0001 1011 。我去,没想到我定义的规则竟和 ANSI 不谋而合。

同桌阿霄把抡在空中的拳头收了起来,默默地回去敲代码了。

ASCII 码扩展码

在使用英语的国家,ASCII 码就足够用了。但是,在其他欧洲发达国家比如法国,使用的语言是法语,有类似于这样的 á 符号,ASCII 码就不能表示了。那怎么办呢?

我们看上表就会发现,ASCII 码表的表示范围是十进制 0~127,也就是二进制 0000 00000111 1111 。其实只是用了后边的 7 位,第一位都是 0 。

而计算机二进制中一个字节是 8 个位,现在只用了 7 位。不行啊太浪费了,要充分利用第一个高位,扩展一下,这样多了一位,能表示的字符范围就多了一倍。(2的8次方=256)

这样一些欧洲其他国家,也能在计算机中表示自己的文字了。

后来,随着计算机的普及,中国的用户也多了起来。却发现,一个字节只能表示 256 个字符,远远不能满足我们的要求。

于是,就出现了 GB2312 编码,它使用了两个字节来表示一个汉字。但是,并没有把所有的位都用完,前面一个字节范围 0xA1 ~ 0xF7 (即 10110001 ~ 11110111),后面一个字节范围 0xA1 ~ 0xFE (即 10110001 ~ 11111110) 。 这样就能表示简体汉字 6763 个。

GB2312 是国家标准总局发布的《信息交换用汉字编码字符集》,也可以说是简体中文的字符集。

但是,台湾和香港等使用繁体字的地区怎么办。于是,就有了大五码 Big5 编码来存储繁体。高字节(第一个字节)表示范围 0x81~0xFE,低字节(第二个字节)表示范围 0x40 ~ 0x7E,以及0xA1 ~ 0xFE 。

需要注意的是,GB2312 是简体中文,Big5 是繁体中文。如果用其中一种编码文字去读另外一种编码文字就会乱码。所以,就出来了 GBK 编码,把简体中文和繁体中文,以及一些 GB2312 不支持的人名(如历代总理有的名字用 GB2312 打不出来),还有一些我们不认识的古汉语都包含进去,共 2 万多个字符。

再然后,我们发现少数民族像藏文,蒙古文这些少数民族的语言,GBK 也支持不了,就再进行扩展,出现了 GB18030 。又多了几千个少数民族的文字。

所以,我们使用的 GB 国标系列文字都是在 ASCII 码之上扩展的,它们是依次向下兼容的。表示文字范围从小到大为 GB2312 = Big5 < GBK < GB18030 。

Unicode 字符集

我们在打开一个文档之前,就必须要知道它的编码格式,否则用错误的方式解码就会出现乱码情况。

设想,如果一个文本中,有多种类型文字,包括中文,韩语,德语,日语,应该用哪种编码方式?貌似怎么处理都会有乱码问题,那怎么办呢?

ISO(国际标准化组织)说:这好办啊,我把地球上,只要是人们使用的,所有语言和符号都囊括其中,为每个字符都指定一个唯一的字符码,这样就没有乱码问题了。于是 Unicode 出现了,又叫统一码,万国码。

如上图表,汉字“一”对应的 unicode 码是 \u4e00。 我们通常在字符码前加个 \u代表这是 unicode 码。4e00 是十六进制表示。

也有很多在线转码工具供我们使用,如:http://tool.chinaz.com/tools/unicode.aspx

Unicode 编码方案

首先强调一下以下几个概念的区别:

  1. 字符:就是我们看到的一个字母或一个汉字、一个标点符号都叫字符。如上边的汉字“一”就是一个字符。
  2. 字符码:在指定的字符集中,一个字符对应唯一一个数字,这个数字就叫字符码。如上边的字符“一”,在 Unicode 字符集中,对应的字符码为 \u4e00
  3. 字符集:规定了字符和字符码之间的对应关系。
  4. 字符编码:规定了一个字符码在计算机中如何存储。

需要注意的是,Unicode 只是一个字符集,它规定了每个字符对应的唯一字符码,却没有规定这个字符码在计算机中怎样存储(也就是它的字符编码格式)。

例如,上边的汉字“一”,它的 Unicode 字符码为 \u4e00,转换成二进制就是 100 1110 0000 0000 。可以看到,它有 15 位二进制数,至少需要两个字节来存储。

这只是简单的汉字,如果其他复杂的字符有可能会需要 三、四 个字节或者更多字节来存储。

那么到底应该用几个字节来存储呢?

于是 UTF-32 编码 制定了标准,一个字符就用四个字节来表示。这样编码和解码都方便,固定取 32 位二进制就行了。

但是这样又引来一个问题。比如 A 字符其实只需要一个字节就可以存储了。如果必须要用四个字节来存储,那么前边三个字节都要补 0 ,这样势必会造成空间的浪费。

于是 UTF-16 编码(一个字符用两个字节或者四个字节)和我们熟悉的 UTF-8 编码格式就出现了。

这里我们重点介绍 UTF-8 。它使用一种变长的编码方式,可以使用 1~4 个字节来表示一个字符。根据不同的字符变换长度。

变长听起来很美好,但是它的不固定性,就让计算机懵逼了。比如,计算机怎么知道这四个字节代表的是一个字符,还是四个字符,亦或是两个字符呢?

于是,UTF-8 规定了以下编码规则,来避免以上问题。

  • 对于单字节的符号,第一位设为0,后边 7 位对应这个字符的ASCII码值。因此,像“A"这样的英文字母,UTF-8 编码和 ASCII 编码是相同的。
  • 对于大于一个字节的符号,假设为 n 字节,那么第一个字节的前 n 位都设为 1,这样有几个 1 就说明有几个字节。然后,第 n+1 位设为0 。后边的字节,前两位都设为10 ,剩余的其他二进制位都用这个字符的 Unicode 码填充(从后向前填充,不够补0)。
字节个数 Unicode符号范围(16进制) UTF-8 编码格式(二进制)
1(单字节) 0000 0000 ~ 0000 007F 0xxxxxx
2 0000 0080 ~ 0000 07FF 110xxxxx 10xxxxxx
3 0000 0800 ~ 0000 FFFF 1110xxxx 10xxxxxx 10xxxxxx
4 0001 0000 ~ 0010 FFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

刚开始看上表,可能比较懵逼。其实,Unicode 符号表示的范围最大为四个字节,因此二进制为 4*8=32 位。我们知道,二进制转换十六进制时,以四位为一个单位转换,因此,对应的十六进制为 32/4=8 位。

上表中的 Unicode 符号范围是以 16 进制表示,可以看到就是 8 位的。

我们还是以汉字 “一” 为例,16进制表示为 4e00,补全所有位,其实就是 0000 4E00 (不区分大小写)。因此,查上表发现,它处在三个字节的 Unicode 范围内(0000 0800 < 0000 4e00 < 0000 FFFF)。

所以,它用 UTF-8 来编码,就是三个字节的,即格式是这样的 1110xxxx 10xxxxxx 10xxxxxx

4e00 转换为二进制为 100 1110 0000 0000,二进制位从后向前依次填充到上述格式中的x位置(也是从后向前填充)。

于是,就得出汉字 “一” 的 UTF-8 编码后的二进制表示为:1110 0100 1011 1000 1000 0000

其实,可以发现,汉字的二进制为 15 位,前边补零一位即为 16 位 0100 1110 0000 0000。而三个字节的 UTF-8 编码格式中的 x 个数也为 3*8 - (4+2+2) = 16 位,正好一一对应。

那么,我们这一通推算,是否正确呢。可以在程序中打印这个字符的二进制格式,以及UTF-8编码后的二进制。程序如下,

public class Test {
    public static void main(String[] args) throws UnsupportedEncodingException {
        System.out.println("字符'一'的二进制为:" + Integer.toBinaryString('一'));
        System.out.println("========");
        String str = "一";
        System.out.println("转换为UTF-8编码格式的二进制为:"+ toBinary(str,"utf-8"));
    }

    public static String toBinary(String str, String encode) throws UnsupportedEncodingException {
        StringBuilder sb = new StringBuilder();
        byte[] bytes = str.getBytes(encode);
        for (int i = 0; i < bytes.length; i++) {
            byte b = bytes[i];
            sb.append(Integer.toBinaryString(b & 0xFF));
        }
        return sb.toString();
    }

}

打印结果为:

字符'一'的二进制为:100111000000000
========
转换为UTF-8编码格式的二进制为:111001001011100010000000

PS:通常的,我们发现常用的汉字以 16 进制表示都在 0000 0800 ~ 0000 FFFF 范围内。因此,汉字在 UTF-8 编码下通常占用三个字节。

细心的同学可能发现了,我上边转换的汉字可以用 char 类型来存储,这是为什么呢?

这是因为,在 Java 中,默认使用的字符集就是 Unicode,可以容纳 100 多万个字符,其中就包括汉字。

我们使用的绝对大多数汉字,都在0000 0800 ~ 0000 FFFF 这个范围内,可以看出来前边的四位十六进制都用不到(都是0000),因此,只需要后边的四位十六进制位,转换为二进制就是 4*4=16 位,只占用了两个字节(16/8=2)。而 char 在 Java 中占用两个字节,完全可以用来存储汉字。

总结

最后,来解答下文章开头的问题。

乱码的问题,究其根本原因,其实是编码和解码时的规则不一样导致的。

字符编码和字符集是两个不同的概念。一句话表示:字符集定义了字符到数字的映射关系,字符编码定义了这个数字如何在计算机中表达(存储)。

对于 ASCII 和 GB 系列,他们既是字符集也是字符编码。GB 兼容 ASCII 码。

而对于 Unicode 来说,字符集是 Unicode,而字符编码可以是 UTF-8,UTF-16 和 UTF-32 。所以,我们平时常用的 UTF-8 编码其实只是 Unicode 的一种编码实现方式而已。

本期内容你学会了吗,把学会打在评论区。。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

字符编码和字符集有什么区别?Unicode是什么,和UTF-8是什么关系?你想知道的都在这篇文章了 的相关文章

  • 如何以编程方式找出我的 PermGen 空间使用情况?

    我正在尝试诊断java lang OutOfMemoryError PermGen Space在 Sun 的 Hotspot JVM 上运行时出现错误 并且想知道我的程序在不同时刻使用了多少 PermGen 空间 有没有办法以编程方式找出这
  • 如何在java中压缩/解压tar.gz文件

    谁能告诉我在java中压缩和解压缩tar gzip文件的正确方法我一直在搜索 但我能找到的最多的是zip或gzip 单独 我写了一个包装器公共压缩 http commons apache org compress called jarchi
  • Java Sqlite Gradle

    我对 gradle 和 java 还很陌生 我有一个使用 sqlite 的项目 它通过 intellij idea 运行良好 但我无法从终端运行它 它会抛出异常 java lang ClassNotFoundException org sq
  • 如何降低圈复杂度?

    我正在开发一个将 RequestDTO 发送到 Web 服务的类 我需要在发送请求之前验证该请求 请求可以从 3 个不同的地方发送 并且每个 请求类型 有不同的验证规则 例如请求1必须有姓名和电话号码 请求2必须有地址等 我有一个 DTO
  • 在哈希图中存储字符和二进制数

    我正在尝试存储字母到二进制数的映射 这是我的映射 h 001 i 010 k 011 l 100 r 101 s 110 t 111 为此 我创建了一个哈希映射并存储了键值对 我现在想显示给定句子的相应二进制值 这是我的代码 package
  • 如何在Mac上使用eclipse安装jetty

    我是一个新手 jetty 和 RESTful API 我想使用 Jetty 创建 REST 服务 并希望将嵌入式 jetty 与 eclipse 一起使用 任何人都可以建议我在 Mac OS 中使用 Eclipse 安装 Jetty Jet
  • 使用 JAXB 编组 LocalDate

    我正在构建一系列链接类 我希望能够将其实例编组到 XML 以便我可以将它们保存到文件中并稍后再次读取它们 目前我使用以下代码作为测试用例 import javax xml bind annotation import javax xml b
  • java中队列的实现

    在 Java 中实现队列是一个非常常见的面试问题 我在网上冲浪 看到了许多实现 他们做了一些奇特的事情 比如实现队列接口和编写自己的addLast and removeFirst 方法 我的问题是我不能使用LinkedList 类并使用其预
  • JAX-WS:有状态 WS 在独立进程中失败

    我在 Tomcat 上部署了一个有状态的 Web 服务 它由工厂服务和主要 API 服务组成 并且工作得很好 工厂服务将 W3CEndpointReference 返回到主 API 实例 客户端使用会话 现在 我尝试将相同的服务作为独立应用
  • 为什么一个线程会中断另一个线程[重复]

    这个问题在这里已经有答案了 在Java多线程应用程序中 我们处理InterruptedThreadException 如果另一个线程中断当前线程 则会抛出此异常 现在 当另一个线程知道它将导致异常时 它可能想要中断当前线程的原因是什么 很多
  • 如何屏蔽 Protobuf 中的某些字段

    我找不到一种方法来屏蔽 protobuf 结构中的某些字段 我确实阅读了有关 FieldMaskUtil 的内容并尝试了几个示例 但它似乎做了相反的操作 即复制 FieldMask 中提到的字段 这与我想要的不同 这是示例结构和相应的测试代
  • 此版本不符合 Google Play 64 位要求,添加库后仍然出现错误

    我正在 Play 商店上传一个视频编辑器应用程序 其中包含带有一些本机代码的库 所以我通过将其添加到 gradle 来使其兼容 64 位 ndk abiFilters armeabi v7a arm64 v8a x86 x86 64 添加了
  • Java 中 static 关键字如何工作?

    我正在阅读Java教程 http docs oracle com javase tutorial index html从一开始我就有一个问题static字段或变量上的关键字 作为Java said here http docs oracle
  • Java 类:匿名类、嵌套类、私有类

    有人能解释一下Java中匿名类 嵌套类和私有类之间的区别吗 我想知道与每个相关的运行时成本以及每个编译器的方法 这样我就可以掌握哪个最适合用于例如性能 编译器优化的潜力 内存使用以及其他 Java 编码人员的普遍可接受性 我所说的匿名类是指
  • Netty中连接关闭后重新连接的最佳方法是什么

    简单场景 扩展 SimpleChannelUpstreamHandler 的较低级别的类 A 此类是发送消息和接收响应的主力 系统其他部分可以使用顶级类 B 来发送和接收消息 可以模拟同步和异步 此类创建 ClientBootstrap 设
  • 为什么 RMI 注册表忽略 java.rmi.server.codebase 属性

    我正在运行 java RMI 的 Hello World 示例 1 我在空文件夹中运行注册表 motta motta laptop tmp rmiregistry 2 我启动 HTTP 服务器以在运行时检索类 下载文件夹包含客户端 服务器的
  • 使用 Cucumber Scenario Outline 处理 Excel 电子表格

    如果可能的话 我试图找到一种更优雅的方法来处理从与 Excel 电子表格行 第 n 个 相关的 Cucumber Scenario Outline 中调用第 n 个数字 目前 我正在使用迭代编号来定义要从中提取数据的 Excel 电子表格的
  • java中wav文件转换为字节数组

    我的项目是 阿塞拜疆语音的语音识别 我必须编写一个程序来转换wav文件到字节数组 如何将音频文件转换为byte 基本上如第一个答案中的片段所描述 但不是BufferedInputStream use AudioSystem getAudio
  • 如何使用maven创建基于spring的可执行jar?

    我有一个基于 Maven 的 Spring WS 客户端项目 我想将其打包为单个 jar 在eclipse中 一切运行正常 当我尝试将其打包为可执行 jar 时 我收到 ClassNotFound 异常 因为 Spring jar 未包含在
  • Java中的媒体播放器库[关闭]

    Closed 这个问题正在寻求书籍 工具 软件库等的推荐 不满足堆栈溢出指南 help closed questions 目前不接受答案 我正在评估用于在 Java 中播放音频 视频的库 它不需要 100 Java Java 与本机库的绑定

随机推荐

  • 【Python_requests学习笔记(九)】基于requests和threading模块实现多线程爬虫

    基于requests和threading模块实现多线程爬虫 前言 此篇文章中介绍基于 requests 和 threading 模块实现多线程爬虫 并以 抓取Cocos中文社区中 热门主题下的帖子名称及id数据 为例进行讲解 因主要介绍如何
  • 华大单片机HC32L130 / HC32L136 / HC32F030 系列硬件开发指南

    适用对象 系列 产品型号 HC32L130 HC32L130E8PA HC32L130F8UA HC32L130J8TA HC32L130J8UA HC32L136 HC32L136J8TA HC32L136K8TA HC32L130 HC
  • 三角函数常见基本公式

    定义式 图形 正弦 sin 余弦 cos 正切 tan或tg 余切 cot或ctg 正割 sec 余割 csc 函数关系 商数关系 倒数关系 平方关系 和差角公式 二角和差公式 三角和公式 积化和差公式 倍角公式 二倍角公式 三倍角公式 四
  • centos7初始化操作-时间同步/网络防火墙/本地源/ssh/等

    一 chrony安装及配置 验证 说明 协议 NTP协议 时间同步必要场景 集群 日志 加密协议等 相关文章 https blog csdn net weixin 44515412 article details 106875753 1 安
  • 使用Lodop控件打印表单和二维码

    文章目录 1 了解Lodop 1 1Lodop的定义 1 2Lodop主要函数 1 3Lodop的下载 2 在页面中引入Lodop 3 支持的浏览器 4 Lodop的应用 4 1使用Lodop打印表单 4 2打印二维码 1 了解Lodop
  • Python学习笔记(十二)————判断语句相关

    目录 1 布尔类型的定义 2 比较运算符 3 if语句 4 if else语句 5 if elif else语句 1 布尔类型的定义 布尔类型的字面量 True 表示真 是 肯定 False 表示假 否 否定 布尔类型的数据 不仅可以通过定
  • UNI APP---Android端原生插件开发实战(一)

    1 前言 最近一个项目要求我们的产品必须走网络隧道 并且提供了对应的SDK 很明显只能通过原生开发的方式才能实现这个流程 笔者没有做过原生开发 也没有学过java 所以也踩了不少坑啊 花了两天时间总算完成任务 今天系统的总结下步骤 由于是根
  • 第一章 Qt入门

    2017 10 20 HelloZEX 感谢奇趣科技公司 Trolltech Qt门户 https www qt io 为我们提供了Qt 一种图形用户界面框架 利用Qt提供的C 应用程序开发框架 可以轻松实现 一次编写 随处编译 跨平台解决
  • 深度学习过程中测试准确率先上升后下降是为什么?

    很典型的过拟合问题 过拟合问题的表现 在训练过程中的验证准确率前上升 后下降 即在验证集上的表现先好后差 造成过拟合的原因 数据相对模型来说比较简单 即模型的表达能力过剩 在初始训练阶段 模型逐渐开始学习到一些信息 因而在验证集上的表现是提
  • setTimeout引发的刨根问底

    setTimeout 定时器 是JavaScript中一个比较重要且常用的方法 该方法用于在指定的毫秒数后调用函数或计算表达式 平时开发可能基本都是使用 setTimeout fn ms 的形式 当然还有比较神奇的用法 特别是在前端面试中
  • flex弹性布局教程-07容器属性flex-direction

    本节目标 掌握flex direction的使用 了解主轴变化的概念 内容摘要 本篇讲解了容器属性 flex direction 用来设置主轴的方向 阅读时间大约10 15分钟 flex容器属性 前面讲的属性都是设置在项目上的 另外还有6个
  • hud 1467(动态规划。解题报告)

    题目的地址 http acm hdu edu cn showproblem php pid 1467 在给出的三角型中 找出最大的白色三角形 三角形最大个数可用层数表示 分别对向上 和向下的三角形进行遍历 如果向下的三角形a i j 为白
  • 5V升压8.4V 25W升压充电一体芯片选型

    第一款AH3300 5V升压8 4V 25W升压充电一体芯片AH3300是一款工作于4V到28V的PFM升压型充电控制集成电路 AH3300两串锂电池升压充电芯片 内部集成有基准电压源 5V电压调制单元 电感电流检测单元 电池电压检测电路和
  • 离线环境下批量配置Python的各种依赖包

    如何在离线环境下一次性的批量配置Python依赖包 如果你的工作环境要求断网或离线 那么在进行Python的环境配置时需要频繁下载依赖包 例如在配置tensorflow或者mmdetection等 如果一个一个下载导入内网那就很坑 如何批量
  • Python(六):函数与Lambda表达式

    函数与Lambda表达式 1 函数 还记得 Python 里面 万物皆对象 么 Python 把函数也当成对象 可以从另一个函数中返回出来而去构建高阶函数 比如 参数是函数 返回值是函数 函数的定义 函数以def关键词开头 后接函数名和圆括
  • Redis-cli简单操作命令

    Redis一大特点就是提供list set zset hash等数据结构的存储 下面就是它的一些主要操作命令 redis cli h ip p 6379 SELECT 1 切换数据库 DBSIZE 查看当前数据库key的数量 FLUSHDB
  • shell 里执行sqlldr,not found

    解决方法 设置用户的环境变量 方法1 把Oracle的环境变量加到运行命令的用户的 home username bash profile里 方法2 直接切换Oracle用户执行
  • Pandas的学习(读取mongodb数据库集合到DataFrame,将DataFrame类型数据保存到mongodb数据库中)

    1 读取mongodb数据库集合到DataFrame import pymongo import pandas as pd client pymongo MongoClient 数据库连接字符串 第二种方式 host localhost p
  • python3 No module named 'PIL'

    在python3 scrapy框架已经安装好的情况下 还是出现了 python3 No module named PIL 在Python3下 PIL已经被Pillow替代了 所以只需要安装Pillow就可以了 使用命令 pip3 insta
  • 字符编码和字符集有什么区别?Unicode是什么,和UTF-8是什么关系?你想知道的都在这篇文章了

    前言 想必大家编写代码时肯定和我一样 也遇到过汉字乱码的问题 特别是 有时候和上下游对接接口 不能统一编码格式的话 一堆乱码问题 让人头皮发麻 那么为什么会有这么多的乱码问题 什么是字符编码 什么是字符集 他们之间有什么区别和联系 什么是