java-注解实现导入导出CSV文件

什么是CSV文件

csv是最通用的一种文件格式,它可以非常容易地被导入各种PC表格及数据库中。 此文件,一行即为数据表的一行。生成数据表字段用半角逗号隔开。csv文件用记事本和excel都能打开。

CSV与Excel文件的区别

1.CSV是纯文本文件,excel不是纯文本,excel包含很多格式信息在里面。
2.CSV文件的体积会更小,创建分发读取更加方便,适合存放结构化信息,比如记录的导出,流量统计等等。
3.CSV文件在windows平台默认的打开方式是excel,但是它的本质是一个文本文件。

工具选取

FASTCSV
工具优势
java-注解实现导入导出CSV文件_第1张图片

依赖引入


   de.siegmar
    fastcsv
    2.0.0

注解实现

@Target({ ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CsvField {
    /**
     * 字段的标题
     * @return
     */
    String value() default "";

    /**
     * 是否忽略此字段
     * @return
     */
    boolean ignore() default false;
    /**
     * 转换器,按需生成结果
     * @return
     */
    Class using() default CsvConvertHandler.None.class;
}

注解转换器

public abstract class CsvConvertHandler implements CsvConvertVisitable {
    @Override
    public String convert(Object value) {
        return this.get((T) value);
    }

    protected abstract String get(T value);

    public abstract static class None extends CsvConvertHandler {
        public None() {
        }
    }
}

注解使用

@EqualsAndHashCode()
@Data
public class TestBean {

    @CsvField(value = "字段1")
    private String name1;

    @CsvField(value = "字段2")
    private String name2;

    @CsvField(value = "字段3")
    private String name3;

    @CsvField(value = "字段4")
    private String name4;
    }

使用注解导入导出文件(工具类实现)

注解中删除了转换类

public class FastCsvHelper {

    private final static String charset = "GBK";

    private static class FieldData {
        Field field;
        CsvConvertVisitable converter;

        public FieldData(Field field, CsvConvertVisitable converter) {
            this.field = field;
            this.converter = converter;
        }
    }

    /**
     * 读取CSV文件
     * @param inputStream
     * @param clazz
     * @param 
     * @return
     * @throws Exception
     */
    public static  List readCsv(InputStream inputStream, Class clazz) throws Exception {
        if (inputStream == null){
            return new ArrayList<>();
        }
        if (clazz == null){
            throw new Exception("解析模板为空");
        }
        // 筛选出拥有注解的字段
        LinkedHashMap stringFieldDataLinkedHashMap = initCsvFields(clazz);
        if (stringFieldDataLinkedHashMap.size() < 1) {
            return new ArrayList<>();
        }
        List items = readData(inputStream, stringFieldDataLinkedHashMap, clazz);
        return items;
    }

    /**
     * 读取数据
     * @param inputStream
     * @param stringFieldDataLinkedHashMap
     * @param clazz
     * @param 
     * @return
     * @throws Exception
     */
    private static  List readData(InputStream inputStream, LinkedHashMap stringFieldDataLinkedHashMap, Class clazz)
            throws Exception {
        List items = new ArrayList<>();
        try {
            final InputStreamReader inputStreamReader = new InputStreamReader(inputStream, charset);

            List titles = null;
            List fieldDataList = null;
            try (CsvReader csvReader = CsvReader.builder().skipEmptyRows(true).build(inputStreamReader)) {
                for (final Iterator iterator = csvReader.iterator(); iterator.hasNext();){
                    final CsvRow csvRow = iterator.next();
                    //读取表头
                    if (csvRow.getOriginalLineNumber() == 1) {
                        titles = csvRow.getFields();
                        if (titles != null){
                            // 获取列对应赋值方法
                            fieldDataList = getFieldDataList(stringFieldDataLinkedHashMap, titles);
                        } else {
                            throw new Exception("文件标题为空");
                        }
                        continue;
                    }
                    // 读取数据
                    if (!CollectionUtils.isEmpty(fieldDataList)){
                        T instance = null;
                        //创建实例
                        try {
                            instance = clazz.newInstance();
                        } catch (InstantiationException | IllegalAccessException e) {
                            //创建实例失败,直接结束循环
                            throw new Exception("创建实例失败");
                        }
                        for (int i = 0; i < fieldDataList.size(); i++) {
                            //属性赋值
                            setInstanceField(instance,fieldDataList.get(i),csvRow.getField(i));
                        }
                        items.add(instance);
                    }
                }
            }
        } catch (final IOException e) {
            throw new UncheckedIOException(e);
        }
        return items;
    }

    /**
     * 实例属性赋值
     * @param instance
     * @param fieldData
     * @param value
     * @throws IllegalAccessException
     */
    private static void setInstanceField(Object instance, FieldData fieldData, String value) throws IllegalAccessException {
        if (!StringUtils.isEmpty(value)){
            fieldData.field.setAccessible(true);
            ObjectMapper objectMapper = new ObjectMapper();
            // 转化为对应类型
            Object rightTypeValue = objectMapper.convertValue(value, fieldData.field.getType());
            fieldData.field.set(instance,rightTypeValue);
        }
    }

    /**
     * 获取csv标题与转换类属性对应关系列表(顺序存取)
     * @param stringFieldDataLinkedHashMap
     * @param titles
     * @return
     */
    private static List getFieldDataList(LinkedHashMap stringFieldDataLinkedHashMap, List titles) {
        List fieldDataArrayList = new ArrayList<>();
        for (int i = 0; i < titles.size(); i++) {
            if (stringFieldDataLinkedHashMap.get(titles.get(i)) != null){
                // key 为列号,value为对应赋值方法
                fieldDataArrayList.add(stringFieldDataLinkedHashMap.get(titles.get(i)));
            }
        }
        return fieldDataArrayList;
    }

    /**
     * 导出列表 CSV
     * @param items 要导出的数据列表
     * @param os 输出到的流
     * @param res HttpServletResponse(可选),如果指定了就会添加文件下载的头部
     * @param fileName 可选,文件名,用户下载的文件名,传入 res 有效
     * @param 
     * @throws IOException
     */
    public static  void writeCsv(List items,
                                                   Class clazz,
                                                   OutputStream os,
                                                   HttpServletResponse res,
                                                   String fileName) throws IOException, IllegalAccessException {
        if (res != null) {
            setHttpHeader(res, fileName);
            if (os == null){
                os = res.getOutputStream();
            }
        }
        if (os == null){
            return;
        }
        if (items == null || items.size() < 1) {
            os.flush();
            return;
        }
        // 筛选出拥有注解的字段
        LinkedHashMap stringFieldDataLinkedHashMap = initCsvFields(clazz);
        if (stringFieldDataLinkedHashMap.size() < 1) {
            os.flush();
            return;
        }
        // 写入数据
        writeData(items, stringFieldDataLinkedHashMap, os);
    }

    /**
     * 设置下载用的 Http 响应头部
     * @param res
     * @param fileName
     */
    private static void setHttpHeader(HttpServletResponse res, String fileName) {
        fileName = StringUtils.isEmpty(fileName) ? (generateRandomFileName() + ".csv") : (fileName.contains(".csv") ? fileName : fileName + ".csv");
        res.setHeader("content-type", "application/octet-stream; charset=" + charset);
        res.setContentType("application/octet-stream");
        res.setHeader("Content-Disposition", "attachment; filename=" + fileName);
    }

    /**
     * 生成uudi随机文件名
     * @return
     */
    private static String generateRandomFileName() {
        return UUID.randomUUID().toString().replaceAll("-", "");
    }

    /**
     * 初始化要输出的CSV字段
     * @param clazz
     * @param 
     * @return
     */
    private static  LinkedHashMap initCsvFields(Class clazz) {
        Field[] fields = clazz.getDeclaredFields();

        // 筛选出拥有注解的字段与字段属性作为map
        LinkedHashMap stringFieldDataLinkedHashMap = new LinkedHashMap<>();
        for(int i=0;i< fields.length;i++){
            CsvField item = fields[i].getAnnotation(CsvField.class);
            if (item != null && !item.ignore()) {
                CsvConvertVisitable converter = null;
                if (item.using() != null && item.using() != CsvConvertHandler.None.class) {
                    converter = ClassUtil.createInstance(item.using(), true);
                }
                if (StringUtils.isEmpty(item.value())){
                    stringFieldDataLinkedHashMap.put(fields[i].getName(), new FieldData(fields[i], converter));
                } else {
                    stringFieldDataLinkedHashMap.put(item.value(), new FieldData(fields[i], converter));
                }
            }
        }
        return stringFieldDataLinkedHashMap;
    }

    /**
     * 写入数据
     * @param items
     * @param stringFieldDataLinkedHashMap
     * @param os
     * @param 
     * @throws IOException
     * @throws IllegalAccessException
     * @throws JsonProcessingException
     */
    private static  void writeData(List items, LinkedHashMap stringFieldDataLinkedHashMap, OutputStream os)
            throws IOException, IllegalAccessException, JsonProcessingException {
        final OutputStreamWriter outputStreamWriter = new OutputStreamWriter(os, charset);
        try (CsvWriter csv = CsvWriter.builder().build(outputStreamWriter)) {
            String[] strings = new String[stringFieldDataLinkedHashMap.size()];
            String[] textArray = stringFieldDataLinkedHashMap.keySet().toArray(strings);
            // 写入标题
            csv.writeRow(textArray);
            // 写入内容
            for (T item : items) {
                textArray = itemToStringArray(item, stringFieldDataLinkedHashMap, strings);
                if (textArray == null || textArray.length < 1){
                    continue;
                }
                csv.writeRow(textArray);
            }
        } finally {
            os.flush();
        }
    }

    /**
     * 将对象属性转化为字符串数组
     * @param item
     * @param stringFieldDataLinkedHashMap
     * @param values
     * @return
     * @throws IllegalAccessException
     */
    private static String[] itemToStringArray(Object item, LinkedHashMap stringFieldDataLinkedHashMap, String[] values)
            throws IllegalAccessException{
        int i = 0;
        for (Map.Entry stringFieldDataEntry : stringFieldDataLinkedHashMap.entrySet()) {
            FieldData fieldData = stringFieldDataEntry.getValue();
            fieldData.field.setAccessible(true);
            Object val = fieldData.field.get(item);
            if (fieldData.converter != null) {
                values[i] = fieldData.converter.convert(val);
            } else if (val == null) {
                values[i] = "";
                i += 1;
                continue;
            } else {
                values[i] = val.toString();
            }
            if (values[i] == null || values[i].isEmpty()){
                i += 1;
                continue;
            }
            if (!values[i].isEmpty() && (values[i].startsWith("{") || values[i].startsWith("["))){
                values[i] = "\"" + values[i].replace("\"", "\"\"") + "\"";
            } else {
                values[i] = values[i].replace("\\\"", "\"\"");
            }
            i += 1;
        }

        return values;
    }

    }

方法测试

/** 导出excel */
    @GetMapping("/export2")
    @ResponseBody
    Object getExcelFile2(HttpServletResponse res) throws Exception {
        try {

            List items = getList();
            long startTime = System.currentTimeMillis();
            FastCsvHelper.writeCsv(items, TestBean.class, res.getOutputStream(), res, "test");
            System.out.println("查询耗时:"+(System.currentTimeMillis()-startTime)/1000+"s");

        } catch (IOException e) {
            return null;
        }
        return null;
    }

    /** 读取excel */
    @PostMapping("/import")
    @ResponseBody
    Object importExcelFile(@RequestPart(name = "file",required = true) MultipartFile file) throws Exception {
        try {
            long startTime = System.currentTimeMillis();
            FastCsvHelper.readCsv(file.getInputStream(), TestBean.class);
            System.out.println("查询耗时:"+(System.currentTimeMillis()-startTime)/1000+"s");
        } catch (IOException e) {
            return null;
        }
        return null;
    }

测试结果

本机实测,5w行一百字段(每一字段约30字符)
读取:耗时6s-9s,文件大小200m
写入:耗时24s-26s,文件大小200m

你可能感兴趣的:(java,csv)