zoukankan      html  css  js  c++  java
  • 百万甚至千万级数据下载

    (#)直奔主题,这次要说的是针对百万千万数据的下载,使用的是poi excel的下载,这个如果大家不熟悉的话,可以去看看这个基础博客,写的不错

      http://www.jerehedu.com/fenxiang/160218_for_detail.htm

    (#)然而问题来了,(1)excel如何装这么多的数据呢?(2)jvm肯定是一次放不下的  针对与问题(1),其实比较好解决,excel07提供了一个新的

       XSSFWorkbook,它会将数据存储在磁盘上,内存中只加载一部分够自己使用就好,那么对于问题(2)怎么解决呢?其实想想也不是很难,只要使用

       lazy的加载模式就没有问题了(lazy模式只会在使用的时候才将数据加载),那么在java中lazy的模式首先想到的就是Iterable来包装

    (#)这里如果大家的tomcat没有打印gc.log日志,因为这个代码运行的时候,首先要关注的就是gc的问题,如果发现一直在fgc那么肯定就要跪了,这个时候怎么看gc

      日志呢?嗯,如果不清楚的话,首先要做的就是去熟悉一下jvm带的命令,我们使用jstat -gcutil -uid 来看一下,如下:

    (#) 废话不多说,show the  code

      首先是读取数据库数据的代码,如下:

       Iterable<List<AirlineRecord>> airRecords = new Iterable<List<AirlineRecord>>() {
                @Override
                public Iterator<List<AirlineRecord>> iterator() {
                    return new AbstractIterator<List<AirlineRecord>>() {
                        int totalCount = airlineRecordDao.selectCountByState(null);
                        int sizeCount = totalCount / SIZE;
                        int index = 0;
    
                        @Override
                        protected List<AirlineRecord> computeNext() {
                            if (index > sizeCount) {
                                return endOfData();
                            }
                            List<AirlineRecord> records = airlineRecordDao.selectByState(null, new RowBounds(index * SIZE, SIZE));
                            index++;
                            return records;
                        }
                    };
                }
            };
    

      我的数据库中的数据量是100多万的,RowBounds应该使用一个拦截器改成物理分,要不还是会有问题的,然后剩下的就很简单了,利用Iterable本身的lazy属性,现

      在不会将多有的数据都加载进来,每次只会迭代SIZE大小的数据,这样JVM就不会有任何压力,百万,千万的数据都是可以撑的住的

    下面来看一下下载的代码

     public static <T> SXSSFWorkbook export(Iterable<T> excelData, String sheetName, TableFormatterConfigurer configurer) {
            // 声明一个(SXSSFWorkbook)工作薄
            SXSSFWorkbook workbook = new SXSSFWorkbook(); // SXSSFWorkbook默认只在内存中存放100行
            T firstItem = Iterables.getFirst(excelData, null);
            if (firstItem != null) {
                return export(sheetName, excelData, workbook, configurer, (Class<T>) firstItem.getClass());
            }
            return workbook;
        }
    
        /**
         * 导出Excel
         *
         * @param sheetName excel表格名
         * @param excelData 要导出的数据
         * @param workbook 要导出的工作薄
         * @return
         *
         * @author mars.mao created on 2014年10月17日下午7:36:26
         */
        public static <T> HSSFWorkbook export(String sheetName, List<T> excelData, HSSFWorkbook workbook) {
            return export(sheetName, excelData, workbook, TableFormatterConfigurer.NONE);
        }
    
        public static <T> HSSFWorkbook export(String sheetName, List<T> excelData, HSSFWorkbook workbook,
                TableFormatterConfigurer configurer) {
    
            if (excelData == null || excelData.isEmpty() || workbook == null || StringUtils.isBlank(sheetName)) {
                return workbook;
            }
    
            try {
    
                // 定义标题行字体
                Font font = workbook.createFont();
                font.setBoldweight(Font.BOLDWEIGHT_BOLD);
    
                int totalDataSize = excelData.size();
                int sheetCnt = totalDataSize / EXCEL_MAX_ROW_NO + 1;
    
                if (sheetCnt > EXCEL_MAX_SHEET_CNT) {
                    throw new Exception("数据量超过了Excel的容量范围!");
                }
    
                for (int i = 0; i < sheetCnt; i++) {
                    int fromIndex = i * EXCEL_MAX_ROW_NO;
                    int toIndex = fromIndex + EXCEL_MAX_ROW_NO;
                    toIndex = toIndex > totalDataSize ? totalDataSize : toIndex;
                    List<T> sheetData = excelData.subList(fromIndex, toIndex);
    
                    // 生成一个表格
                    Sheet sheet = workbook.createSheet(sheetName + "_" + i);
    
                    // 生成标题行
                    createHeader(sheetData, sheet, font, configurer);
    
                    // 遍历集合数据,产生数据行
                    createBody(sheetData, sheet, configurer);
                }
    
                logger.info("导出的数据行数(不含表头): writeCount={}", excelData.size());
                return workbook;
            } catch (Exception e) {
                logger.error("导出Excel异常!", e);
            }
    
            return workbook;
        }
    
        /**
         * 导出Excel, 使用SXSSFWorkbook支持大数据量的导出
         *
         * @param sheetName excel表格名
         * @param excelData 要导出的数据
         * @param workbook 要导出的工作薄
         * @param configurer 配置数据格式化行为的对象
         * @param clazz 待导出数据的类型信息
         * @return
         */
        public static <T> SXSSFWorkbook export(String sheetName, Iterable<T> excelData, SXSSFWorkbook workbook,
                TableFormatterConfigurer configurer, Class<T> clazz) {
            final Iterator<T> externalIterator;
            if (excelData == null || !(externalIterator = excelData.iterator()).hasNext() || workbook == null
                    || StringUtils.isBlank(sheetName)) {
                return workbook;
            }
    
            try {
                // 定义标题行字体
                Font font = workbook.createFont();
                font.setBoldweight(Font.BOLDWEIGHT_BOLD);
    
                int writeCount = 0; // 记录导出的行数
                int i = 0;
                while (externalIterator.hasNext()) {
                    // 生成一个表格
                    Sheet sheet = workbook.createSheet(sheetName + "_" + i);
    
                    // 生成标题行
                    createHeader(sheet, font, configurer, clazz);
    
                    // 遍历集合数据,产生数据行
                    writeCount += createBody(new Iterable<T>() {
                        public Iterator<T> iterator() {
                            return new Iterator<T>() {
                                int lineCount = 0;
    
                                public boolean hasNext() {
                                    return lineCount < EXCEL_MAX_ROW_NO && externalIterator.hasNext();
                                }
    
                                public T next() {
                                    if (!hasNext()) {
                                        throw new NoSuchElementException("没有更多的元素");
                                    }
                                    lineCount++;
                                    return externalIterator.next();
                                }
    
                                public void remove() {
                                    throw new UnsupportedOperationException("不支持删除操作");
                                }
                            };
                        }
                    }, sheet, configurer, clazz);
    
                    if ((++i) == EXCEL_MAX_SHEET_CNT) {
                        throw new Exception("数据量超过了Excel的容量范围!");
                    }
                }
    
                logger.info("导出的数据行数(不含表头): writeCount={}", writeCount);
                return workbook;
            } catch (Exception e) {
                logger.error("导出Excel异常!", e);
            }
    
            return workbook;
        }
    
        /**
         * 创建表格数据
         *
         * @param excelData 要导出的数据
         * @param sheet excel表格
         * @param configurer 配置数据格式化行为的对象
         * @return 返回创建的数据行数
         *
         * @author mars.mao created on 2014年10月17日下午3:43:43
         */
        @SuppressWarnings("unchecked")
        private static <T> int createBody(List<T> excelData, Sheet sheet, TableFormatterConfigurer configurer)
                throws Exception {
            if (CollectionUtils.isEmpty(excelData)) {
                return 0;
            }
    
            Class<?> dataClass = excelData.get(0).getClass();
            return createBody(excelData, sheet, configurer, dataClass);
        }
    
        /**
         * 创建表格数据
         *
         * @param excelData 要导出的数据
         * @param sheet excel表格
         * @param configurer 配置数据格式化行为的对象
         * @param clazz 要导出的对象的类型信息, 用以获取相关注解以及字段值
         * @return 返回创建的数据行数
         */
        private static <T> int createBody(Iterable<T> excelData, Sheet sheet, TableFormatterConfigurer configurer,
                Class<?> clazz) throws Exception {
            if (excelData == null) {
                return 0;
            }
            List<Field> fields = getExportableFields(clazz, configurer);
            Map<String, Method> methodMap = getFormattingMethods(clazz);
    
            int dataRowIndex = 1;
            for (T data : excelData) {
                // 创建数据行
                Row dataRow = sheet.createRow(dataRowIndex);
    
                int columnIndex = 0;
                for (Field field : fields) {
                    ExcelColumn columnHeader = field.getAnnotation(ExcelColumn.class);
                    // 创建列
                    String textValue;
                    Method formattingMethod = methodMap.get(field.getName());
                    if (formattingMethod != null) {
                        // 优先使用"格式化方法"对属性进行格式化
                        formattingMethod.setAccessible(true);
                        textValue = String.valueOf(formattingMethod.invoke(data));
                    } else {
                        // 在没有"格式化方法"时使用@ExcelColumn指定的格式化方式
                        textValue = getTextValue(data, field, columnHeader, configurer);
                    }
                    Cell cell = dataRow.createCell(columnIndex);
                    // HSSFRichTextString text = new HSSFRichTextString(textValue);
                    // cell.setCellValue(text);
                    cell.setCellValue(textValue);
    
                    columnIndex++;
                }
    
                dataRowIndex++;
            }
    
            return dataRowIndex - 1;
        }
    
        /**
         * 生成Excel的标题行
         *
         * @param excelData 导出的数据列表
         * @param sheet excel表
         * @return
         *
         * @author mars.mao created on 2014年10月17日下午2:08:41
         */
        @SuppressWarnings("unchecked")
        private static <T> void createHeader(List<T> excelData, Sheet sheet, Font font, TableFormatterConfigurer configurer) {
            if (CollectionUtils.isEmpty(excelData)) {
                return;
            }
    
            Class<?> dataClass = excelData.get(0).getClass();
            createHeader(sheet, font, configurer, dataClass);
        }
    
        private static void createHeader(Sheet sheet, Font font, TableFormatterConfigurer configurer, Class<?> clazz) {
            List<Field> fields = getExportableFields(clazz, configurer);
    
            Row headerRow = sheet.createRow(0);
            int columnIndex = 0;
            for (Field field : fields) {
                ExcelColumn columnHeader = field.getAnnotation(ExcelColumn.class);
                // 获取指定的列标题和列宽度
                String columnTitle = columnHeader.headerName();
                int columnWidth = columnHeader.columnWidth();
    
                // 创建列
                Cell cell = headerRow.createCell(columnIndex);
                // 设置列标题
                if (sheet instanceof HSSFSheet) {
                    RichTextString text = new HSSFRichTextString(columnTitle);
                    text.applyFont(font);
                    cell.setCellValue(text);
                } else {
                    cell.setCellValue(columnTitle);
                }
                // 设置列宽度
                sheet.setColumnWidth(columnIndex, columnWidth * 256);
    
                columnIndex++;
            }
        }
    
        /**
         * 获取格式化的文本内容
         *
         * @param obj 输入对象
         * @param field 对象域
         * @param columnHeader ExcelColumn注解的配置实例
         * @param configurer TableFormatterConfigurer的配置实例
         * @return 格式化后的文本
         */
        @SuppressWarnings("unchecked")
        private static <T> String getTextValue(T obj, Field field, ExcelColumn columnHeader,
                TableFormatterConfigurer configurer) {
            String aimPattern = columnHeader.pattern();
            Object fieldValue = getFieldValue(obj, field); // 反射获取字段的值
            String textValue = null;
    
            Class<? extends Formatter> formatterClass = columnHeader.formatterClass();
            boolean allowAlternativeFormatter = columnHeader.allowAlternativeFormatter();
    
            boolean formatted = false;
            try {
                // 1) 尝试使用注册在的formatterMap中的formatter; 以属性名为key的formatterMap优先
                if (allowAlternativeFormatter) {
                    Formatter registeredFormatter = configurer.getFormatter(field.getName());
                    if (registeredFormatter == null && fieldValue != null) {
                        registeredFormatter = configurer.getFormatter(fieldValue.getClass());
                    }
                    if (registeredFormatter != null) {
                        textValue = registeredFormatter.format(fieldValue);
                        formatted = true;
                    }
                }
                // 2) 没有获取到注册的formatter, 则尝试使用指定的formatterClass
                if (!formatted && formatterClass != Formatter.None.class) {
                    Formatter formatter = formatterClass.newInstance();
                    textValue = formatter.format(fieldValue);
                    formatted = true;
                }
            } catch (Exception e) {
                logger.error("导出Excel使用formatter格式化出错: {}", e.getMessage(), e);
                formatted = false;
            }
            if (!formatted) {
                /*
                 * 3) [未指定formatter, 且未开启formatter的注册] 或者 [以上格式化失败]; 使用默认的处理方式...
                 */
                textValue = defaultToString(fieldValue, aimPattern);
            }
            return textValue;
        }
    
        /*
         * 默认的格式处理方式 (与历史版本兼容)
         */
        private static String defaultToString(Object fieldValue, String aimPattern) {
            String textValue = " ";
            if (fieldValue != null) {
                textValue = fieldValue.toString();
            }
            if (fieldValue instanceof Date) {
                try {
                    String pattern = StringUtils.isBlank(aimPattern) ? DEFAULT_DATE_FORMAT : aimPattern;
                    Date date = (Date) fieldValue;
                    textValue = DateFormatUtils.format(date, pattern);
                } catch (Exception e) {
                    logger.error("导出Excel日期格式化错误!", e);
                }
            } else if (fieldValue instanceof DateTime) { // 添加对DateTime类型的兼容(采用与Date类型一样的pattern设置) [by chenjiahua.chen
                                                         // 2016/09/05]
                try {
                    String pattern = StringUtils.isBlank(aimPattern) ? DEFAULT_DATE_FORMAT : aimPattern;
                    DateTime dateTime = (DateTime) fieldValue;
                    textValue = dateTime.toString(pattern);
                } catch (Exception e) {
                    logger.error("导出Excel日期格式化错误!", e);
                }
            } else if (fieldValue instanceof Money) { // 添加对Money类型的兼容, 以toMoneyString输出(两位小数) [by chenjiahua.chen
                                                      // 2016/09/05]
                try {
                    Money money = (Money) fieldValue;
                    return money.toMoneyString();
                } catch (Exception e) {
                    logger.error("导出Excel金额格式化错误!", e);
                }
            } else if (fieldValue instanceof Number) {
                if (StringUtils.isNotBlank(aimPattern)) {
                    try {
                        double doubleValue = Double.parseDouble(fieldValue.toString());
                        DecimalFormat df1 = new DecimalFormat(aimPattern);
                        textValue = df1.format(doubleValue);
                    } catch (Exception e) {
                        logger.error("导出Excel数字格式化错误!", e);
                    }
                }
            }
            return textValue;
        }
    
        /**
         * 反射获取字段的值
         *
         * @param obj 对象
         * @param field 字段
         * @return
         *
         * @author mars.mao created on 2014年10月17日下午2:58:53
         */
        private static <T> Object getFieldValue(T obj, Field field) {
            Object fieldValue = " ";
    
            try {
                field.setAccessible(true);
                fieldValue = field.get(obj);
                if (fieldValue != null) {
                    return fieldValue;
                }
            } catch (Exception e) {
                logger.error("导出Excel动态获取字段值异常", e);
            }
            return fieldValue;
        }
    
        /**
         * 根据给定类的信息获取其所有字段 (包括继承而来的字段!)
         */
        private static List<Field> getExportableFields(Class<?> clazz, final TableFormatterConfigurer configurer) {
            List<Field> fields = Lists.newArrayList();
            /* 循环向上查找, 以支持父类中的属性导出 [by chenjiahua.chen 2016/09/05] */
            for (Class<?> dataClass : getClassesInHierarchy(clazz)) {
                fields.addAll(Arrays.asList(dataClass.getDeclaredFields()));
            }
    
            // 过滤不需要导出的属性
            fields = Lists.newArrayList(Iterables.filter(fields, new Predicate<Field>() {
                @Override
                public boolean apply(Field field) {
                    ExcelColumn columnHeader = field.getAnnotation(ExcelColumn.class);
                    if (columnHeader == null || configurer.getHeadersToBeFiltered().contains(columnHeader.headerName())) {
                        return false;
                    }
                    return true;
                }
            }));
            return fields;
        }
    
        private static Map<String, Method> getFormattingMethods(Class<?> clazz) {
            List<Method> methods = Lists.newArrayList();
            /* 循环向上查找, 以支持父类中的格式化方法 [by chenjiahua.chen 2017/02/09] */
            for (Class<?> dataClass : getClassesInHierarchy(clazz)) {
                methods.addAll(Arrays.asList(dataClass.getDeclaredMethods()));
            }
    
            // 过滤和格式化无关的方法
            methods = Lists.newArrayList(Iterables.filter(methods, new Predicate<Method>() {
                @Override
                public boolean apply(Method method) {
                    return method.getAnnotation(FieldFormat.class) != null;
                }
            }));
    
            return Maps.uniqueIndex(methods, new Function<Method, String>() {
                @Override
                public String apply(Method method) {
                    return method.getAnnotation(FieldFormat.class).fieldName();
                }
            });
        }
    
        /**
         * 获取继承体系中的类
         */
        private static List<Class<?>> getClassesInHierarchy(Class<?> clazz) {
            List<Class<?>> classes = Lists.newArrayList();
    
            Stack<Class<?>> classStack = new Stack<Class<?>>();
            Class<?> currentClass = clazz;
            while (currentClass != null) {
                classStack.push(currentClass);
                currentClass = currentClass.getSuperclass();
            }
            while (!classStack.isEmpty()) {
                classes.add(classStack.pop());
            }
            return classes;
        }
    
        public static Workbook dynamicExport(String sheetName, List<RowRecord> excelData) {
            if (org.apache.commons.collections4.CollectionUtils.isEmpty(excelData) || StringUtils.isBlank(sheetName)) {
                return null;
            }
            HSSFWorkbook workbook = new HSSFWorkbook();
            // 标题行字体
            Font font = workbook.createFont();
            font.setBold(true);
            // 定义单元格格式
            CellStyle cellStyle = workbook.createCellStyle();
            cellStyle.setAlignment(HorizontalAlignment.CENTER);
            // 分割sheet
            int totalCnt = excelData.size();
            int sheetCnt = totalCnt / EXCEL_MAX_ROW_NO + 1;
            if (sheetCnt > EXCEL_MAX_SHEET_CNT) {
                throw new RuntimeException("数据量超过了Excel的容量范围!");
            }
    
            for (int i = 0; i < sheetCnt; i++) {
                int fromIndex = i * EXCEL_MAX_ROW_NO;
                int toIndex = fromIndex + EXCEL_MAX_ROW_NO;
                toIndex = toIndex > totalCnt ? totalCnt : toIndex;
                List<RowRecord> sheetData = excelData.subList(fromIndex, toIndex);
                // 生成一个表格
                Sheet sheet = workbook.createSheet(sheetName + "_" + i);
                // 生成标题行
                createHeader(sheetData, sheet, font, cellStyle);
                // 遍历集合数据,产生数据行
                createBody(sheetData, sheet);
            }
    
            return workbook;
        }
    
        private static void createHeader(List<RowRecord> excelData, Sheet sheet, Font font, CellStyle cellStyle) {
    
            if (org.apache.commons.collections4.CollectionUtils.isEmpty(excelData)) {
                return;
            }
            RowRecord rowRecord = excelData.get(0);
    
            createHeader(sheet, rowRecord, font, cellStyle);
        }
    
        private static void createHeader(Sheet sheet, RowRecord rowRecord, Font font, CellStyle cellStyle) {
            if (org.apache.commons.collections4.CollectionUtils.isEmpty(rowRecord.getColumnRecordList())) {
                return;
            }
            // 获取表头节点的层数
            List<ColumnRecord> columnRecordList = rowRecord.getColumnRecordList();
            int HeaderLayerCnt = 1;
            for (ColumnRecord columnRecord : columnRecordList) {
                if (HeaderLayerCnt < columnRecord.getColumnNameList().size()) {
                    HeaderLayerCnt = columnRecord.getColumnNameList().size();
                }
            }
            int i = 0;
            int columnIndex;
            int preColumnIndex;
            String preHeader;
            while (i < HeaderLayerCnt) {
                preHeader = null;
                preColumnIndex = columnIndex = 0;
                Row headerRow = sheet.createRow(i);
                for (ColumnRecord columnRecord : rowRecord.getColumnRecordList()) {
                    if (columnRecord.getColumnNameList().size() < i + 1) {
                        columnIndex++;
                        preColumnIndex++;
                        continue;
                    }
                    if (preHeader == null || !columnRecord.getColumnNameList().get(i).equals(preHeader)) {
                        // 获取指定的列标题和列
                        if (columnIndex - preColumnIndex > 1) {
                            sheet.addMergedRegion(new CellRangeAddress(i, i, preColumnIndex, columnIndex - 1));
                        }
                        preColumnIndex = columnIndex;
                        String columnTitle = columnRecord.getColumnNameList().get(i);
                        // 创建列
                        Cell cell = headerRow.createCell(columnIndex);
                        // 设置列标题
                        RichTextString text = new HSSFRichTextString(columnTitle);
                        text.applyFont(font);
                        cell.setCellValue(text);
                        cell.setCellStyle(cellStyle);
                        preHeader = columnRecord.getColumnNameList().get(i);
                    }
                    // 最后一列判定
                    if ((columnIndex == rowRecord.getColumnRecordList().size() - 1) && (columnIndex - preColumnIndex >= 1)) {
                        sheet.addMergedRegion(new CellRangeAddress(i, i, preColumnIndex, columnIndex));
                    }
                    columnIndex++;
                }
                i++;
            }
    
        }
    
        private static void createBody(List<RowRecord> rowRecords, Sheet sheet) {
            int rowIndex = sheet.getLastRowNum() + 1;
            for (RowRecord rowRecord : rowRecords) {
                Row dataRow = sheet.createRow(rowIndex);
                int columnIndex = 0;
                for (ColumnRecord columnRecord : rowRecord.getColumnRecordList()) {
                    String textValue = getTextValue(columnRecord);
                    Cell cell = dataRow.createCell(columnIndex);
                    cell.setCellValue(textValue);
                    sheet.autoSizeColumn(columnIndex, true);
                    columnIndex++;
                }
                rowIndex++;
            }
        }
    
        private static String getTextValue(ColumnRecord columnRecord) {
            Class<? extends Formatter> formatterClass = columnRecord.getFormatter();
            if (formatterClass.equals(Formatter.DefaultFormatter.class)) {
                return columnRecord.getPropertyValue().toString();
            }
            try {
                Formatter<Object> formatter = formatterClass.newInstance();
                return formatter.format(columnRecord.getPropertyValue());
            } catch (Exception e) {
                logger.error("导出Excel使用formatter格式化出错: {}", e.getMessage(), e);
            }
    
            return null;
        }
    

      说点啥呢,嗯,就随便说说如果操作不当的时候会出现什么情况呢,首先看看gc,会一直在进行fgc,但是并没有什么卵用,E 100%,O 99.99%,每次fgc由于数据都是被

      引用的状态,也就是说根本gc不掉的,那么就会一直进行fgc,fgc的时候是stop the world,所以你的tomcat会出在假死状态,并且肯定会造成服务不可用,一会服务器

        就会真的跪了,所以写代码的时候一定要注意你的内存使用情况,注意数据的大小.

      最后说下,我导出了100多万的数据,没啥问题,理论上导出千万的数据也没有问题,只是会分成多个sheet而已,如果使用上有什么问题,欢迎咨询可以一起讨论!

  • 相关阅读:
    Linux 共享库
    使用Visual Studio(VS)开发Qt程序代码提示功能的实现(转)
    ZOJ 3469 Food Delivery(区间DP)
    POJ 2955 Brackets (区间DP)
    HDU 3555 Bomb(数位DP)
    HDU 2089 不要62(数位DP)
    UESTC 1307 windy数(数位DP)
    HDU 4352 XHXJ's LIS(数位DP)
    POJ 3252 Round Numbers(数位DP)
    HDU 2476 String painter (区间DP)
  • 原文地址:https://www.cnblogs.com/wscit/p/6812231.html
Copyright © 2011-2022 走看看