这个问题困扰我两天,主要是因为在网上看了一个有很大问题的解决办法:微软的Excel打开的csv文件不支持uft-8编码,而是支持UTF-16LE编码,所以我们需要输出BOM头,然后将UTF-8转换成UTF-16LE

//输出BOM
echo(chr(255).chr(254));
echo(mb_convert_encoding($content,"UTF-16LE","UTF-8"));

这样直接转换为UTF-16LE编码,本身确实可以将导出的CSV文件中的乱码变成日语、韩语。但是,却不能正确的使用分隔符,所有的字段全部都挤在一起了。于是我花了整整一天的时间研究怎样让分隔符生效。很不幸,试了很多办法还是没用。

其实,解决办法没这么复杂。

要解决PHP生成CSV文件的乱码问题,只需要在文件的开始输出BOM头,告诉windows CSV文件的编码方式,从而让Excel打开CSV时采用正确的编码。

    什么是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。
UTF-8不需要BOM来表明字节顺序,但可以用BOM来表明编码方式。字符”ZERO WIDTH NO-BREAK SPACE”的UTF-8编码是
EF BB BF。所以如果接收者收到以EF BB BF开头的字节流,就知道这是UTF-8编码了。
Windows就是使用BOM来标记文本文件的编码方式的。

在所有内容输出之前

print(chr(0xEF).chr(0xBB).chr(0xBF));

注释:

几个UTF编码的BOM头

define ('UTF32_BIG_ENDIAN_BOM'   ,   chr(0x00) . chr(0x00) . chr(0xFE) . chr(0xFF));
define ('UTF32_LITTLE_ENDIAN_BOM',   chr(0xFF) . chr(0xFE) . chr(0x00) . chr(0x00));
define ('UTF16_BIG_ENDIAN_BOM' ,   chr(0xFE) . chr(0xFF));
define ('UTF16_LITTLE_ENDIAN_BOM',   chr(0xFF) . chr(0xFE));
define ('UTF8_BOM'   ,   chr(0xEF) . chr(0xBB) . chr(0xBF));

完整的代码

 header('Expires: 0');
header('Cache-control: private');
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Content-Description: File Transfer');
header('Content-Encoding: UTF-8');
header('Content-type: text/csv; charset=UTF-8');
header('Content-Disposition: attachment; filename=Customers_Export.csv');

echo "\xEF\xBB\xBF"; // UTF-8 BOM
/*
* OR print(chr(0xEF).chr(0xBB).chr(0xBF));
*/