java opencsv解析csv文件


記一次使用opencsv解析csv文件時碰到的坑

最近在開發過程中需要解析csv文件,公司用的解析工具是opencsv,在根據opencsv的官方文檔去解析時發現csv文件中含有繁體字,使用其自帶的CsvToBean來轉換會出現異常com.opencsv.exceptions.CsvRequiredFieldEmptyException: Number of data fields does not match number of headers.於是我這里想到的方法是使用CsvReader來讀取文件,然后通過反射來注入到bean中,這里做個記錄希望對大家有幫助

一、引入依賴包

<dependency>
    <groupId>com.opencsv</groupId>
    <artifactId>opencsv</artifactId>
    <version>4.4</version>
</dependency>

二、具體代碼

1.自定義注解,基礎一點的就是只需要定義數據列標題名title、格式轉換convert,我這里是由於業務需要所以稍微復雜些

import java.lang.annotation.*;

/**
 * <p>
 * 解析csv文件注解
 * </p>
 *
 * @Author zlc0w01
 * @Date 2020/4/20 10:15
 * @Version 1.0
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CsvReadColmn {
    /**
     * 該列數據的標題名
     */
    String title();

    /**
     * 是否需要加密
     */
    boolean encrypt() default false;

    /**
     * 讀csv文件字段綁定
     * 綁定格式轉換類,字符串轉Object
     * @return
     */
    Class<? extends AbstractConvertCsvBase> convert() default AbstractConvertCsvBase.Converter.class;

    /**
     * 轉換依賴字段,如有某個字段轉換需要依賴其他字段,
     * 可設置為依賴字段的title
     * @return
     */
    String convertRelyColumn() default "";

}

2.接下來就是定義用於轉換的基類了,這里面可以自己定義,我這里定義的意思是轉換所有字段,去掉前面的單引號“'”,其他需要定義的轉換規則可以繼承這個類,然后重寫convert方法就行了

public abstract class AbstractConvertCsvBase {
    private static final String SPLIT = "'";

    /**
     * 轉換
     * @param params 參數中必須有key為"value"
     * @return
     */
    public Object startConvert(Map<String,String> params){
        String value = params.get("value");
        if (StringUtils.isNotBlank(value) && SPLIT.equals(value.substring(0,1))){
            value = value.substring(1);
        }
        if (StringUtils.isBlank(value)){
            return null;
        }
        params.put("value",value);
        return convert(params);
    }

    /**
     * 轉換方法
     * @param params
     * @return
     */
    public abstract Object convert(Map<String,String> params);

    public static class Converter extends AbstractConvertCsvBase{
        public static Converter newInstance() {
            return new Converter();
        }
        @Override
        public Object convert(Map<String,String> params) {
            return params.get("value");
        }
    }
}

3.定義需要轉換的bean

public class GbInsurancePolicy implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * id
     */
    @CsvReadColmn(title = "內部號碼")
    private String id;

    @CsvReadColmn(title = "出生日期",convert = CsvConvertStringToSimpleDate.class)
    private Date birthday;

}

//上面說到定義轉換規則,這里拿出生日期舉例
public class CsvConvertStringToSimpleDate extends AbstractConvertCsvBase {

@SneakyThrows
@Override
public Object convert(Map<String, String> params) {
String value = params.get("value");
SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd");
return sf.parse(value);
}
}

4.定義反射來解析csv文件

public List<T> readSpecialCsv(String filePath) throws Exception{
        List<T> list = new ArrayList<>();
        FileInputStream fr = new FileInputStream(filePath);
        UnicodeInputStream unicodeInputStream = new UnicodeInputStream(fr, true);
        String enc = unicodeInputStream.getEncodingFromStream();
        InputStreamReader is = new InputStreamReader(unicodeInputStream, enc);
        CSVReader reader = new CSVReader(is);
        String [] nextLine;
        String[] header = reader.readNext();
        while ((nextLine = reader.readNext()) != null) {
            if (nextLine.length < header.length){
                continue;
            }
            T t = getTClass().newInstance();
            Field[] fields = t.getClass().getDeclaredFields();
            for (Field field : fields) {
                field.setAccessible(true);
                Field fieldName = t.getClass().getDeclaredField(field.getName());
                CsvReadColmn csvReadColmn = fieldName.getAnnotation(CsvReadColmn.class);
                if (null != csvReadColmn){
                    int columnPosition = Arrays.asList(header).indexOf(csvReadColmn.title());
                    String value = nextLine[columnPosition];
                    AbstractConvertCsvBase convert = csvReadColmn.convert().newInstance();
                    Map<String,String> params = new HashMap<>();
                    params.put("value",value);
                    //是否有需要依賴某個字段來轉換的
                    if (StringUtils.isNotBlank(csvReadColmn.convertRelyColumn())){
                        int relyColumnPosition = Arrays.asList(header).indexOf(csvReadColmn.convertRelyColumn());
                        String relyColumn = nextLine[relyColumnPosition];
                        params.put("relyColumn",relyColumn);
                    }
                    Object obj = convert.startConvert(params);
                    String methodName = "set" + field.getName().substring(0, 1).toUpperCase() + field.getName().substring(1);
                    Method m = t.getClass().getDeclaredMethod(methodName, fieldName.getType());
                    m.invoke(t, obj);
                }
            }
            list.add(t);
        }
        reader.close();
        return list;
    }

以上就是整個流程,希望對大家有幫助


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM