JAVA中opencsv包解析CSV大文件

业务的诞生

最近有些从大数据部门下载的基础数据,并已csv格式存储的,其中一些单个文件中数据量达到200W条。现在需要我把文件重新解析一次。因为其中一个表头对应的值是一个{key1=value1, key2=value2,key3=value3}类型。因为key的值都是定义的码值,所以要把key1、key2、key3等解析出来并翻译成对应的中文名称作为新的表头,然后生成新的csv文件。

优先思考解决思路

1.用Excel打开csv文件最大能展示出1048576行,想让领导能看到所有数据,当然需要拆分文件哦

2.我是将csv拆分成多个且最大900000条数据的csv文件,即便如此,单个文件也达到400M左右,程序处理过程中,要考虑下JVM内存,(我本地跑的程序,Xmx设置成8G),内存太小,肯定会内存溢出的。

3.接下来,当然是要做程序员最爱的事情,写逻辑!!!

csv大文件的拆分

网上找到了一个很好的文件处理工具:Data.olllo

 下载地址:http://olllo.top

操作都是傻瓜式的,就不细说:安装好工具-->打开工具-->打开文件-->拆分文件

代码实现过程

1.导入opencsv包

<!-- csv文件解析依赖 -->
        <dependency>
            <groupId>com.opencsv</groupId>
            <artifactId>opencsv</artifactId>
            <version>5.4</version>
        </dependency>

2.创建CsvUtils工具类

@Slf4j
public class CsvUtils {

    private static final String FIELD = "\\w+=(\\-|\\+)?\\d+(\\.\\d+)?";

    /**
     * 解析csv文件并转成bean(方法二)
     *
     * @param file csv文件
     * @return 数组
     */
    public static List<String[]> getCsvDataMethod2(MultipartFile file) {

        List<String[]> list = new ArrayList<String[]>();
        int i = 0;
        try {
            CSVReader csvReader = new CSVReaderBuilder(
                    new BufferedReader(
                            new InputStreamReader(file.getInputStream(), "utf-8"))).build();
            Iterator<String[]> iterator = csvReader.iterator();
            while (iterator.hasNext()) {
                String[] next = iterator.next();
                //去除第一行的表头,从第二行开始
                //if (i >= 1) {}

                list.add(next);
                i++;
            }
            return list;
        } catch (Exception e) {
            System.out.println("CSV文件读取异常");
            return list;
        }
    }

    /**
     * 解析csv文件并转成bean(方法二) 方法的增强,解析某个表头字段(数据是多个key-value),分离成新的多个字段
     *
     * @param file csv文件
     * @return 数组
     */
    public static Map<String, Object> getCsvDataMethod2Strong(MultipartFile file) {

        Map<String, Object> resultMap = new HashMap<>();

        // 重新解析数据,将staticsdatamap字段中的数据,解析成各个不同的字段并重新命名中文名称
        // 为了避免staticsdatamap中的各个字段数量不同导致表头不统一,先分析出所有的字段,其数据中没有的字段,则补空
        List<String> header = new ArrayList<>();
        int staticsdatamapIndex = 0;
        // 新增的表头字段map
        Map<String, String> staticsdatamapAttributeMap = new HashMap<>();
        // 新增的表头字段属性值map
        Map<Integer, Map<String, String>> staticsdatamapAttributeValueMap = new HashMap<>();

        List<List<String>> list = new ArrayList<>();
        int i = 0;
        try {
            CSVReader csvReader = new CSVReaderBuilder(
                    new BufferedReader(
                            new InputStreamReader(file.getInputStream(), "utf-8"))).build();
            Iterator<String[]> iterator = csvReader.iterator();
            while (iterator.hasNext()) {
                List<String> next = new ArrayList(Arrays.asList(iterator.next()));

                //第一行的表头,数据从第二行开始
                if (i == 0) {
                    header = next;
                    // 分析表头字段是staticsdatamap的下标值
                    for (int j = 0; j < header.size(); j++) {
                        if ("staticsdatamap".equals(header.get(j))) {
                            staticsdatamapIndex = j;
                        }
                    }
                } else {
                    // 获取staticsdatamap字段对应的值
                    String staticsdatamapValue = next.get(staticsdatamapIndex);
                    // 解析staticsdatamap字段对应的值,分析出各个字段值
                    analysisMap(staticsdatamapAttributeMap, staticsdatamapAttributeValueMap, staticsdatamapValue, i - 1);
                    list.add(next);

                    log.info("解析完第 {} 条数据", i);
                }

                i++;
            }

            // 根据staticsdatamap解析出来的表头字段以及各个数据对应的数值,重新组装成一个新的List
            for (Map.Entry<String, String> entry : staticsdatamapAttributeMap.entrySet()) {
                header.add(entry.getValue());

                for (int j = 0; j < list.size(); j++) {
                    if (staticsdatamapAttributeValueMap.containsKey(j)) {
                        list.get(j).add(staticsdatamapAttributeValueMap.get(j).get(entry.getKey()));
                    } else {
                        list.get(j).add("");
                    }
                }
            }

            resultMap.put("header", header);
            resultMap.put("data", list);

            return resultMap;
        } catch (Exception e) {
            System.out.println("CSV文件读取异常");
            return resultMap;
        }
    }

    private static void analysisMap(Map<String, String> staticsdatamapAttributeMap,
                                    Map<Integer, Map<String, String>> staticsdatamapAttributeValueMap,
                                    String staticsdatamapValue,
                                    int index) {
        // 正则匹配出所有的字段
        Pattern pattern = Pattern.compile(FIELD);
        Matcher matcher = pattern.matcher(staticsdatamapValue);
        while (matcher.find()) {
            // 获取单个匹配项,格式为:spn=value
            String fieldValue = matcher.group();
            // 将单个匹配项拆分成一个list
            List<String> keyAndValueList = Arrays.asList(fieldValue.split("="));
            // 从SPNMap中查找中文对照关系,如果找到了,则重新创建新的表头字段名: 中文名(SPN),如果没有,则剔除数据
            if (SPNMap.getInstance().getMap().containsKey(keyAndValueList.get(0))) {
                String newFieldName = SPNMap.getInstance().getMap().get(keyAndValueList.get(0)) + "(" + keyAndValueList.get(0) + ")";
                staticsdatamapAttributeMap.put(keyAndValueList.get(0), newFieldName);

                if (CollectionUtils.isEmpty(staticsdatamapAttributeValueMap.get(index))) {
                    Map<String, String> tempMap = new HashMap<>();
                    tempMap.put(keyAndValueList.get(0), keyAndValueList.get(1));
                    staticsdatamapAttributeValueMap.put(index, tempMap);
                } else {
                    staticsdatamapAttributeValueMap.get(index).put(keyAndValueList.get(0), keyAndValueList.get(1));
                }
            }
        }
    }
    
    /**
     * CSV文件生成方法
     * @param head 文件头
     * @param dataList 数据列表
     * @param outPutPath 文件输出路径
     * @param filename 文件名
     * @return
     */
    public static File createCSVFile(List<Object> head, List<List<Object>> dataList,String outPutPath, String filename) {

        File csvFile = null;
        BufferedWriter csvWtriter = null;
        try {
            csvFile = new File(outPutPath + File.separator + filename + ".csv");
            File parent = csvFile.getParentFile();
            if (parent != null && !parent.exists()) {
                parent.mkdirs();
            }
            csvFile.createNewFile();

            // GB2312使正确读取分隔符","
            csvWtriter = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(
                    csvFile), "GBK"), 1024);
            // 写入文件头部
            writeRow(head, csvWtriter);

            int i = 0;
            // 写入文件内容
            for (List<Object> row : dataList) {
                writeRow(row, csvWtriter);

                i ++;

                log.info("成功写入CSV第 {} 条数据", i);
            }
            csvWtriter.flush();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                csvWtriter.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return csvFile;
    }

    /**
     * 写一行数据方法
     * @param row
     * @param csvWriter
     * @throws IOException
     */
    private static void writeRow(List<Object> row, BufferedWriter csvWriter) throws IOException {
        // 写入文件头部
        for (Object data : row) {
            StringBuffer sb = new StringBuffer();
            String rowStr = sb.append("\"").append(data == null ? "" : data).append("\",").toString();
            csvWriter.write(rowStr);
        }
        csvWriter.newLine();
    }
}

3.创建一个饿汉式单利类初始化常量集合(涉及隐私安全,所以用key1、key2、key3......代替)

public class SPNMap {

    private static final Map<String, String> map = new HashMap<>();

    private static SPNMap spnMap = new SPNMap();

    private SPNMap() {

    }

    public static SPNMap getInstance(){
        return spnMap;
    }

    public static Map<String, String> getMap() {
        return map;
    }

    static {
        map.put("key1", "value1");
        map.put("key1", "value2");
        map.put("key1", "value3");
        map.put("key1", "value4");
        map.put("key1", "value5");
    }
}

4.Controller代码

/**
 * @program com.example.demo.controller
 * @description 公共
 * @auther Mr.Xiong
 * @create 2020-03-07 12:36
 */
@RestController
@RequestMapping("/common")
public class CommonController {

    @Autowired
    private CommonService commonService;

    @ResponseBody
    @RequestMapping("/importCSV")
    public void importCSV(MultipartFile file) {
        commonService.importCSV(file);
    }
}

5.Service

/**
 * @program com.example.demo.service
 * @description common service
 * @auther Mr.Xiong
 * @create 2020-03-07 13:10
 */
@Service
public interface CommonService {

    void importCSV(MultipartFile file);
}

6.ServiceImpl

/**
 * @program com.example.demo.service
 * @description common service
 * @auther Mr.Xiong
 * @create 2020-03-07 13:10
 */
@Slf4j
@Service
public class CommonServiceImpl implements CommonService {

    private static final String DIR = "C:\\Users\\50299\\Desktop\\csv";

    public void importCSV(MultipartFile file) {
        log.info("importCSV --- START");

        log.info("获取CSV文件中的所有表头加数据");
        Map<String, Object> resultMap = CsvUtils.getCsvDataMethod2Strong(file);

        log.info("获取CSV文件中的所有表头");
        List<Object> header = (List<Object>) resultMap.get("header");

        log.info("获取CSV文件中的所有数据");
        List<List<Object>> data = (List<List<Object>>) resultMap.get("data");

        log.info("导出新的CSV文件");
        CsvUtils.createCSVFile(header, data, DIR, file.getOriginalFilename().split("\\.")[0]);

        log.info("importCSV --- END");
    }
}

 7.postman调用。。。

 8.整个过程,大概用了一天时间。当一切搞定的时候,感觉还挺有成就感!!!

        

  • 1
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值