需求是讀取一個csv文件,然后解析成對應的數據結構。csv必須包含指定的某些列,通過列名header來進行校驗。
解析配置文件的方法。
1 public List<QuestionData> buildConfigData(final MultipartFile file) { 2 3 CsvReader csvReader = null; 4 List<QuestionData> questionDataList; 5 try (DataInputStream inputStream = new DataInputStream(file.getInputStream())) { 6 csvReader = new CsvReader(new InputStreamReader(inputStream, Charset.forName("UTF-8"))); 7 8 if (!csvReader.readHeaders()) { 9 return Lists.newLinkedList(); 10 } 11 12 final String[] headers = csvReader.getHeaders(); 13 getAndCheckHeader(headers); 14 questionDataList = getQuestionData(csvReader, headers); 15 16 } catch (final IOException e) { 17 log.error("解析配置文件錯誤", e); 18 throw new FatalException("解析配置文件錯誤"); 19 } finally { 20 if (csvReader != null) { 21 csvReader.close(); 22 } 23 } 24 return questionDataList; 25 }
其中,檢查header的方法:
private static final Set<String> NEEDED_COLUMNS = ImmutableSet .of(QuestionDataType.ORDER.name(), QuestionDataType.DESC.name(), QuestionDataType.OPTION_A.name(), QuestionDataType.OPTION_B.name(), QuestionDataType.OPTION_C.name(), QuestionDataType.ANSWER.name()); private void getAndCheckHeader(final String[] headers) { //某些必要的列不存在 HashSet<String> sets = Sets.newHashSet(headers); if (!sets.containsAll(NEEDED_COLUMNS)) { throw new FatalException("缺少必要的列信息"); } }
實際出現的問題是,上傳文件的時候總是出現缺少必要的列信息這個異常。debug發現,containsAll這個方法一直返回false,但是看NEEDED_COLUMNS里面的字符串,在header里面都存在,例如ORDER字符串:
從這里看,headers里面有ORDER字符串,但是NEEDED_COLUMNS.contains(headers[0])返回的結果就是false。
debug時使用evaluate,將headers[0]的value copy一下,粘貼到輸入框里,就發現了問題:
可以看的出來,headers[0]的實際值是"\uFEFFORDER",而非"ORDER",前面多了一個"\uFEFF"。
經查,"\uFEFF"是BOM頭,windows下保存文件時經常會插入在字符串最前面,debug時直接看值是看不出來有這個BOM頭的。
解決方案,使用apache的BOMInputStream,可以過濾掉BOM頭:
public List<QuestionData> buildConfigData(final MultipartFile file) { CsvReader csvReader = null; List<QuestionData> questionDataList; //過濾BOM頭 try (BOMInputStream inputStream = new BOMInputStream(file.getInputStream())) { csvReader = new CsvReader(new InputStreamReader(inputStream, Charset.forName("UTF-8"))); if (!csvReader.readHeaders()) { return Lists.newLinkedList(); } final String[] headers = csvReader.getHeaders(); getAndCheckHeader(headers); questionDataList = getQuestionData(csvReader, headers); } catch (final IOException e) { log.error("解析配置文件錯誤", e); throw new FatalException("解析配置文件錯誤"); } finally { if (csvReader != null) { csvReader.close(); } } return questionDataList; }
使用BOMInputStream,將原有的InputSteam包一層即可。
參考文章:Java處理文件BOM頭的方式推薦