csv格式轉Excel開啟中文亂碼的解決
【亂碼解決】
接到一個需求,需要將PC管理端頁面的Excel檔案下載改為CSV的格式。據說CSV也是一種比較通用的做法,因為Excel各個版本對於單Sheet的行數都會有限制,大資料量的情況下直接用CSV會方便很多,另外如果使用Excel組裝資料,那麼介面實現的記憶體也會佔用更多(更多的物件)。
直接上程式碼:
package com.jf.mzzc.manage.util.exportCSVUtil; import com.jf.mzzc.common.util.RFC5987StringUtils; import com.opencsv.CSVWriter; import org.springframework.web.servlet.view.AbstractView;import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.OutputStreamWriter; import java.nio.charset.StandardCharsets; import java.util.List; import java.util.Map; public class CsvView extends AbstractView { public static final String TEMPLATE_KEY = "templateName";public static final String OUTPUT_NAME_KEY = "output"; public static final Integer LIMIT_RAW = 15000; /** * Subclasses must implement this method to actually render the view. * <p>The first step will be preparing the request: In the JSP case, * this would mean setting model objects as request attributes. * The second step will be the actual rendering of the view, * for example including the JSP via a RequestDispatcher. * *@param model combined output Map (never {@code null}), * with dynamic values taking precedence over static attributes * @param request current HTTP request * @param response current HTTP response * @throws Exception if rendering failed */ @Override protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception { List<String[]> entryList = (List<String[]>) model.get(TEMPLATE_KEY); String fileName = (String) model.get(OUTPUT_NAME_KEY); String rfc5987FileName = RFC5987StringUtils.rfc5987_encode(fileName); response.setContentType("application/octet-stream"); response.setHeader("Content-Disposition", "attachment;filename=\"" + rfc5987FileName + "\";filename*=utf-8''" + rfc5987FileName); OutputStreamWriter outputStreamWriter = new OutputStreamWriter(response.getOutputStream(), StandardCharsets.UTF_8); CSVWriter writer = new CSVWriter(outputStreamWriter); for (int i = 0; i < entryList.size(); i++) { String[] strings = entryList.get(i); writer.writeNext(strings); } writer.close(); } }
在Controller層:
@Controller @RequestMapping(value = "/manage/order") @Api(value = "API.VALUE", tags = "訂單") public class ChargeOrderController { @DubboReference private ChargeOrderFacade chargeOrderFacade; /** * 帶條件的csv下載 * * @param model * @return */ @ResponseBody @RequestMapping(value = "/download", method = RequestMethod.GET) public View downloadCsvView(HttpServletRequest request, Model model, ChargeOrderQueryVo queryVo) { Search search = SearchUtil.genSearch(request); List<String[]> dataList = chargeOrderFacade.extractCsvViewInfo(search, queryVo); CsvView view = new CsvView(); String time = new SimpleDateFormat("yyyy-MM-dd").format(new Date()); model.addAttribute(CsvView.TEMPLATE_KEY, dataList); model.addAttribute(CsvView.OUTPUT_NAME_KEY, String.format("充電訂單-%s.csv", time)); return view; } }
測試也能夠正常下載CSV檔案的,但是用Excel開啟這個檔案發現中文亂碼,明明設定了UTF-8的怎麼會亂碼呢?再次嘗試用記事本開啟檔案發現,中文是可以正常顯示的。此時對文字另存為UTF8格式,再次用Excel開啟,此時中文能夠正常顯示了。
網上搜索一番找到了原因。csv檔案前必須要加個BOM頭,Excel才能正確開啟檔案。於是做如下修改:
... List<String[]> entryList = (List<String[]>) model.get(TEMPLATE_KEY); String fileName = (String) model.get(OUTPUT_NAME_KEY); String rfc5987FileName = RFC5987StringUtils.rfc5987_encode(fileName); response.setContentType("application/octet-stream"); response.setHeader("Content-Disposition", "attachment;filename=\"" + rfc5987FileName + "\";filename*=utf-8''" + rfc5987FileName); OutputStreamWriter outputStreamWriter = new OutputStreamWriter(response.getOutputStream(), StandardCharsets.UTF_8); byte[] bom = {(byte) 0xEF, (byte) 0xBB, (byte) 0xBF}; outputStreamWriter.write(new String(bom)); CSVWriter writer = new CSVWriter(outputStreamWriter); for (int i = 0; i < entryList.size(); i++) { String[] strings = entryList.get(i); writer.writeNext(strings); } writer.close(); ...
此時下載的csv檔案使用excel能夠正常開啟,且中文顯示正常。
————————————————————————————————————————————————
【BOM是什麼】
BOM的全稱是Byte Order Mark,位元組順序標記。它是一種特殊的Unicode字元,作為一個魔數一樣的存在,可以告訴正在讀取這個文字流的程式三件事情:
1.16位或者32位編碼的情況下,文字流的位元組順序;
2.一個比較高級別的確認資訊,表明這個文字流的格式是Unicode;
3.使用了Unicode字元編碼。
但是這個BOM資訊,用不用都是可選的,有些軟體支援,有些軟體不支援。在Windows環境下,很多軟體都是支援,甚至要求這個BOM資訊的;但是UNIX環境下,如果攜帶了BOM資訊,很多時候又會出問題。所以,這裡要注意的是,到底使用這個流的軟體是否支援BOM,到了Windows環境下,如果出現了亂碼,要能想到這個問題點;而在Linux環境下,或者是跨平臺的軟體,如果出現了問題,又要排除這個點。
有些軟體對於BOM的設定是可選的,比如Intellij Idea中對於配置檔案的設定,就可以選擇;Notepad++中也是可以選的。是否選擇,完全看我們的需求,但是使用不當也會帶來上述問題。
另外,要注意的是,Windows處理文字時,會自動加上這個BOM資訊。比如,新建一個文字,另存為的時候,選擇UTF-8格式,儲存檔案,此時可以看到文字的大小變成了3位元組,這個3位元組就是被加上了BOM資訊。