乐趣区

关于前端:你知道-biangbiang-面和编码有什么关系吗万字长文手拉手带你一步步认识编码

前言

这篇文章是钻研上传文件的时候扩大进去的知识点,因为上传文件的时候会波及到文件的编码等内容,作为前端平时接触这些货色又比拟少,我又是个不彻底搞清楚问题就不罢休的人,所以往往会因为一个小问题牵扯进去一堆问题,接着,疑难又带来新的疑难(禁止套娃!)。

写这篇文章花了很长时间,如果你看完之后有所播种,一个 Star ✨就是对我最好的激励。

ps: 文章不会纠结一些不太重要的信息,比方某协定是谁提出的,在什么年份提出的,这些基本上看过一次就忘了,也不重要,我会提取重要的信息来分享给大家。

前置常识

在开始之前,先整顿一下咱们所须要的前置常识,对于这些咱们只须要有个简略的印象即可。

计算机罕用的单位换算

二进制位、位、比特、bit、b,这些都是代表计算机存储的最小单位「位」,也就是二进制的 01

1B = 8b

字节、Byte、B,这些单位也是表白的同一个意思「字节」,1 个「字节」等于 8「位」。

1kb = 1024B

kb、mb,这些大家应该就要相熟多了,获取文件大小的时候都能够看到这些单位。

1mb = 1024kb

前端中的进制转换

// 十六进制转十进制
parseInt(0x0f, 16);
// 十六进制转二进制
(0x0f).toString(2);

// 十进制转十六进制
(15).toString(16);
// 十进制转二进制
(15).toString(2);

// 二进制转十六进制
parseInt(1111, 2).toString(16);
// 二进制转十进制
parseInt(1111, 2);

计算机之初,芜杂无奈对立的编码方案

为了不便了解,文章以 UNICODE 呈现作为工夫线划分,UNICODE 就是盘古开天辟地的那一斧子。

ASCII 的到来

家喻户晓,在计算机中,所有的数据存储和运算时都要应用二进制示意,因为计算机用高电平和低电平示意 10。而后就有这么一群人,他们决定 用 8 个二进制位组成码位示意所有字符

8 个二进制位,每个地位都能够是 0 或者 1,所以一共有 2^8 = 256 个码位,能够示意 256 个字符,这些字符又分为控制字符、通信专用字符和可显示字符。

控制字符和通信专用字符放在一起说,从 0000 0000 ~ 0001 1111,再加上 0111 1111,一共是 33 个码位,也就是有 33 个字符。比方,0000 0111,示意的意思是响铃,那时的计算机在接管到 0000 0111 的时候就会铃铃作响。

可显示字符,第 0010 0000 ~ 0111 1110,一共是 95 个,比方 0011 0000,示意的意思是 0,再比方 0100 1010,示意的是大写英文字母 J

因为计算机刚开始只在美国应用,那大家相安无事,用的挺好,这些字符依照规定的程序排排坐所产生的表,就是咱们在 C 语言外面学到的 ASCII 表

ps: 当初教育太疯狂了,意识的小孩小学就在学编程,他当初就晓得 ASCII 码了 0.0

ASCII 的扩大表以及 GBK 编码方案

起初,因为计算机的倒退,一些东方国家开始在 ASCII 码表的前面减少本人国家的字符和制表符等字符,这就是 ASCII 扩大表,他占用了 1000 0000 ~ 1111 1111 的地位来示意本人的字符。

ps: ASCII 扩大表在不同系统配置的内码表也不同,这里就不赘述了,想要理解的同学能够参考 这个文档

再起初,计算机流传的越来越远,来到了第三世界。咱们发现,泱泱中华数以万计的文字,ASCII 码表是不可能放下咱们的字符了,256 个地位全给咱们都不够,那该怎么办?

聪慧的中国人间接把 127 号码位之后的奇怪符号勾销掉(也就是 ASCII 扩大表的内容),规定,一个小于 127 的字符意义与原来雷同,但两个大于 127 的字符连在一起时,就示意一个汉字,后面的一个字节(高字节)从 0xa1 ~ 0xf7,前面一个字节(低字节)从 0xa1 ~ 0xfe。(这里开始文章就不必二进制来示意码位了,写起来太长,而且一堆 0 1 看着也不不便,我就把二进制转为其对应的十六进制,0x 打头就代表十六进制)

这样咱们既能够保障在 ASCII 表中的英文字母不会显示乱码,另外还能组合出八千多个地位来放本人的文字、数学符号、日文假名等,而且,咱们甚至把原先在 ASCII 码表里就有的标点符号,又全副编了两个字节长的字符,这就是咱们常说的全角字符了,而 127 号以下的符号就称为半角字符。(「,」和「,」看出不同了吗?前者是半角字符,后者是全角字符)

起初这个计划用着还不错,咱们就给它取了个名字 GB2312,后面的 GB 意思是国标。

然而咱们的汉字切实太多了,八千多个地位还是不够,那咱们罗唆就不要求低字节是 127 号之后的了,规定只有高字节大于 127,那就代表这是 GBK 编码方案中代表的字符。这个编码方案就称为 GBK,GBK 不仅包含了 GB2312 的所有内容,还减少了好多汉字和繁体字。

再起初,少数民族也要用电脑了,要把他们的文字也加进去,于是就再进行扩大,GBK 扩成了 GB18030

有趣味的同学能够到 这个网站 查问国标对应字符的码位。

听起来是不是挺完满的?GB18030,简直能够囊括你能见到的所有中文,然而咱们这只解决了中文的编码,无奈显示其余国家或者地区的文字,比方,那时候还有一个编码方案叫 Big5,遍及与台湾、香港、澳门等繁体中文通行区,倚天中文系统、window 繁体中文等零碎的字符集都是以 Big5 为基准。

你看,中国的边疆和港澳台编码方式都不一样,那其余国家就更不用说了,后果就是,大家都闭门造车,如果须要看其余国家的文档那就得装置切换其余国家的编码方案。

UNICODE 万国码的问世

再这么凌乱上来必定不行,这时候,创立一个 囊括全世界的字符的字符集 就势在必行,这个时候,有两个组织开始着手对立字符集,国际标准化组织(ISO)开发的 ISO 10646 字符集,对立码联盟开发的 UNICODE 字符集。再起初,他们意识到本人应该做的是统一标准而不是吃一堑; 长一智,最终才有了咱们在用的 UNICODE 字符集,当然 ISO 10646 字符集仍然存在,并且和 UNICODE 共存,而且依据规约,他们各自的码位的字符含意都雷同。

因为咱们比拟相熟的是 UNICODE,所以接下来的内容我都会以 UNICODE 为主。

UNICODE 立体的含意以及码位区段阐明

刚开始 UNICODE 的做法很简略,ASCII 的 1 个字节(8 位)不是不够吗,那就 2 个字节(16 位),2^16 = 65536 个码位,这总够了吧?

后果是被啪啪打脸,在被各个国家的字符践踏了一遍之后,不行,这得改,于是决定取 UNICODE 中的两段区域 0xd800 ~ 0xdbff(高代理位)和 0xdc00 ~ 0xdfff(低代理位),用他们来组成新的码位。这两段代理位都有 1024 个码位,那就是减少了 1024^2 = 1048576 个码位,再加上原先的 2^16 = 65536 个码位,所以按情理来说,UNICODE 一共有 1048576 + 65536 = 1114112 个码位。

当初咱们有失常 2 个字节的码位,还有高代理位和低代理位组成的 4 个字节的码位,咱们该怎么辨别他们呢?这个时候,咱们须要引入立体的概念,把这些立体划分到不同立体,2 个字节的是 BMP 立体,4 个字节的是辅助立体。

第 0 立体称为 BMP 其范畴为 0x0000 ~ 0xffff
第 1 辅助立体称为 SMP 又称为多文种补充立体,其范畴为 0x10000 ~ 0x1ffff
第 2 辅助立体称为 SIP,又称为表意文字补充立体,其范畴为 0x20000 ~ 0x2ffff
第 3 辅助立体称为 TIP,又称为表意文字第三立体,其范畴为 0x30000 ~ 0x3ffff
第 4 至 13 辅助立体尚未应用。
第 14 辅助立体称为 SSP,又称为非凡用处补充立体,其范畴为 0xe0000 ~ 0xeffff
第 15 辅助立体,其范畴为 0xf0000 ~ 0xfffff
第 16 辅助立体,其范畴为 0x100000 ~ 0x10ffff

立体其实能够了解成把雷同长度的码位区段取了不同名字。咱们罕用的字符都在 BMP 立体,看下图,从 0x0000 ~ 0xffff 一共有 65536 个码位,其中高代理位和低代理位组合成了辅助立体。

另外,有趣味的同学能够到 这个网站 上查看 UNICODE 所有的字符。

等等!高代理位和低代理位的操作是不是很相熟?看文章认真的同学应该马上就反馈过去了,对,没错,就像当初咱们针对 ASCII 而提出的 GBK,咱们用 127 号当前的 ASCII 码组合出几万个码位给汉字用,UNICODE 也是一样,取了两段代理位组合成了辅助立体,那为什么 UNICODE 有几百万个码位,而咱们提出的 GBK 只有几万个码位,那是因为 ASCII 只有 1 个字节,UNICODE 有 2 个字节,仅此而已。

那咱们有了 UNICODE 字符集之后,所有问题就都解决了吗?其实并没有,过后的状况是,因为 UNICODE 一开始就没有打算去兼容之前任何字符集,所以 UNICODE 的推广之路并不平坦。

这时候还有仔细的同学可能会问了,你下面的图画的,UNICODE 不是把 ASCII 前一半的字符拿去了吗?而且每个码位对应的字符都一样,为啥就不兼容了?

这个同学就看的更仔细了,然而 ASCII 是 1 个字节,而 UNICODE 的 BMP 立体是 2 个字节,同样表白英文字母 A,ASCII 是 01000001,而 UNICODE 是 00000000 01000001,后面多了一堆无用的 0。所以,UNICODE 推广碰壁还有一个起因就是,存储雷同内容的英文文档,空间占用会翻倍,更别说其余文字了。

解决兼容和空间问题,聪慧的 UTF-8

这个时候,为了兼容 ASCII,UTF-8 就呈现了,他是 UNICODE 的编码方案。在实现上他不仅能够兼容 ASCII,并且因为它是可变长编码方案,对于纯英文的文档,能够使空间应用减半。

然而对于中文文档,绝对于 GBK 编码方案还是导致空间减少了,因为原先咱们 GBK 用 2 个字节组合出了几万个码位,但在 UTF-8 中,即便是 BMP 立体的汉字,那也须要 3 个字节。为什么?咱们间接看上面的例子,对于”裤裆“的”裤“字,UTF-8 是如何编码的。

首先这里有一个表格,是 UTF-8 的替换模板,模板也是有法则的,如果结尾是 0,那就占用一个字节,如果结尾是 1,那间断几个 1 就示意有几个字节。

十六进制范畴 UTF-8 模板
0x0000 ~ 0x007f 0xxxxxxx
0x0080 ~ 0x07ff 110xxxxx 10xxxxxx
0x0800 ~ 0xffff 1110xxxx 10xxxxxx 10xxxxxx
0x10000 ~ 0x10ffff 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

仔细的同学可能又要问了(你就不能换个形式引题!?),看最初一个模板,如果 x 地位全是 1 的话,明明就大于 0x10ffff,为什么范畴限度在了 0x10ffff?这就是代理位所带来的问题了,因为 UNICODE 一开始设定 2 个字节示意字符失策,起初用了代理位做补救,但这就导致码位的个数被限度在了 0x10ffff,即便 UTF-8 的设计能够表白、包容更多的字符。

// 获取”裤“的 UNICODE 码位
const code = "裤".charCodeAt(); // 35044
// 获取 35044 的十六进制示意
// 查表可得 0x88e4 位于上表的第 3 行
const hex = code.toString(16); // 0x88e4
// 获取 35044 的二进制示意
const binary = code.toString(2); // 1000 1000 1110 0100

//          10001000 11100100   |”裤“的二进制序列
//     1000   100011   100100   |”裤“的二进制排序后
// 1110xxxx 10xxxxxx 10xxxxxx   |   模板中的第三行
// --------------------------   |   从低位到高位带入模板
// 11101000 10100011 10100100   |   取得编码后的二进制序列
//       e8       a3       a4   |   二进制序列转为十六进制

// 最终失去 e8 a3 a4 的编码后果

// 通过 node Buffer 来验证
const buffer = new Buffer.from("裤", "utf-8"); // <Buffer e8 a3 a4>

如上咱们能够看到,汉字在通过 UTF-8 编码后,变成了 3 个字节,而 GBK 咱们应用 2 个字节就能够示意。所以,如果你的网站或者文档只须要在国内传输的,应用 GBK 未尝不可?能够缩小不少文档大小。

另外,咱们也写一下英文字母通过 UTF-8 编码后会获取到多少字符。

// 获取 A 的 UNICODE 码位
const code = "A".charCodeAt(); // 65
// 获取 65 的十六进制示意,0x41 位于上表的第 1 行
const hex = code.toString(16); // 0x41
// 获取 65 的二进制示意
const binary = code.toString(2); // 1000001

//  1000001   |   A 的二进制序列
// 0xxxxxxx   |   模板中的第一行
// --------   |   从低位到高位带入模板
// 01000001   |   取得二进制序列
//       41   |   二进制序列转为十六进制

// 最终失去 41 的编码后果

// 通过 node Buffer 来验证
const buffer = new Buffer.from("A", "utf-8"); // <Buffer 41>

可见,对于英文字母来说,00000000 01000001 变成了 01000001,所占空间缩小了一半。

UTF-16 编码方案

说完了 UTF-8,咱们来说说 UTF-16 和 UCS-2,前面还有 UTF-32 和 UCS-4。另外,下面咱们不是提到了 ISO 组织么?UCS 就是该组织提出的编码方案,UCS-2 能够说就是 UTF-16 的前身。

尽管看着 UTF-16 和 UTF-8 是 double 的样子,但其实并不。

首先,对于 BMP 面的字符,UTF-16 就间接用 2 个字节来示意,包含英文字母。

另外,咱们方才不是提到代理位和代理位组合而成的辅助立体么?忘了的同学能够翻上去看看,其实代理位就是专门用于 UTF-16 的,对于辅助立体的字符,UTF-16 就用 4 个字节来示意,也就是高代理位和低代理位组合示意。

看了这么久文章,同学们该饿了吧,咱们说点好吃的,陕西的特产,biangbiang 面。

这个 biang 字,如下图。

biang 在 UNICODE 的码位为 0x30ede,你能够关上 这个网站,搜寻 0x30ede 来查看 biang 字收录在 UNICODE 字符集的地位。尽管这个字在浏览器中还不可能打进去,然而咱们能够用这个字做引子,来说说 UTF-16 的编码该如何实现。

biangCharCodeHex = "0x30ede";
// 从 200414 这个码位就可以看进去,biang 字不在 BMP 立体
// 因为 BMP 立体只有 65536 个码位
parseInt("0x30ede"); // 200414

// 接下来演示 UTF-16 编码过程

// 先获取 200414 的二进制示意
(200414).toString(2); // 11 0000 1110 1101 1110

//            11 0000 1110 1101 1110   |   码位对应的二进制
//             1 0000 0000 0000 0000   |   减去 0x10000
//            10 0000 1110 1101 1110   |   失去的二进制
//          0010 0000 1110 1101 1110   |   把失去的二进制前补 0,补充到 20 位
//       0010000011       1011011110   |   整顿一下,10 位一隔,不便浏览
// 1101100000000000 1101110000000000   |   右边是 0xd800,左边是 0xdc00
// ---------------------------------   |   还记得高代理位和低代理位的区间么?//                                     |   高代理位从 0xd800 ~ 0xdbff,咱们取 0xd800
//                                     |   低代理位从 0xdc00 ~ 0xdfff,咱们取 0xdc00
// 1101100010000011 1101111011011110   |   间接把 10 位别离取代代理位前面的 0,从前面开始取代
//             d883             dede   |   把上述二进制转为 16 进制,最终获取到 UTF-16 编码

// 通过 node Buffer 来验证
// 因为 node 只反对 UTF-16 小端序
// http://nodejs.cn/api/buffer.html#buffer_buffers_and_character_encodings
// 所以示意为 dede 83d8,留神,这是从右往左读的
// 另外 "\u{30ede}" 这是个 ES6 用来示意辅助立体的字符的办法
const buffer = new Buffer.from("\u{30ede}", "utf16le"); // <Buffer 83 d8 de de>

以上,咱们就实现了 UTF-16 编码,另外 UNICODE@3.0 也给出了辅助立体字符的转换公式

High = Math.floor((charCode - 0x10000) / 0x400) + 0xd800;
Low = ((charCode - 0x10000) % 0x400) + 0xdc00;

// 咱们把方才 biang 的码位代入试试
High = (Math.floor((0x30ede - 0x10000) / 0x400) + 0xd800).toString(16); // d883
Low = (((0x30ede - 0x10000) % 0x400) + 0xdc00).toString(16); // dede

在下面的例子里咱们也看到了,UTF-16 还存在大端序和小端序,也就是字节序(BOM)的问题,其实就是咱们得通知程序,这段编码该从左开始读还是从右开始读。

举个例子,“奠”的编码后果是 5960,“恙”的编码后果是 6059,如果没有表明读取的方向,显示的后果就会有问题。

所以,UTF-16 还有两个分支,UTF-16BE(大端序)和 UTF-16LE(小端序)。并且对于用 UTF-16 编码的文件,会在文件头部减少一个代表字节序的标识,UTF-16BE 放的是 0xfeff,UTF-16LE 放的是 0xfffe,所以你会发现用 UTF-16 保留的文件,所占用空间会多 2 个字节。

咱们晓得了 UTF-16 能够用高下代理位组合成新的码位,UCS-2 和 UTF-16 的区别就在此,UCS-2 也是用 2 个字节示意 BMP 立体的字符,然而它不能示意辅助立体,并且,因为 ISO 要保障和 UNICODE 保持一致,所以 UCS-2 的 0xd800 ~ 0xdbff0xdc00 ~ 0xdfff 码位的字符是空的,你能够把 UCS-2 了解成是 UTF-16 的子集。

UTF-32 编码方案

接下来咱们更快的来过一下 UTF-32 和 UCS-4,他俩都是间接对每个字符都应用 4 个字节,并且刚开始 UCS-4 提出来时,4 个字节,一共 32 个位,然而在计算机中咱们个别会把最高位当做符号位(这里是否如此,存疑),那 UCS-2 就有 2^31 = 2147483648 个码位,然而因为 UCS-4 要合乎 UNICODE 的规范,码位只能用到 0x10ffff,所以 UTF-32 就被提出来了,他只用来示意 0x000000 ~ 0x10ffff 的码位。

到这里,有没有发现代理位的坑?明明 UTF-8 和 UTF-32 能够示意更多的字符,然而因为用了代理位,UNICODE 的字符个数下限就被制裁到了 0x10ffff,尽管咱们当初还有大量的码位没有被应用,UNICODE 被称为万国码,但如果当前如果有了宇宙码呢,那是不是要重启一套编码,或者被迫应用脑电波传输?(狗头

字符集和编码方案的阶段总结

至此,字符集和编码方案就先告一段落,咱们来做一个总结

  1. 刚开始由美国提出了 ASCII 码表,并且这也是晚期计算机的编码方案,规定 8 个位为 1 个字节,一共有 2^8 = 256 个码位。
  2. 起初计算机传到了其余国家,所以又出了 ASCII 扩大表,把 127 位之后的码位占满了。
  3. 再起初,计算机传到了中国,咱们通过特定的规定,将两个 ASCII 码组合,失去了中国汉字编码,过后风行的编码方案有 GB2312、GBK、GB18030、Big5 等。
  4. 最初,国际标准化组织终止了各国发明各自编码方案的行为,打造了 UNICODE,试图将全世界的字符都收纳其中。
  5. UNICODE 在提出之初间接规定应用 2 个字节表白所有字符,并没有思考兼容任何编码方案,包含 ASCII,所以推广非常艰巨,直到 UTF-8 编码方案的问世。
  6. UNICODE 的码位一共有 0x10ffff 个,这些码位被分为 17 个立体,咱们罕用的是 BMP 立体。
  7. UNICODE 的 BMP 立体前 127 个码位内容间接照搬 ASCII,并将 0xd800 ~ 0xdbff0xdc00 ~ 0xdfff 规定为高下代理位,他俩组合生成辅助立体所需码位。
  8. UTF-8 是可变长的编码方案,编码后果是 1 ~ 4 个字节,既保证了英文字符的编码后果和 ASCII 初版字符集保持一致,又能够通过特定规定,编码所有的 UNICODE 码位。
  9. UTF-16 也是可变长编码方案,编码后果是 2 或者 4 个字节,BMP 立体的字符用 2 个字节示意,辅助立体用高下代理位组合计算后的 4 个字节示意。并且因为没有 UTF-8 的非凡规定,所以存在大小端字节序的问题。
  10. UCS-2 是 UTF-16 的前身,编码后果是 2 个字节,不反对辅助立体的字符示意。编码空间为 0x0000 ~ 0xffff
  11. UTF-32 是定长编码方案,编码后果是固定的 4 个字节,和 UTF-16 一样,也存在字节序的问题。
  12. UCS-4 是 UTF-32 的前身,编码后果是固定的 4 个字节。编码空间为 0x00000000 ~ 0x7fffffff
  13. UTF 系列编码方案的编码空间都为 0x000000 ~ 0x10ffff

验证阶段,理论看到才会记得牢

后面说了这么多货色,可能有的同学转瞬就忘了,那咱们来理论演示一下,不同编码对于文件大小的影响。

首先是英文文档,创立 3 个文件,内容都是 abc,而后比照三个文件的大小。

从上图咱们能够看到:

GBK 和 UTF-8 对于三个英文字母,都是 3 个字节的空间占用,因为 GBK 对于 127 号以前的英文字母编码放弃 1 个字节,UTF-8 对于英文字母的编码规定也是失去 1 个字节。

还有 UTF-16,占用了 8 个字节,咱们下面提到 UTF-16 为了解决字节序问题,会在文件首部减少 2 个字节示意读取程序,咱们去掉这 2 个字节,得出在 UTF-16 中,每个英文字母占位 2 个字节。

再是中文文档,也是 3 个文件,内容是 我爱你,比照三个文件的大小。

从上图咱们能够看到:

GBK 占用了 6 个字节,因为 GBK 编码方案把 2 个 ASCII 组合成了一个新的码位给中文应用,每个汉字就占 2 个字节。

UTF-16 占用了 8 个字节,先去掉文件首部示意字节序的 2 个字节,再因为 UTF-16 对于 BMP 立体的字符,对立用 2 个字节显示,汉字在 UTF-16 中就是占用 2 个字节。

UTF-8 占用了 9 个字节,咱们下面提到的,对于 BMP 立体的汉字,UTF-8 通过编码后,每个汉字占用 3 个字节。

参考 & 倡议

作为开发,平时开发的工作忙碌就没啥工夫课外阅读,然而偶然也要把本人从代码中拔出来。尽管写代码实现性能很有成就感,然而如果有机会踏入本人未曾涉足的世界,也是人生一大乐事。

参考文章

网页编码就是那点事
ASCII 码表
Code Charts
Unicode 和 UTF-8 有什么区别?
字符编码笔记:ASCII,Unicode 和 UTF-8
细说:Unicode, UTF-8, UTF-16, UTF-32, UCS-2, UCS-4

倡议浏览

ANSI 是什么编码?
汉字编码:GB2312, GBK, GB18030, Big5
细说:Unicode, UTF-8, UTF-16, UTF-32, UCS-2, UCS-4
UTF-8, UTF-16, UTF-32 & BOM
JavaScript’s internal character encoding: UCS-2 or UTF-16?

系列文章内容预报

当然 UNICODE 并不是只有这么简略,一个码位一个字符,比方印度语 नमस्ते,这是 hello 的意思,这是怎么实现的也大有文章。

另外 kùdāng 这个字符串在浏览器环境,输入的 length 为什么是 8 而不是 6,这就波及到 javascript 外部的编码格局问题。

这个钻研上来内容切实太多了,我得好好整顿一下,最初再联合本人的了解写成文章。

页脚

代码即人生,我甘之如饴。

技术一直在变
头脑始终在线
前端路漫漫
咱们下期见

by — 裤裆三重奏

我在这里 gayhub@jsjzh 欢送大家来找我玩儿。如果你看完文章之后有所播种,想要夸夸我,一个 Star ✨就是对我最好的激励。

欢送小伙伴们间接加我 vx,拉你进群一起搞事件,记得备注一下你是从哪里看到文章的。

ps: 如果图片生效,能够加我 wechat: kimimi_king

<div align=”center”>
<image src=”https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/53fb3e16b1f64ebbb8aee73734371257~tplv-k3u1fbpfcp-watermark.image” />
</div>

退出移动版