共计 10027 个字符,预计需要花费 26 分钟才能阅读完成。
前言
这篇文章是钻研上传文件的时候扩大进去的知识点,因为上传文件的时候会波及到文件的编码等内容,作为前端平时接触这些货色又比拟少,我又是个不彻底搞清楚问题就不罢休的人,所以往往会因为一个小问题牵扯进去一堆问题,接着,疑难又带来新的疑难(禁止套娃!)。
写这篇文章花了很长时间,如果你看完之后有所播种,一个 Star ✨就是对我最好的激励。
ps: 文章不会纠结一些不太重要的信息,比方某协定是谁提出的,在什么年份提出的,这些基本上看过一次就忘了,也不重要,我会提取重要的信息来分享给大家。
前置常识
在开始之前,先整顿一下咱们所须要的前置常识,对于这些咱们只须要有个简略的印象即可。
计算机罕用的单位换算
二进制位、位、比特、bit、b,这些都是代表计算机存储的最小单位「位」,也就是二进制的 0
或 1
。
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 的到来
家喻户晓,在计算机中,所有的数据存储和运算时都要应用二进制示意,因为计算机用高电平和低电平示意 1
和 0
。而后就有这么一群人,他们决定 用 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 ~ 0xdbff
和 0xdc00 ~ 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 被称为万国码,但如果当前如果有了宇宙码呢,那是不是要重启一套编码,或者被迫应用脑电波传输?(狗头
字符集和编码方案的阶段总结
至此,字符集和编码方案就先告一段落,咱们来做一个总结
- 刚开始由美国提出了 ASCII 码表,并且这也是晚期计算机的编码方案,规定 8 个位为 1 个字节,一共有
2^8 = 256
个码位。 - 起初计算机传到了其余国家,所以又出了 ASCII 扩大表,把 127 位之后的码位占满了。
- 再起初,计算机传到了中国,咱们通过特定的规定,将两个 ASCII 码组合,失去了中国汉字编码,过后风行的编码方案有 GB2312、GBK、GB18030、Big5 等。
- 最初,国际标准化组织终止了各国发明各自编码方案的行为,打造了 UNICODE,试图将全世界的字符都收纳其中。
- UNICODE 在提出之初间接规定应用 2 个字节表白所有字符,并没有思考兼容任何编码方案,包含 ASCII,所以推广非常艰巨,直到 UTF-8 编码方案的问世。
- UNICODE 的码位一共有
0x10ffff
个,这些码位被分为 17 个立体,咱们罕用的是 BMP 立体。 - UNICODE 的 BMP 立体前 127 个码位内容间接照搬 ASCII,并将
0xd800 ~ 0xdbff
和0xdc00 ~ 0xdfff
规定为高下代理位,他俩组合生成辅助立体所需码位。 - UTF-8 是可变长的编码方案,编码后果是 1 ~ 4 个字节,既保证了英文字符的编码后果和 ASCII 初版字符集保持一致,又能够通过特定规定,编码所有的 UNICODE 码位。
- UTF-16 也是可变长编码方案,编码后果是 2 或者 4 个字节,BMP 立体的字符用 2 个字节示意,辅助立体用高下代理位组合计算后的 4 个字节示意。并且因为没有 UTF-8 的非凡规定,所以存在大小端字节序的问题。
- UCS-2 是 UTF-16 的前身,编码后果是 2 个字节,不反对辅助立体的字符示意。编码空间为
0x0000 ~ 0xffff
。 - UTF-32 是定长编码方案,编码后果是固定的 4 个字节,和 UTF-16 一样,也存在字节序的问题。
- UCS-4 是 UTF-32 的前身,编码后果是固定的 4 个字节。编码空间为
0x00000000 ~ 0x7fffffff
。 - 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>