大批量資料excel下載---本文作者只試了51萬資料的下載,用時7秒
一.背景:
現在的專案裡,有諸多下載功能,隨著資料越來越多,下載的時間也越來越長,很影響使用者體驗,為了解決這一問題,我不得不挺身而出,斬破難關。專案中原本用的是poi-HSSFWorkbook,但是如果是50萬資料量下載,回經歷一個漫長的等待過程,然後記憶體溢位。jxl也不用想了,估計也差不多。
二.兩種方法:
後來從網上搜索發現針對大資料量的匯出有兩條路可以走:第一:用poi-SXSSFWorkbook;第二:用io流的方式。
1.好吧,先試了第一種SXSSFWorkbook的方式解決問題,最後我水平有限,沒能成功的使用第一種SXSSFWorkbook的思路解決50萬資料的匯出問題,因為系統也崩了。不過我覺得失敗的原因是我程式碼寫的有問題,沒有正確使用SXSSFWorkbook用的不對
http://blog.csdn.net/qq_29631809/article/details/72785338
https://www.programcreek.com/java-api-examples/index.php?api=org.apache.poi.xssf.streaming.SXSSFWorkbook
2.那麼只能把希望留給第二種io流的方式了。
先給大家一個連線,我就是從這個連線的內容獲取的思路,大家先看看,不知道你們有沒有什麼想法:
http://blog.csdn.net/xx123698/article/details/48782013
不願意看此連結的,可以直接看我截的圖:
三.具體思路
到此,相信諸位看官已經有了方向,不過具體實現起來,仍是很模糊。我來敘述一下我的思路,以下5步:
四.思路明白了,就是上程式碼了
1.Controller類
package com.quanran.controller;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import springfox.documentation.annotations.ApiIgnore;
import com.github.pagehelper.Page;
import com.quanran.HardwareMonitoringVo;
import com.quanran.TerminalService;
/**
* <p>Discription:[查詢裝置監聽列表並匯出]</p>
* Created on 2018年2月2日 下午5:45:37
* @param hardwareMonitoringVo 封裝查詢引數officeBuildingId和terminalDeviceType的實體類
* @param pageNum 當前頁數
* @param pageSize 每頁顯示的條數,如果查詢所有傳 -1
* @param response 返回給前臺的response物件
* @author:[全冉]
*/
@Api(value = "裝置管理相關介面", description = "裝置管理相關介面")
@RestController
@RequestMapping("/terminal")
public class EcmTerminalController {
@Resource
private TerminalService terminalService;
/**
* <p>Discription:[查詢裝置監聽列表並匯出]</p>
* Created on 2017年12月11日
* @param response response
* @author:[全冉]
*/
@ApiOperation("查詢裝置監聽列表並匯出[ [email protected]]")
@GetMapping("/exportHardwareMonitoring")
@ApiImplicitParams({
@ApiImplicitParam(name = "officeBuildingId", value = "辦公區", required = true, paramType = "query"),
@ApiImplicitParam(name = "terminalDeviceType", value = "1:pad 2:終端機", required = false, paramType = "query"),
@ApiImplicitParam(name = "pageNum", value = "當前頁碼", required = true, paramType = "query"),
@ApiImplicitParam(name = "pageSize", value = "當頁大小,如果查詢所有 pageSize = -1", required = true, paramType = "query")
})
public void exportHardwareMonitoring(@ApiIgnore() HardwareMonitoringVo hardwareMonitoringVo,
@ApiParam(value = "當前頁碼", required = true) @RequestParam(required = true) int pageNum,
@ApiParam(value = "當頁大小,如果查詢所有 pageSize = -1", required = true) @RequestParam(required = true) int pageSize,
HttpServletResponse response) {
terminalService.exportHardwareMonitoring(hardwareMonitoringVo, new Page<HardwareMonitoringVo>(pageNum, pageSize), response);
}
}
2.TerminalService類package com.quanran.service;
import javax.servlet.http.HttpServletResponse;
import com.github.pagehelper.Page;
import com.quanran.dao.dto.HardwareMonitoringVo;
/**
* Description: [裝置管理服務]
* Created on 2017年11月09日
* @author <a href="mailto: [email protected]">全冉</a>
* @version 1.0
* Copyright (c) 2017年 北京全冉有限公司
*/
public interface TerminalService {
/**
* <p>Discription:[查詢裝置監聽列表並匯出]</p>
* Created on 2018年2月2日 下午5:54:06
* @param hardwareMonitoringVo 封裝前臺傳過來的查詢引數
* @param page 分頁物件,封裝了pageNum和pageSize兩個引數
* @param response response物件
* @author:[全冉]
*/
void exportHardwareMonitoring(HardwareMonitoringVo hardwareMonitoringVo, Page<HardwareMonitoringVo> page, HttpServletResponse response);
}
3.TerminalServiceImpl類
package com.quanran.service.impl;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import com.github.pagehelper.Page;
import com.quanran.common.log.LogMessage;
import com.quanran.dao.dto.HardwareMonitoringVo;
import com.quanran.dao.mapper.HardwareMonitoringMapper;
import com.quanran.service.TerminalService;
import com.quanran.service.multithreading.HardwareMonitoringThread;
import com.quanran.visitor.common.util.ExcelExportUtils;
/**
* Description: [裝置管理服務實現]
* Created on 2017年11月09日
* @author <a href="mailto: [email protected]">全冉</a>
* @version 1.0
* Copyright (c) 2017年 北京全冉有限公司
*/
@Component
public class TerminalServiceImpl implements TerminalService {
@Resource
private HardwareMonitoringMapper hardwareMonitoringMapper;
/**
* 列印日誌
*/
private static final Logger LOGGER = LoggerFactory.getLogger(TerminalServiceImpl.class);
public void exportHardwareMonitoring(HardwareMonitoringVo hardwareMonitoringVo, Page<HardwareMonitoringVo> hardwareMonitorings, HttpServletResponse response) {
LOGGER.info("進入【TerminalServiceImpl-exportHardwareMonitoring】方法,開始毫秒為:【"+System.currentTimeMillis()+"】");
try {
/**
* 第一步:準備一些引數
*/
// 此次匯出的總行數
Integer allCount = hardwareMonitoringMapper.selectCount(hardwareMonitoringVo);
// excel表頭
List<String> headList = new ArrayList<String>(Arrays.asList("裝置編號","區域",
"終端裝置","硬體","異常原因","異常時間","狀態","終端機名稱"));
// 計算此次匯出需要多少個臨時的excle檔案
Integer num = allCount/ExcelExportUtils.EXCELSIZE;
if (allCount%ExcelExportUtils.EXCELSIZE != 0) {
num++;
}
// 儲存臨時excel的臨時資料夾路徑
String path = ExcelExportUtils.getTemExcelDirectory();
/**
* 第二步:多執行緒查詢並生成臨時的excel檔案
*/
threadSelectAndCreateTemExcel(hardwareMonitoringVo, response, ExcelExportUtils.EXCELSIZE, allCount, num, path);
/**
* 第三步:下載
*/
ExcelExportUtils.downloadTemExcel("硬體監控", response, path, num, ExcelExportUtils.EXCELSIZE, headList);
/**
* 第三步:刪除
*/
ExcelExportUtils.deleteDir(new File(path));
} catch (Exception e) {
e.printStackTrace();
LOGGER.info("【TerminalServiceImpl-exportHardwareMonitoring】方法異常,異常毫秒為:【"+System.currentTimeMillis()+"】");
}
LOGGER.info("結束【TerminalServiceImpl-exportHardwareMonitoring】方法,結束毫秒為:【"+System.currentTimeMillis()+"】");
}
/**
* <p>Discription:[多執行緒查詢並生成臨時的excel檔案]</p>
* Created on 2018年1月29日 下午2:13:19
* @param hardwareMonitoringVo 查詢條件的引數物件
* @param response response物件
* @param excelSize 每次查詢並生成一個臨時excel檔案的條數
* @param allCount 此次請求匯出的總行數
* @param num 此次請求需要多少個臨時的excle檔案
* @param path 儲存臨時excel檔案的臨時資料夾路徑
* @author:[全冉]
*/
private void threadSelectAndCreateTemExcel(HardwareMonitoringVo hardwareMonitoringVo, HttpServletResponse response,
Integer excelSize, Integer allCount, Integer num, String path) {
try {
LOGGER.info("進入【TerminalServiceImpl-threadSelectAndCreateTemExcel】方法,當前時間為:【"+System.currentTimeMillis()+"】");
// 生成一個執行緒計數器,每當一個執行緒的run方法完畢,num的值就減1
CountDownLatch latch = new CountDownLatch(num);
// 用多執行緒分批從資料庫讀取資料,每批都會生成一個臨時的excel,分了幾個批次就有幾個excel
for (int i = 0; i < num; i ++) {
hardwareMonitoringVo.setFromIndex(i*excelSize);
// 最後一個執行緒,查詢的條數需要計算
if (i == num -1) {
excelSize = allCount - (i-1)*excelSize;
}
hardwareMonitoringVo.setHowManyCount(excelSize);
// 每個執行緒都需要傳一個新物件,不然多執行緒操作的都是同一個結果集,結果不正確
HardwareMonitoringVo vo = new HardwareMonitoringVo();
vo.setOfficeBuildingId(hardwareMonitoringVo.getOfficeBuildingId());
vo.setTerminalDeviceType(hardwareMonitoringVo.getTerminalDeviceType());
vo.setFromIndex(hardwareMonitoringVo.getFromIndex());
vo.setHowManyCount(hardwareMonitoringVo.getHowManyCount());
HardwareMonitoringThread thread = new HardwareMonitoringThread(vo, hardwareMonitoringMapper, response, excelSize, path, latch);
thread.start();
}
// latch計數器的值不為0,則執行緒會阻塞,一直等著所有執行緒都跑完才會繼續向下執行
latch.await();
LOGGER.info("結束【TerminalServiceImpl-threadSelectAndCreateTemExcel】方法,當前時間為:【"+System.currentTimeMillis()+"】");
} catch (InterruptedException e) {
LOGGER.error(LogMessage.getNew()
.add("【TerminalServiceImpl-threadSelectAndCreateTemExcel】方法出現異常")
.add("===入參1====", hardwareMonitoringVo.toString())
.add("===入參2====", excelSize)
.add("===入參3====", allCount)
.add("===入參4====", num)
.add("===入參5====", path)
.add("===錯誤資訊====", e)
.add(e).toString());
LOGGER.info("【TerminalServiceImpl-threadSelectAndCreateTemExcel】方法出現異常,當前時間為:【"+System.currentTimeMillis()+"】");
}
}
}
4.HardwareMonitoringThread類package com.quanran.service.multithreading;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import javax.servlet.http.HttpServletResponse;
import com.quanran.dao.dto.ExportHardwareMonitoringVo;
import com.quanran.dao.dto.HardwareMonitoringVo;
import com.quanran.dao.mapper.HardwareMonitoringMapper;
import com.quanran.visitor.common.util.ExcelExportUtils;
/**
* <p>Description: [硬體監控的多執行緒類]</p>
* Created on 2018年2月2日 下午5:58:02
* @author <a href="mailto: [email protected]">全冉</a>
* @version 1.0
* Copyright (c) 2018 北京全冉有限公司
*/
public class HardwareMonitoringThread extends Thread {
/**
* 含有分批資訊的監控物件
*/
HardwareMonitoringVo hmVo;
/**
* 操作資料庫的mapper物件
*/
HardwareMonitoringMapper hardwareMonitoringMapper;
/**
* response物件
*/
HttpServletResponse response;
/**
* 每個批次查詢並生成一個臨時excel檔案的行數
*/
Integer excelSize;
/**
* 儲存臨時excel檔案的資料夾
*/
String path;
/**
* 執行緒計數器
*/
CountDownLatch latch;
/**
* <p>Discription:[建構函式:利用建構函式傳參]</p>
* @coustructor 方法.
*/
public HardwareMonitoringThread(HardwareMonitoringVo hardwareMonitoringVo, HardwareMonitoringMapper hardwareMonitoringMapper, HttpServletResponse response, Integer excelSize, String path, CountDownLatch latch) {
super();
this.hmVo = hardwareMonitoringVo;
this.hardwareMonitoringMapper = hardwareMonitoringMapper;
this.response = response;
this.excelSize = excelSize;
this.path = path;
this.latch = latch;
}
/**
* <p>Discription:[執行緒的具體操作]</p>
* Created on 2018年1月29日 下午5:10:06
* @author:[全冉]
*/
@Override
public void run() {
// 利用分頁limit實現分批,每個執行緒hmVo物件的fromIndex和howManyCount屬性值都不一樣
List<ExportHardwareMonitoringVo> everyQueryList = hardwareMonitoringMapper.selectWhere(hmVo);
// 儲存要匯出到臨時excel檔案的行資料
List<List<String>> recordList = new ArrayList<List<String>>();
if (null != everyQueryList && everyQueryList.size() > 0) {
for (int i = 0; i < everyQueryList.size(); i++) {
List<String> rowData = ExcelExportUtils.changList(everyQueryList.get(i),
"id", "officeBuildingName", "terminalDeviceTypeDescription",
"hardwareTypeDescription", "abnormalCause", "abnormalTime", "statusDescription", "terminalName");
recordList.add(rowData);
}
// 將當前執行緒查詢的資料匯入到臨時資料夾裡一個名為"temExcelFile"的excel檔案裡
ExcelExportUtils.writeExcelToTemDir("temExcelFile", path, recordList);
}
// 每個執行緒完畢,讓執行緒計數器的值減1
latch.countDown();
}
}
5.ExcelExportUtils類package com.quanran.visitor.common.util;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedWriter;
import java.io.ByteArrayInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.UUID;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import javax.servlet.http.HttpServletResponse;
import org.springframework.util.ClassUtils;
import org.springframework.web.util.HtmlUtils;
/**
* <p>Description: [大批量excel下載的工具類]</p>
* Created on 2018年2月2日 下午6:00:47
* @author <a href="mailto: [email protected]">全冉</a>
* @version 1.0
* Copyright (c) 2018 北京全冉有限公司
*/
public class ExcelExportUtils {
/**
* 多執行緒會併發生成臨時檔案的檔名,所以用同一物件加鎖控制
*/
final static String LOCKOBJECT = "lockObject";
/**
* 每次查詢並生成一個臨時excel檔案的行數
*/
public final static Integer EXCELSIZE = 30000;
/**
* 工作空間下的專案名稱
*/
final static String PROJECTNAME = "didi-visitor";
/**
* 此屬性值作為單檔案下載和多檔案打包下載的一個標準:即要下載的總資料條數大於此值,則進行多檔案打包下載;要是下載的總資料條數小於此值,則進行單檔案下載。
*/
final static Integer COUNT = 250000;
/**
* <p>Discription:[生成一個UUID]</p>
* Created on 2018年1月18日 下午7:54:11
* @return String 生成的UUID
* @author:[全冉]
*/
private static String getUUID() {
String uuid = UUID.randomUUID().toString();
//去掉“-”符號
return uuid.replaceAll("-", "");
}
/**
* <p>Discription:[不同的系統下,每次操作都生成一個臨時的資料夾]</p>
* Created on 2018年1月18日 下午7:56:56
* @return String 返回臨時資料夾的路徑
* @author:[全冉]
*/
public static String getTemExcelDirectory() {
String path = ""; // 臨時資料夾路徑
String sep = File.separator; // 平臺共用路徑間隔符
String linuxTemExcelDirectory = sep+"usr"+sep+"local"; // linux系統臨時excel的目錄
String pathSuffix = "temExcelFolder" + sep + getUUID();
String osName = System.getProperty("os.name");
if (osName.indexOf("Windows") >= 0 || osName.indexOf("window") >= 0 ) {
//windows系統走此
String sourcePath = ClassUtils.getDefaultClassLoader().getResource("").getPath().substring(1);
String[] sourcePathArray = sourcePath.split("/");
for (int i=0;i<sourcePathArray.length;i++) {
if (PROJECTNAME.equals(sourcePathArray[i])) {
path += pathSuffix;
break;
}
path += sourcePathArray[i]+sep;
}
} else {
//linux系統走此
path = linuxTemExcelDirectory + sep + pathSuffix;
}
return path;
}
/**
* <p>Discription:[每個執行緒都調此方法將資料匯入到臨時資料夾裡一個名為"temExcelFile_X"的excel檔案裡]</p>
* Created on 2018年1月19日 上午10:45:53
* @param fileName 檔名稱
* @param path 存臨時excel檔案的資料夾路徑
* @param recordList 要匯入臨時excel的資料
* @author:[全冉]
*/
public static void writeExcelToTemDir(String fileName, String path, List<List<String>> recordList) {
BufferedWriter buff = null;
try {
// 建立臨時excel檔案時,需要生成不同的名字,這塊程式碼可能併發執行,有可能存在多個執行緒同時操作同一個excel檔案,所以加鎖
synchronized (LOCKOBJECT) {
// 臨時資料夾路徑不存在就建立
File file = new File(path);
if (!file.exists()) {
file.mkdirs();
}
// 臨時資料夾下所有檔名字組成的字串陣列
String[] children = file.list();
String filePath = path + File.separator + fileName + "_" + (children.length+1) + ".xls";
System.out.println("檔名為:【"+fileName + "_" + (children.length+1) + ".xls"+"】");
OutputStreamWriter ow = new OutputStreamWriter(new FileOutputStream(filePath),"GBK");
// 生成檔案
buff = new BufferedWriter(ow);
}
// 臨時excel檔案裡的每一行資料
List<String> currentRecord = new ArrayList<String>();
StringBuffer currentSb = null;
for (Integer j = 0; j < recordList.size(); j ++) {
currentRecord = recordList.get(j);
currentSb = new StringBuffer();
// 迴圈,將一行資料的每個列都追加到一個可變字串上
for (int m = 0; m < currentRecord.size(); m ++) {
if (m == currentRecord.size()-1) {
currentSb.append(currentRecord.get(m));
} else {
currentSb.append(currentRecord.get(m)).append("\t");
}
}
// 往臨時excel裡寫入當前行的資料
buff.write(currentSb.toString());
// 往臨時excel裡寫入換行
buff.write("\r\n");
}
buff.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (buff != null) {
buff.close();
buff = null;
}
// 召喚jvm的垃圾回收器
System.gc();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* <p>Discription:[將臨時檔案從臨時檔案下載到本地]</p>
* Created on 2018年1月29日 下午6:58:18
* @param fileName 下載的檔名稱
* @param response response物件
* @param path 儲存臨時excel的臨時資料夾路徑
* @param num 臨時的excle檔案個數
* @param excelSize 臨時excel檔案的行數
* @param headList excel表頭
* @author:[全冉]
*/
public static void downloadTemExcel(String fileName, HttpServletResponse response, String path, Integer num, Integer excelSize, List<String> headList) {
File file = new File(path);
if (file.isDirectory()) {
String[] children = file.list();
// 判斷是單檔案下載還是多檔案打包下載
Integer allRecordCount = excelSize*children.length;
if (allRecordCount <= COUNT) {
// 單檔案下載(下載到本地是一個excel檔案)
singleFileDownload(fileName, path, children, response, headList);
}
if (allRecordCount > COUNT) {
// 多檔案打包下載
multiFileDownload(fileName, path, children, allRecordCount, COUNT, response, headList);
}
}
}
/**
* <p>Discription:[單檔案下載]</p>
* Created on 2018年1月29日 下午7:12:34
* @param fileName 下載的檔名稱
* @param path 儲存臨時excel的臨時資料夾路徑
* @param children path路徑下的所有臨時excel的名字拼成的字串陣列
* @param response response物件
* @param headList excel表頭
* @author:[全冉]
*/
private static void singleFileDownload(String fileName, String path, String[] children, HttpServletResponse response, List<String> headList) {
InputStream fis = null;
OutputStream os = null;
File outfile = null;
byte[] buffer = null;
try {
// 生成表頭
StringBuffer headSb = new StringBuffer();
for (int i = 0; i < headList.size(); i ++) {
if (i == headList.size()-1) {
headSb.append(headList.get(i)).append("\r\n");
} else {
headSb.append(headList.get(i)).append("\t");
}
}
byte[] headBuffer = headSb.toString().getBytes("GBK");
// 將表頭的位元組長度也加進到下載的檔案位元組長度裡
long countLength = headBuffer.length;
for (int i = 0; i < children.length; i ++) {
outfile = new File(path, children[i]);
countLength += outfile.length();
}
// 設定response物件,獲取response的輸出流
response.reset(); //重置結果集
response.addHeader("Content-Disposition", "attachment;filename=" + new String((fileName+".xls").getBytes("utf-8"), "iso8859-1")); //返回頭 檔名
response.addHeader("Content-Length", "" + countLength); //返回頭 檔案大小
response.setContentType("application/octet-stream"); //設定資料種類
os = new BufferedOutputStream(response.getOutputStream());
// 將表頭插入到excel中
fis = new BufferedInputStream(new ByteArrayInputStream(headBuffer));
fis.read(headBuffer); //讀取檔案流
os.write(headBuffer); // 輸出檔案
os.flush();
// 將每一個臨時excel都匯出
for (int i = 0; i < children.length; i ++) {
outfile = new File(path, children[i]);
fis = new BufferedInputStream(new FileInputStream(outfile));
buffer = new byte[fis.available()];
fis.read(buffer); //讀取檔案流
os.write(buffer); // 輸出檔案
os.flush();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (os != null) {
os.close();
os = null;
}
if (fis != null) {
fis.close();
fis = null;
}
// 召喚jvm的垃圾回收器
System.gc();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* <p>Discription:[多個檔案,打包下載]</p>
* Created on 2018年1月29日 下午7:26:21
* @param zipName 壓縮包名稱
* @param path 儲存臨時excel的臨時資料夾路徑
* @param children path路徑下的所有臨時excel的名字拼成的字串陣列
* @param allRecordCount 所有臨時excel檔案的行數之和
* @param count 下載到客戶端的excel最多能裝的記錄條數
* @param response response物件
* @param headList excel表頭
* @author:[全冉]
*/
private static void multiFileDownload(String zipName, String path, String[] children, Integer allRecordCount,
Integer count, HttpServletResponse response, List<String> headList) {
// 生成表頭
StringBuffer headSb = new StringBuffer();
for (int i = 0; i < headList.size(); i ++) {
if (i == headList.size()-1) {
headSb.append(headList.get(i)).append("\r\n");
} else {
headSb.append(headList.get(i)).append("\t");
}
}
// 計算下載到客戶端的zip包裡會有多少個excel檔案
Integer excelNum = allRecordCount/count;
if (allRecordCount%count != 0) {
excelNum++;
}
// 臨時檔案裡多少個excel生成一個zip包裡的excel
Integer temNum = children.length/excelNum;
// beforeList的值為往壓縮包裡放入新檔案的依據;afterList的值為壓縮包裡關閉一個新檔案流的依據
List<Integer> beforeList = new ArrayList<Integer>();
List<Integer> afterList = new ArrayList<Integer>();
for (int i = 0; i < children.length; i ++) {
if (i%temNum == 0) {
beforeList.add(i);
}
if (i != 0 && i%temNum == 0) {
afterList.add(i-1);
}
if (i == children.length-1) {
afterList.add(i);
}
}
ZipOutputStream zipos = null;
DataOutputStream os = null;
InputStream is = null;
try {
// 解決不同瀏覽器壓縮包名字含有中文時亂碼的問題
response.setContentType("APPLICATION/OCTET-STREAM");
response.setHeader("Content-Disposition", "attachment; filename=" + new String((zipName+".zip").getBytes("utf-8"), "iso8859-1"));
// 設定壓縮流:直接寫入response,實現邊壓縮邊下載
zipos = new ZipOutputStream(new BufferedOutputStream(response.getOutputStream()));
// 設定壓縮方式
zipos.setMethod(ZipOutputStream.DEFLATED);
// 壓縮包裡多個檔案的名字下標
Integer nameIndex = 1;
// 迴圈將檔案寫入壓縮流
for (int i = 0; i < children.length; i ++) {
// 新增ZipEntry物件到壓縮流中
if (beforeList.contains(i)) {
zipos.putNextEntry(new ZipEntry(zipName+"_"+nameIndex+".xls"));
nameIndex++;
// 表頭輸入到檔案中
os = new DataOutputStream(zipos);
is = new BufferedInputStream(new ByteArrayInputStream(headSb.toString().getBytes("GBK")));
byte[] b = new byte[100];
int length = 0;
while ((length = is.read(b)) != -1) {
os.write(b, 0, length);
}
is.close();
is = null;
}
// 生成當前File物件
File file = new File(path, children[i]);
// 將壓縮流寫入檔案流
os = new DataOutputStream(zipos);
is = new FileInputStream(file);
byte[] b = new byte[100];
int length = 0;
while ((length = is.read(b)) != -1) {
os.write(b, 0, length);
}
is.close();
is = null;
// 關閉當前Entry物件的壓縮流
if (afterList.contains(i)) {
zipos.closeEntry();
}
}
os.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (is != null) {
is.close();
is = null;
}
if (os != null) {
os.close();
os = null;
}
if (zipos != null) {
zipos.close();
zipos = null;
}
// 召喚jvm的垃圾回收器
System.gc();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* <p>Discription:[實體類裝換為字串List集合]</p>
* Created on 2018年1月19日 下午1:59:50
* @param obj Objec的子類
* @param propertyNames 多個屬性
* @return List<String> 返回的一行excel資料,均為String型別
* @author:[全冉]
*/
public static List<String> changList(Object obj, String... propertyNames) {
List<String> list = new ArrayList<String>();
String value = "";
Object object = null;
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
for(String propertyName : propertyNames){
try {
object = Reflections.invokeGetter(obj, propertyName);
if (object == null ) {
//value = "" ; nothing to do
} else if(object instanceof Date) {
value = sdf.format((Date) object);
} else if(object instanceof String) {
value = HtmlUtils.htmlUnescape(object.toString());
} else {
value = object.toString();
}
} catch (Exception e) {
throw new RuntimeException(e.getClass().getName()+":"+e.getMessage());
}
list.add(value);
object = null;
value = "";
}
return list;
}
/**
* <p>Discription:[遞迴刪除目錄下的所有子子孫孫檔案和檔案件,最後再刪除當前空目錄]</p>
* Created on 2018年1月17日 下午6:01:30
* @param dir 將要刪除的檔案目錄
* @return Boolean true:如果所有刪除成功,返回true
* false:如果某一個刪除失敗,後續的不在刪除,並且返回false
* @author:[全冉]
*/
public static Boolean deleteDir(File dir) {
if (dir.isDirectory()) {
String[] children = dir.list();
// 遞迴刪除目錄中的子目錄下
for (int i=0; i<children.length; i++) {
boolean success = deleteDir(new File(dir, children[i]));
if (!success) {
return false;
}
}
}
// 目錄此時為空,可以刪除
return dir.delete();
}
}
6.Reflections類package com.quanran.visitor.common.util;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.Assert;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.InvocationTargetException;
/**
* <p>Description: [反射工具類.
* 提供呼叫getter/setter方法, 訪問私有變數, 呼叫私有方法, 獲取泛型型別Class, 被AOP過的真實類等工具函式]</p>
* Created on 2015-04-17
* @author quanran
* @version 1.0
* Copyright (c) 2015 北京全冉有限公司
*/
@SuppressWarnings("rawtypes")
public class Reflections {
private static final String SETTER_PREFIX = "set";
private static final String GETTER_PREFIX = "get";
private static final String CGLIB_CLASS_SEPARATOR = "$$";
private static final Logger LOOGER = LoggerFactory.getLogger(Reflections.class);
/**
* <p>Discription:[呼叫Getter方法.
* 支援多級,如:物件名.物件名.方法]</p>
* Created on 2015-04-17
* @return object 物件
* @author:[quanran]
*/
public static Object invokeGetter(Object obj, String propertyName) {
Object object = obj;
for (String name : StringUtils.split(propertyName, ".")){
String getterMethodName = GETTER_PREFIX + StringUtils.capitalize(name);
object = invokeMethod(object, getterMethodName, new Class[] {}, new Object[] {});
}
return object;
}
/**
* <p>Discription:[呼叫Setter方法.
* 支援多級,如:物件名.物件名.方法]</p>
* Created on 2015-04-17
* @author:[quanran]
*/
public static void invokeSetter(Object obj, String propertyName, Object value) {
Object object = obj;
String[] names = StringUtils.split(propertyName, ".");
for (int i=0; i<names.length; i++){
if(i<names.length-1){
String getterMethodName = GETTER_PREFIX + StringUtils.capitalize(names[i]);
object = invokeMethod(object, getterMethodName, new Class[] {}, new Object[] {});
}else{
String setterMethodName = SETTER_PREFIX + StringUtils.capitalize(names[i]);
invokeMethodByName(object, setterMethodName, new Object[] { value });
}
}
}
/**
* <p>Discription:[直接讀取物件屬性值, 無視private/protected修飾符, 不經過getter函式]</p>
* Created on 2015-04-17
* @return object 物件
* @author:[quanran]
*/
public static Object getFieldValue(final Object obj, final String fieldName) {
Field field = getAccessibleField(obj, fieldName);
if (field == null) {
throw new IllegalArgumentException("Could not find field [" + fieldName + "] on target [" + obj + "]");
}
Object result = null;
try {
result = field.get(obj);
} catch (IllegalAccessException e) {
LOOGER.error("不可能丟擲的異常{}", e.getMessage());
}
return result;
}
/**
* <p>Discription:[直接設定物件屬性值, 無視private/protected修飾符, 不經過setter函式]</p>
* Created on 2015-04-17
* @author:[quanran]
*/
public static void setFieldValue(final Object obj, final String fieldName, final Object value) {
Field field = getAccessibleField(obj, fieldName);
if (field == null) {
throw new IllegalArgumentException("Could not find field [" + fieldName + "] on target [" + obj + "]");
}
try {
field.set(obj, value);
} catch (IllegalAccessException e) {
LOOGER.error("不可能丟擲的異常:{}", e.getMessage());
}
}
/**
* <p>Discription:[直接呼叫物件方法, 無視private/protected修飾符.
* 用於一次性呼叫的情況,否則應使用getAccessibleMethod()函式獲得Method後反覆呼叫.
* 同時匹配方法名+引數型別]</p>
* Created on 2015-04-17
* @return object 物件
* @author:[quanran]
*/
public static Object invokeMethod(final Object obj, final String methodName, final Class<?>[] parameterTypes,
final Object[] args) {
Method method = getAccessibleMethod(obj, methodName, parameterTypes);
if (method == null) {
throw new IllegalArgumentException("Could not find method [" + methodName + "] on target [" + obj + "]");
}
try {
return method.invoke(obj, args);
} catch (Exception e) {
throw convertReflectionExceptionToUnchecked(e);
}
}
/**
* <p>Discription:[直接呼叫物件方法, 無視private/protected修飾符,
* 用於一次性呼叫的情況,否則應使用getAccessibleMethodByName()函式獲得Method後反覆呼叫.
* 只匹配函式名,如果有多個同名函式呼叫第一個]</p>
* Created on 2015-04-17
* @return object 物件
* @author:[quanran]
*/
public static Object invokeMethodByName(final Object obj, final String methodName, final Object[] args) {
Method method = getAccessibleMethodByName(obj, methodName);
if (method == null) {
throw new IllegalArgumentException("Could not find method [" + methodName + "] on target [" + obj + "]");
}
try {
return method.invoke(obj, args);
} catch (Exception e) {
throw convertReflectionExceptionToUnchecked(e);
}
}
/**
* <p>Discription:[迴圈向上轉型, 獲取物件的DeclaredField, 並強制設定為可訪問.
*
* 如向上轉型到Object仍無法找到, 返回null]</p>
* Created on 2015-04-17
* @return object 物件
* @author:[quanran]
*/
public static Field getAccessibleField(final Object obj, final String fieldName) {
Validate.notNull(obj, "object can't be null");
Validate.notBlank(fieldName, "fieldName can't be blank");
for (Class<?> superClass = obj.getClass(); superClass != Object.class; superClass = superClass.getSuperclass()) {
try {
Field field = superClass.getDeclaredField(fieldName);
makeAccessible(field);
return field;
} catch (NoSuchFieldException e) {//NOSONAR
// Field不在當前類定義,繼續向上轉型
continue;// new add
}
}
return null;
}
/**
* <p>Discription:[迴圈向上轉型, 獲取物件的DeclaredMethod,並強制設定為可訪問.
* 如向上轉型到Object仍無法找到, 返回null.
* 匹配函式名+引數型別。
*
* 用於方法需要被多次呼叫的情況. 先使用本函式先取得Method,然後呼叫Method.invoke(Object obj, Object... args)]</p>
* Created on 2015-04-17
* @return object 物件
* @author:[quanran]
*/
public static Method getAccessibleMethod(final Object obj, final String methodName,
final Class<?>... parameterTypes) {
Validate.notNull(obj, "object can't be null");
Validate.notBlank(methodName, "methodName can't be blank");
for (Class<?> searchType = obj.getClass(); searchType != Object.class; searchType = searchType.getSuperclass()) {
try {
Method method = searchType.getDeclaredMethod(methodName, parameterTypes);
makeAccessible(method);
return method;
} catch (NoSuchMethodException e) {
// Method不在當前類定義,繼續向上轉型
continue;// new add
}
}
return null;
}
/**
* <p>Discription:[迴圈向上轉型, 獲取物件的DeclaredMethod,並強制設定為可訪問.
* 如向上轉型到Object仍無法找到, 返回null.
* 只匹配函式名。
*
* 用於方法需要被多次呼叫的情況. 先使用本函式先取得Method,然後呼叫Method.invoke(Object obj, Object... args)]</p>
* Created on 2015-04-17
* @return object 物件
* @author:[quanran]
*/
public static Method getAccessibleMethodByName(final Object obj, final String methodName) {
Validate.notNull(obj, "object can't be null");
Validate.notBlank(methodName, "methodName can't be blank");
for (Class<?> searchType = obj.getClass(); searchType != Object.class; searchType = searchType.getSuperclass()) {
Method[] methods = searchType.getDeclaredMethods();
for (Method method : methods) {
if (method.getName().equals(methodName)) {
makeAccessible(method);
return method;
}
}
}
return null;
}
/**
* <p>Discription:[改變private/protected的方法為public,儘量不呼叫實際改動的語句,避免JDK的SecurityManager抱怨]</p>
* Created on 2015-04-17
* @author:[quanran]
*/
public static void makeAccessible(Method method) {
if ((!Modifier.isPublic(method.getModifiers()) || !Modifier.isPublic(method.getDeclaringClass().getModifiers()))
&& !method.isAccessible()) {
method.setAccessible(true);
}
}
/**
* <p>Discription:[改變private/protected的成員變數為public,儘量不呼叫實際改動的語句,避免JDK的SecurityManager抱怨]</p>
* Created on 2015-04-17
* @author:[quanran]
*/
public static void makeAccessible(Field field) {
if ((!Modifier.isPublic(field.getModifiers()) || !Modifier.isPublic(field.getDeclaringClass().getModifiers()) || Modifier
.isFinal(field.getModifiers())) && !field.isAccessible()) {
field.setAccessible(true);
}
}
/**
* <p>Discription:[通過反射, 獲得Class定義中宣告的泛型引數的型別, 注意泛型必須定義在父類處
* 如無法找到, 返回Object.class.
* eg.
* public UserDao extends HibernateDao<User>]</p>
* Created on 2015-04-17
* @return object 物件
* @author:[quanran]
*/
@SuppressWarnings("unchecked")
public static <T> Class<T> getClassGenricType(final Class clazz) {
return getClassGenricType(clazz, 0);
}
/**
* <p>Discription:[通過反射, 獲得Class定義中宣告的父類的泛型引數的型別.
* 如無法找到, 返回Object.class.
*
* 如public UserDao extends HibernateDao<User,Long>]</p>
* Created on 2015-04-17
* @return class 類
* @author:[quanran]
*/
public static Class getClassGenricType(final Class clazz, final int index) {
Type genType = clazz.getGenericSuperclass();
if (!(genType instanceof ParameterizedType)) {
LOOGER.warn(clazz.getSimpleName() + "'s superclass not ParameterizedType");
return Object.class;
}
Type[] params = ((ParameterizedType) genType).getActualTypeArguments();
if (index >= params.length || index < 0) {
LOOGER.warn("Index: " + index + ", Size of " + clazz.getSimpleName() + "'s Parameterized Type: "
+ params.length);
return Object.class;
}
if (!(params[index] instanceof Class)) {
LOOGER.warn(clazz.getSimpleName() + " not set the actual class on superclass generic parameter");
return Object.class;
}
return (Class) params[index];
}
/**
*
* @param instance 例項
* @return Class 類
*/
public static Class<?> getUserClass(Object instance) {
Assert.notNull(instance, "Instance must not be null");
Class clazz = instance.getClass();
if (clazz != null && clazz.getName().contains(CGLIB_CLASS_SEPARATOR)) {
Class<?> superClass = clazz.getSuperclass();
if (superClass != null && !Object.class.equals(superClass)) {
return superClass;
}
}
return clazz;
}
/**
* <p>Discription:[將反射時的checked exception轉換為unchecked exception]</p>
* Created on 2015-04-17
* @return RuntimeException 異常
* @author:[quanran]
*/
public static RuntimeException convertReflectionExceptionToUnchecked(Exception e) {
if (e instanceof IllegalAccessException || e instanceof IllegalArgumentException
|| e instanceof NoSuchMethodException) {
return new IllegalArgumentException(e);
} else if (e instanceof InvocationTargetException) {
return new RuntimeException(((InvocationTargetException) e).getTargetException());
} else if (e instanceof RuntimeException) {
return (RuntimeException) e;
}
return new RuntimeException("Unexpected Checked Exception.", e);
}
}
相關推薦
大批量資料excel下載---本文作者只試了51萬資料的下載,用時7秒
一.背景: 現在的專案裡,有諸多下載功能,隨著資料越來越多,下載的時間也越來越長,很影響使用者體驗,為了解決這一問題,我不得不挺身而出,斬破難關。專案中原本用的是poi-HSSFWorkbook,但是如果是50萬資料量下載,回經歷一個漫長的等待過程,然後記憶體溢位。jxl也
Web應用環境下不同頁面之間的傳值(本文暫時只討論 form表單資料提交)
這是我個人在嘗試的多種頁面傳值後選擇的一個form表單傳值的需求。 一、需求背景: 本次開發遇到的是一個H5頁面分別為3個層次頁面,頁面A為資料輸出頁面,頁面B為資料確認頁面,頁面C為注意事項確認及最終確認申請頁面。 二、流程分析: 本人比較笨,所以直接用ProcessOn
【問題解決】Mybatis一對多/多對多查詢時只查出了一條資料
問題描述: 如果三表(包括了關係表)級聯查詢,主表和明細表的主鍵都是id的話,明細表的多條資料只能查詢出來第一條/最後一條資料。 三個表,許可權表(Permission),許可權組表(PermissionGroup),許可權組與許可權的關係表(P
myeclipse部署專案時只部署了WEB-INF資料夾,解決方法
找打專案下面setting資料夾下面的檔案: org.eclipse.wst.common.component 修改對應的 <wb-resource deploy-path="/" source-path="/webapp" tag="defaultRootSo
新需求只寫了一半遇到緊急BUG,需要git操作怎麼辦?
1.先用 $ git stash 把當前的工作現場 存放 起來 2.$ git stash save '備註資訊' //為這次暫存做個標記 3.git stash 可以多次操作,每次新的stash會固定放到[email protected]{0}位置,以此類
Java 匯出大批量資料excel(百萬級)(轉載)
參考資料:http://bbs.51cto.com/thread-1074293-1-1.html http://bbs.51cto.com/viewthread.php?tid=1074
C# 如何批量刪除Excel單元格中的公式只保留資料
在建立Excel報表時,我們經常會用到很多公式來計算資料,建立後出於保密性或其他一些原因,有些時候我們可能想刪除這些公式,只保留資料。這篇文章將介紹如何使用Free Spire.XLS元件和C#批量刪除Excel文件中的所有公式並保留資料。下面Excel文件工作表中D列的單元
將Excel資料快速大批量匯入資料庫的程式碼
兩種途徑將資料從EXCEL中匯入到SQL SERVER。 一、 在程式中,用ADO.NET。程式碼 如下: //連線串 string strConn = "Provider=Microsoft.Jet.OLEDB.4.0;Extended Properties=Excel 8.0;Data S
c# 大批量資料匯出到excel
技術要點:1.使用的是StreamWriter sw = new StreamWriter(path, false,Encoding.GetEncoding("gb2312"));最終生成檔案2.使用 StringBuilder sb = new StringBuild
超級噁心的大批量資料匯出到Excel
真無語了,咋那麼多客戶要匯出資料到Excel?還都動不動就好幾萬條到幾十萬?匯出了都看不看啊,真是倒黴催的。唉,牢騷一頓,進入正題。 業務功能比較簡單就是把資料庫內的資料,匯出到Excel檔案,檔案裡也沒有什麼修飾,比如顏色,底紋之類的啥都不帶,純資料檔案,客戶要拿這個檔案
根據資料批量生成excel檔案
第一步匯入依賴: <!--excel支援--> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-ooxml</artifactId
快速傳輸大批量資料(tar+lz4+pv+ssh)
快速傳輸大批量資料(tar+lz4+pv+ssh) 伺服器之間傳輸資料平時常使用的命令如scp、rsync這兩個,一些小的檔案或目錄這兩個命令足以搞定,但是一旦資料幾十上百G,這樣傳輸就有些慢了。 前兩天做遠端資料傳輸的時候,用scp命令始終感覺有點慢,就google了一下,發現了一
高德地圖大批量資料(上萬)畫歷史軌跡實現方案
轉載請註明出處:https://www.cnblogs.com/Joanna-Yan/p/9896180.html 需求:裝置傳回伺服器的軌跡點,需要在web地圖上顯示。包括畫座標點覆蓋物、軌跡路線圖。當資料量達到一定量時,介面出現卡頓。問題出現幾天前端人員都未解決。 第一反應,大量的覆蓋物肯
MySQL刪除大批量資料
1.刪除大表的部分資料 一個表有1億6000萬的資料,有一個自增ID。最大值就是1億6000萬,需要刪除大於250萬以後的資料,有什麼辦法可以快速刪除? 看到mysql文件有一種解決方案:http://dev.mysql.com/doc/refman/5.0/en/delete.html  
java實現大批量json檔案資料去重
上週從資料採集部門拿到一批400份的json檔案,每個檔案裡30w+的json物件,物件裡有uid,對重複的uid,需要去重下. 本人電腦4核8G已經不能滿足了,總是記憶體不夠用.所以在伺服器上寫了一下(配置8核128G) ,結果讀取檔案以及去重都沒問題, 在最後的寫入是又是
使用POI操作Excel修改模板(批量替換excel中的資料並判斷excel版本)
package com.sycamore.controller; import org.apache.poi.POIXMLDocument; import org.apache.poi.hssf.usermodel.HSSFCell; import org.apache.poi.hssf.userm
oracle 匯入大批量資料的一些坑
匯入 1.2g的dmp檔案 時候 發現報錯 oracle 的1658 意思 是 你表空間的容量 不夠了 有幾種可能性: 1: dbf檔案 所在的磁碟檔案不夠了 2: 表空間沒有設定自增 第一種情況 自行查詢 第二種情況 可以先用 SELECT FIL
使用POI操作Excel修改模板(批量替換excel中的資料)
使用POI操作Excel修改模板(批量替換excel中的資料) import org.apache.poi.hssf.usermodel.HSSFCell; import org.apache.poi.hssf.usermodel.HSSFRow; import org.apache
[樂意黎]MySQL使用事務及 PDO 插入大批量資料
<?php $serverIP = "127.0.0.1"; $userName= "user"; $passWord= "pass"; $dbname = "aerchi"; try { $pdo = new PDO("mysql:host=$serverIP;dbname=
Excel檔案一鍵上傳並解析完成資料批量匯入資料庫
原來做檔案上傳的時候,都是有一個輸入框,點選上傳按鈕,先瀏覽檔案,選擇檔案後,把檔案的路徑儲存到form表單中,最後通過form表單提交到服務端。這樣的介面不是很美觀。為了使用者有更好地體驗(UE),現在的大多數系統都是採用一鍵檔案上傳,使用者點選上傳按鈕,選擇