WangMao's Blog
原 Secret Blog,坐标深圳,码农

Windows Excel 打开 CSV 文件乱码的问题

运营同学有一个需求提给了我,说是每周一和周五需要到系统手动导出所有成交报告,而系统是根据城市进行分库的,导完一个城市的成交报告需要切换系统的登录城市。这个步骤异常繁琐,希望我能给个解决方案来解放他的双手。

在项目拉取了一个 hotfix 分支就开搞,通过命令触发接口导出数据生成 CSV 文件并通过附件发送给我。

测试完成,数据完整。发给运营同学准备测试数据,然后气氛有点奇怪。

运营同学:乱码。

我:试试导入?

运营同学:还是乱码。

乱码?UTF-8 还乱码?这 Office 用得啥编码格式。

搜索一通,有说是 Excel 的 Bug,可以通过加 BOM 头搞定。

BOM 头是啥呢?

在 UCS 编码中有一个叫做 “Zero Width No-Break Space“ ,中文译名作 “零宽无间断间隔” 的字符,它的编码是 FEFF。而 FFFE 在 UCS 中是不存在的字符,所以不应该出现在实际传输中。UCS 规范建议我们在传输字节流前,先传输字符 “Zero Width No-Break Space”。这样如果接收者收到 FEFF,就表明这个字节流是 Big-Endian 的;如果收到 FFFE,就表明这个字节流是 Little- Endian 的。因此字符 “Zero Width No-Break Space” (“零宽无间断间隔”)又被称作 BOM。

类似 WINDOWS 自带的记事本等软件,在保存一个以 UTF-8 编码的文件时,会在文件开始的地方插入三个不可见的字符(0xEF 0xBB 0xBF,即 BOM)。它是一串隐藏的字符,用于让记事本等编辑器识别这个文件是否以 UTF-8 编码。对于一般的文件,这样并不会产生什么麻烦。但对于 PHP 来说,BOM 是个大麻烦。

摘自 百度百科 BOM(Byte Order Mark)

再补充一份不同编码的字节顺序标记的表。

编码表示 (十六进制)表示 (十进制)
UTF-8EF BB BF239 187 191
UTF-16(大端序)FE FF254 255
UTF-16(小端序)FF FE255 254
UTF-32(大端序)00 00 FE FF0 0 254 255
UTF-32(小端序)FF FE 00 00255 254 0 0
UTF-72B 2F 76 和以下的一个字节:[38 / 39 / 2B / 2F]43 47 118 和以下的一个字节:[56 / 57 / 43 / 47]
en:UTF-1F7 64 4C247 100 76
en:UTF-EBCDICDD 73 66 73221 115 102 115
en:Standard Compression Scheme for Unicode0E FE FF14 254 255
en:BOCU-1FB EE 28及可能跟随着FF251 238 40及可能跟随着255
GB-1803084 31 95 33132 49 149 51

也就是说,只需要在 CSV 头部加一个 BOM 头就可以解决乱码。

1
2
3
4
5
<?php
// 设置 UTF-8 BOM 头
$bom = chr(239).chr(187).chr(191);
$filecontents = $bom . $filecontents;
...

嗯,记一笔,运营同学欠我一顿饭。