报表技术2(百万数据导入导出,POI操作word)
POI模板導出,操作word
- 導出用戶詳情數據(圖片,公式處理)
- 使用模板導出用戶詳細信息
- 使用模板引擎
- 1.編寫模板引擎
- 2.使用模板引擎
- 百萬數據導出
- 代碼實現:
- 百萬數據導入
- 步驟分析:
- 1.自定義處理器
- 2.自定義解析
- 3.測試
- CSV文件
- 導出csv文件:
- 讀取csv文件
- POI導出Word(.docx)
- API介紹
- 思路分析
- 代碼實現
Java-報表技術1-POI:https://blog.csdn.net/weixin_43994244/article/details/126227702
導出用戶詳情數據(圖片,公式處理)
模板樣式
使用模板導出用戶詳細信息
//日期格式化private SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");/*** 導出用戶詳細信息--使用模板* @param id* @param response* @throws Exception*/public void downLoadUserInfoByTemplate(Long id, HttpServletResponse response) throws Exception {//1.讀取模板//獲取項目的根目錄,創建目錄---獲取絕對路徑File rootPath = new File(ResourceUtils.getURL("classpath:").getPath());File templateFile = new File(rootPath.getAbsolutePath(), "/excel_template/userInfo.xlsx");//創建有模板的工作簿Workbook workbook = new XSSFWorkbook(templateFile);Sheet sheet = workbook.getSheetAt(0);//2.獲取用戶數據User user = userMapper.selectByPrimaryKey(id);//3.數據放入模板---存入哪一行哪個單元格//用戶名 第2行第2列sheet.getRow(1).getCell(1).setCellValue(user.getUserName());//手機號 第3行第2列sheet.getRow(2).getCell(1).setCellValue(user.getPhone());//生日 第4行第2列sheet.getRow(3).getCell(1).setCellValue(simpleDateFormat.format(user.getBirthday()));//工資 第5行第2列sheet.getRow(4).getCell(1).setCellValue(user.getSalary());//入職日期 第6行第2列sheet.getRow(5).getCell(1).setCellValue(simpleDateFormat.format(user.getHireDate()));//省份 第7行第2列sheet.getRow(6).getCell(1).setCellValue(user.getProvince());//城市 第7行第4列sheet.getRow(6).getCell(3).setCellValue(user.getCity());//現住址 第8行第1列---合并單元格sheet.getRow(7).getCell(1).setCellValue(user.getAddress());//司齡 第6行第4列---導出公式(入職時間到現在)// --填入excel的公式=CONCATENATE(DATEDIF(B6,TODAY(),"Y"),"年",DATEDIF(B6,TODAY(),"YM"),"個月")sheet.getRow(5).getCell(3).setCellFormula("CONCATENATE(DATEDIF(B6,TODAY(),\"Y\"),\"年\",DATEDIF(B6,TODAY(),\"YM\"),\"個月\")");//圖片處理開始//照片--第二行到第五行,第三列到第四列--合并單元格String userPhoto = user.getPhoto();//先創建一個字節輸出流ByteArrayOutputStream outputStream = new ByteArrayOutputStream();//優化,獲取拓展名,根據不同拓展名String extName = userPhoto.substring(userPhoto.lastIndexOf(".") + 1).toUpperCase();//讀取圖片,放入一個帶有緩沖區的圖片類中,主要作用是將一副圖片加載到內存中BufferedImage bufferedImage = ImageIO.read(new File(rootPath+user.getPhoto()));//把圖片寫入到字節輸出流中ImageIO.write(bufferedImage,extName,outputStream);//Patriarch圖片寫入Drawing patriarch = sheet.createDrawingPatriarch();//ClientAnchor指定圖片位置--后四起始列,起始行,結束列,結束行,前四個參數,左上角向x,y偏離多少,右下角向x,y偏離多少(偏移單位-1cm=36w) 不占滿表格ClientAnchor clientAnchor = new XSSFClientAnchor(0, 0, 0, 0, 2, 1, 4, 5);//優化int format = 0;switch (extName){case "JPG":{format = XSSFWorkbook.PICTURE_TYPE_JPEG;} case "JPEG":{format = XSSFWorkbook.PICTURE_TYPE_JPEG;} case "PNG":{format = XSSFWorkbook.PICTURE_TYPE_PNG;}}//開始把圖片寫入到sheet指定的位置patriarch.createPicture(clientAnchor,workbook.addPicture(outputStream.toByteArray(),format));//圖片處理結束//4.下載//一個流兩個頭String fileName = "員工("+user.getUserName()+")詳細數據.xlsx";response.setHeader("content-disposition","attachment;filename="+new String(fileName.getBytes(),"ISO8859-1"));response.setContentType("application/vnd.ms-excel");response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");//執行workbook.write(response.getOutputStream());}使用模板引擎
前提:導出代碼時,必須要提前知道要導出數據在哪一行哪一個單元格,但是模板一旦發生調整,java代碼也要修改,所以可以自定義引擎,有了引擎即使模板修改了java代碼也不用修改。
思路:在制作模板時,在需要插入數據的位置做上標記,在導出時,對象的屬性和標記做對應,如果對應匹配一樣,就把值賦值到相應的位置。
模板:
工具類: bean和map轉換
可以借用hutool的BeanUtil里的方法:
1.編寫模板引擎
public class ExcelExportEngine {private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");public static Workbook writeToExcel(Object object,Workbook workbook,String photoPath) throws Exception {//把bean轉成mapMap<String, Object> map = EntityUtils.entityToMap(object);Sheet sheet = workbook.getSheetAt(0);Row row = null;Cell cell = null;//循環100行,每一行循環100個單元格---定位到具體的cellfor (int i = 0; i < 10; i++) {row = sheet.getRow(i);if(row == null){break;}else {for (int j = 0; j < 10; j++) {cell = row.getCell(j);if(cell != null){//比較單元格中的值,是否和map中的key一致,如果一致向單元格中放入map這個key對應的值writeToCell(cell,map);}}}}//處理圖片if(StringUtils.isNotBlank(photoPath)){//File rootPath = new File(ResourceUtils.getURL("classpath:").getPath()); //SpringBoot項目獲取根目錄的方式ByteArrayOutputStream byteArrayOut = new ByteArrayOutputStream();//BufferedImage是一個帶緩沖區圖像類,主要作用是將一幅圖片加載到內存中BufferedImage bufferImg = ImageIO.read(new File(photoPath));ImageIO.write(bufferImg, "jpg", byteArrayOut);Drawing patriarch = sheet.createDrawingPatriarch();//獲取模板圖片位置Sheet sheet2 = workbook.getSheetAt(1);row = sheet2.getRow(0);int col1 = ((Double) row.getCell(0).getNumericCellValue()).intValue();int row1 = ((Double) row.getCell(1).getNumericCellValue()).intValue();int col2 = ((Double) row.getCell(2).getNumericCellValue()).intValue();int row2 = ((Double) row.getCell(3).getNumericCellValue()).intValue();//錨點,固定點ClientAnchor anchor = new XSSFClientAnchor(0, 0, 0, 0, col1, row1, col2, row2);patriarch.createPicture(anchor, workbook.addPicture(byteArrayOut.toByteArray(), Workbook.PICTURE_TYPE_JPEG));//刪除第二個sheetworkbook.removeSheetAt(1);}return workbook;}//比較單元格中的值,是否和map中的key一致,如果一致向單元格中放入map這個key對應的值private static void writeToCell(Cell cell, Map<String, Object> map) {CellType cellType = cell.getCellType();//公式的話不處理switch(cellType){case FORMULA:{break;} default:{String cellValue = cell.getStringCellValue();if(StringUtils.isNotBlank(cellValue)){//System.out.println("獲取到的cellValue"+cellValue);for (String key : map.keySet()){if(key.equals(cellValue)){cell.setCellValue(map.get(key).toString());//System.out.println("獲取到的value:"+map.get(key).toString());}}}}}} }2.使用模板引擎
/*** 導出用戶詳細信息--使用模板引擎* @param id* @param response* @throws Exception*/public void downLoadUserInfoByTemplate2(Long id, HttpServletResponse response) throws Exception {//1.讀取模板//獲取項目的根目錄,創建目錄---獲取絕對路徑File rootPath = new File(ResourceUtils.getURL("classpath:").getPath());File templateFile = new File(rootPath.getAbsolutePath(), "/excel_template/userInfo2.xlsx");//創建有模板的工作簿Workbook workbook = new XSSFWorkbook(templateFile);Sheet sheet = workbook.getSheetAt(0);//2.獲取用戶數據User user = userMapper.selectByPrimaryKey(id);System.out.println("獲取到的用戶信息"+user.toString());//3.通過自定義引擎放入數據--圖片的絕對路徑workbook = ExcelExportEngine.writeToExcel(user, workbook,rootPath.getPath()+user.getPhoto());patriarch.createPicture(clientAnchor,workbook.addPicture(outputStream.toByteArray(),format));//圖片處理結束//4.下載//一個流兩個頭String fileName = "員工("+user.getUserName()+")詳細數據.xlsx";response.setHeader("content-disposition","attachment;filename="+new String(fileName.getBytes(),"ISO8859-1"));//xls//response.setContentType("application/vnd.ms-excel");//xlsxresponse.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");//執行workbook.write(response.getOutputStream());}百萬數據導出
用戶模式: 用戶模式有許多封裝好的方法操作簡單,但創建太多的對象,非常耗內存(之前使用的方法)
事件模式: 基于SAX方式解析XML,SAX全稱Simple API for XML,它是一個接口,也是一個軟件包。它是一種XML解析的替代方法,不同于DOM解析XML文檔時把所有內容一次性加載到內存中的方式,它逐行掃描文檔,一邊掃描,一邊解析。
SXSSF對象:是用來生成海量excel數據文件,主要原理是借助臨時存儲空間生成excel
弊端:
1、不能使用模板
2、不能使用太多的樣式
實現方式:
1.dom4j:一次性加載xml文件再解析
2.SAX:逐行加載,逐行解析
3.SXSSWorkBook:一旦內存中的對象的個數達到指定值時,就將內存中的這些對象的內容寫入到磁盤中,就可以將這些對象從內存中銷毀,直至excel導出完成。
代碼實現:
public void downLoadMillion(HttpServletResponse response) throws Exception {//使用sax方式解析Workbook workbook = new SXSSFWorkbook();//導出500w條數據,一個sheet只能放100w行//記錄了處理了多少條數據int number = 0;//分頁查詢int page = 1;//記錄row的行索引int rowIndex = 1;//創建行Row row = null;Sheet sheet = null;while (true) {List<User> userList = this.findPage(page, 1000000);if (CollectionUtils.isEmpty(userList)) {break;//數據為空不再查詢}if (number % 1000000 == 0) {//表示應該創建新的標題sheet = workbook.createSheet("第" + (number/1000000)+ "個sheet表");//重置rowIndexrowIndex = 1;//設置列寬sheet.setColumnWidth(0,8*256);sheet.setColumnWidth(1,12*256);sheet.setColumnWidth(2,15*256);sheet.setColumnWidth(3,15*256);sheet.setColumnWidth(4,30*256);//設置小標題String[] title = new String[]{"編號", "姓名", "手機號", "入職日期", "現住址"};//創建標題行Row titleRow = sheet.createRow(0);for (int i = 0; i < 5; i++) {titleRow.createCell(i).setCellValue(title[i]);}}//向excel中添加內容for (User user : userList) {row = sheet.createRow(rowIndex);row.createCell(0).setCellValue(user.getId());row.createCell(1).setCellValue(user.getUserName());row.createCell(2).setCellValue(user.getPhone());row.createCell(3).setCellValue(simpleDateFormat.format(user.getHireDate()));row.createCell(4).setCellValue(user.getAddress());rowIndex++;number++;}page++;//下一頁}//4.下載//一個流兩個頭String fileName = "百萬數據導出.xlsx";response.setHeader("content-disposition","attachment;filename="+new String(fileName.getBytes(),"ISO8859-1"));response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");//執行workbook.write(response.getOutputStream());}百萬數據導入
用戶模式: 加載并讀取Excel時,是通過一次性的將所有數據加載到內存中再去解析每個單元格內容。當Excel數據量較大時,由于不同的運行環境可能會造成內存不足甚至OOM異常。
事件模式: 它逐行掃描文檔,一邊掃描一邊解析。由于應用程序只是在讀取數據時檢查數據,因此不需要將數據存儲在內存中,這對于大型文檔的解析是個巨大優勢。
步驟分析:
1.設置POI的事件模式
- 根據Excel獲取文件流
- 根據文件流創建OPCPackage ,用來組合讀取到的xml 組合出來的數據占用的空間更小
- 創建XSSFReader對象
2.Sax解析
- 自定義Sheet處理器
- 創建Sax的XmlReader對象
- 設置Sheet的事件處理器
- 逐行讀取
1.自定義處理器
package com.itheima.test;import com.itheima.pojo.User; import org.apache.poi.xssf.eventusermodel.XSSFSheetXMLHandler; import org.apache.poi.xssf.usermodel.XSSFComment;public class SheetHandler implements XSSFSheetXMLHandler.SheetContentsHandler {// 編號 用戶名 手機號 入職日期 現住址private User user=null;@Overridepublic void startRow(int rowIndex) { //每一行的開始 rowIndex代表的是每一個sheet的行索引if(rowIndex==0){user = null;}else{user = new User();}}@Override //處理每一行的所有單元格public void cell(String cellName, String cellValue, XSSFComment comment) {if(user!=null){String letter = cellName.substring(0, 1); //每個單元名稱的首字母 A B Cswitch (letter){case "A":{user.setId(Long.parseLong(cellValue));break;}case "B":{user.setUserName(cellValue);break;}}}}@Overridepublic void endRow(int rowIndex) { //每一行的結束if(rowIndex!=0){System.out.println(user);}} }2.自定義解析
package com.itheima.test;import org.apache.poi.openxml4j.opc.OPCPackage; import org.apache.poi.openxml4j.opc.PackageAccess; import org.apache.poi.xssf.eventusermodel.XSSFReader; import org.apache.poi.xssf.eventusermodel.XSSFSheetXMLHandler; import org.apache.poi.xssf.model.SharedStringsTable; import org.apache.poi.xssf.model.StylesTable; import org.xml.sax.InputSource; import org.xml.sax.XMLReader; import org.xml.sax.helpers.XMLReaderFactory;import java.io.InputStream;/*** 自定義Excel解析器*/ public class ExcelParser {public void parse (String path) throws Exception {//1.根據Excel獲取OPCPackage對象OPCPackage pkg = OPCPackage.open(path, PackageAccess.READ);try {//2.創建XSSFReader對象XSSFReader reader = new XSSFReader(pkg);//3.獲取SharedStringsTable對象SharedStringsTable sst = reader.getSharedStringsTable();//4.獲取StylesTable對象StylesTable styles = reader.getStylesTable();XMLReader parser = XMLReaderFactory.createXMLReader();// 處理公共屬性:Sheet名,Sheet合并單元格parser.setContentHandler(new XSSFSheetXMLHandler(styles,sst, new SheetHandler(), false));XSSFReader.SheetIterator sheets = (XSSFReader.SheetIterator) reader.getSheetsData();while (sheets.hasNext()) {InputStream sheetstream = sheets.next();InputSource sheetSource = new InputSource(sheetstream);try {parser.parse(sheetSource);} finally {sheetstream.close();}}} finally {pkg.close();}} }3.測試
用戶模式下讀取測試Excel文件直接內存溢出,測試Excel文件映射到內存中還是占用了不少內存;事件模式下可以流暢的運行。
public class POIDemo5 {public static void main(String[] args) throws Exception{new ExcelParser().parse("C:\\Users\\syl\\Desktop\\百萬用戶數據的導出.xlsx");} }CSV文件
CSV文件:Comma-Separated Values,中文叫逗號分隔值或者字符分割值,其文件以純文本的形式存儲表格數據。該文件是一個字符序列,可以由任意數目的記錄組成,記錄間以某種換行符分割。每條記錄由字段組成,字段間的分隔符是其他字符或者字符串。所有的記錄都有完全相同的字段序列,相當于一個結構化表的純文本形式。
用文本文件、excel或者類似與文本文件的編輯器都可以打開CSV文件。
使用opencsv類庫來導出csv文
依賴:
opencsv常用API:
寫入到csv文件會用到CSVWriter對象,創建此對象常見API如下:
CSVWriter(Writer writer);
CSVWriter(Writer writer, char separator);
CSVWriter(Writer writer, char separator, char quotechar);
構造器涉及到的三個參數:
使用CSVWriter對象寫入數據常用的方法如下:
writerAll(List<String[]> allLines);
writerNext(String… nextLine);
讀取csv文件會用到CSVReader對象,創建此對象常見API如下:
CSVReader(Reader reader)
CSVReader(Reader reader, char separator)
CSVReader(Reader reader, char separator, char quotechar)
導出csv文件:
/*** 導出百萬數據到CSV文件* @param response* @throws Exception*/public void downLoadCSV(HttpServletResponse response) throws Exception {ServletOutputStream outputStream = response.getOutputStream();//導出String fileName = "csv百萬數據導出.csv";response.setHeader("content-disposition","attachment;filename="+new String(fileName.getBytes(),"ISO8859-1"));response.setContentType("text/csv");//寫文件CSVWriter csvWriter = new CSVWriter(new OutputStreamWriter(outputStream,"UTF-8"));//寫第一行//設置小標題String[] title = new String[]{"編號", "姓名", "手機號", "入職日期", "現住址"};csvWriter.writeNext(title);int page = 1;while (true) {List<User> userList = this.findPage(1, 2000000);if(CollectionUtils.isEmpty(userList)){break;}for(User user : userList){csvWriter.writeNext(new String[]{user.getId().toString(),user.getUserName(),user.getPhone(),simpleDateFormat.format(user.getHireDate()),user.getAddress()});}page++;csvWriter.flush();}csvWriter.close();}讀取csv文件
import com.opencsv.CSVReader;import java.io.FileReader; import java.text.SimpleDateFormat; import java.time.Year; import java.util.List;//讀取百萬級數據的csv文件 public class CsvDemo {`在這里插入代碼片`private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");public static void main(String[] args) throws Exception {CSVReader csvReader = new CSVReader(new FileReader("d:\\百萬用戶數據的導出.csv"));String[] titles = csvReader.readNext(); //讀取到第一行 是小標題 // "編號","姓名","手機號","入職日期","現住址"User user = null;while (true){user = new User();String[] content = csvReader.readNext();if(content==null){break;}user.setId(Long.parseLong(content[0]));user.setUserName(content[1]);user.setPhone(content[2]);user.setHireDate(simpleDateFormat.parse(content[3]));user.setAddress(content[4]);System.out.println(user);}} }POI導出Word(.docx)
API介紹
XWPFDocument代表一個docx文檔,其可以用來讀docx文檔,也可以用來寫docx文檔
一個文檔包含多個段落,一個段落包含多個Runs文本,一個Runs包含多個Run,Run是文檔的最小單元
獲取所有段落:List paragraphs = word.getParagraphs();
獲取一個段落中的所有片段Runs:List xwpfRuns = xwpfParagraph.getRuns();
獲取一個Runs中的一個Run:XWPFRun run = xwpfRuns.get(index);
2、poi操作word中的表格
一個文檔包含多個表格,一個表格包含多行,一行包含多列單元格
獲取所有表格:List xwpfTables = doc.getTables();
獲取一個表格中的所有行:List xwpfTableRows = xwpfTable.getRows();
獲取一行中的所有列:List xwpfTableCells = xwpfTableRow.getTableCells();
獲取一格里的內容:List paragraphs = xwpfTableCell.getParagraphs();
之后和正文段落一樣
思路分析
制作一個word模板,把動態的內容先寫特殊字符然后替換,表格自己創建然后向表格中放內容
代碼實現
1.準備方法:一個是想指定的單元格中放入圖片,另一個是 復制word中表格的行
/*** 向單元格寫入圖片*/private void setCellImage(XWPFTableCell cell, File imageFile) {XWPFRun run = cell.getParagraphs().get(0).createRun();//流會自動關閉try(FileInputStream inputStream = new FileInputStream(imageFile)) {//輸入流,圖片類型,文件名,高度,寬度(圖片擴大倍數)run.addPicture(inputStream,XWPFDocument.PICTURE_TYPE_JPEG,imageFile.getName(), Units.toEMU(100), Units.toEMU(50));} catch (Exception e) {e.printStackTrace();}}/*** 用戶深復制行*/private void copyRow(XWPFTable table, XWPFTableRow sourceRow, int rowIndex) {XWPFTableRow targetRow = table.insertNewTableRow(rowIndex);//設置行屬性targetRow.getCtRow().setTrPr(sourceRow.getCtRow().getTrPr());//獲取源行的單元格--設置單元格屬性List<XWPFTableCell> cells = sourceRow.getTableCells();//if(CollectionUtils.isEmpty(cells)){return;}XWPFTableCell targetCell = null;for (XWPFTableCell cell : cells) {targetCell = targetRow.addNewTableCell();//附上單元格的樣式//單元格的樣式targetCell.getCTTc().setTcPr(cell.getCTTc().getTcPr());//單元個中段落屬性targetCell.getParagraphs().get(0).getCTP().setPPr(cell.getParagraphs().get(0).getCTP().getPPr());}}2.下載用戶合同
/*** 下載用戶合同* @param id*/public void downloadContract(Long id,HttpServletResponse response) throws Exception {//1.讀取到模板//獲取項目的根目錄,創建目錄---獲取絕對路徑File rootPath = new File(ResourceUtils.getURL("classpath:").getPath());File templateFile = new File(rootPath.getAbsolutePath(), "word_template/contract_template.docx");XWPFDocument document = new XWPFDocument(new FileInputStream(templateFile));//2.查詢當前用戶--->map方便操作User user = this.findById(id);HashMap<String, String> hashMap = new HashMap<>();hashMap.put("userName",user.getUserName());hashMap.put("hireDate",simpleDateFormat.format(user.getHireDate()));hashMap.put("address",user.getAddress());//3.1.替換數據--處理正文List<XWPFParagraph> paragraphs = document.getParagraphs();for (XWPFParagraph paragraph : paragraphs) {List<XWPFRun> runs = paragraph.getRuns();for (XWPFRun run : runs) {String text = run.getText(0);//循環遍歷是否包含map的keyfor (String key : hashMap.keySet()) {if(text.contains(key)){//如果包含,替換掉run.setText(text.replaceAll(key,hashMap.get(key)),0);}}}}//3.2.表格數據List<Resource> resources = user.getResourceList();//獲取表格XWPFTable table = document.getTables().get(0);//獲取模板的第一行XWPFTableRow row = table.getRow(0);//存放東西,向表格中添加行int rowIndex = 1;for (Resource resource : resources) {//添加行//table.addRow(row);copyRow(table,row,rowIndex);XWPFTableRow tableRow = table.getRow(rowIndex);tableRow.getCell(0).setText(resource.getName());tableRow.getCell(1).setText(resource.getPrice().toString());tableRow.getCell(2).setText(resource.getNeedReturn()?"需要":"不需要");//照片處理File imageFile = new File(rootPath, "/static"+resource.getPhoto());setCellImage(tableRow.getCell(3),imageFile);rowIndex++;}//4.導出wordString fileName = "員工"+user.getUserName()+"合同.docx";response.setHeader("content-disposition","attachment;filename="+new String(fileName.getBytes(),"ISO8859-1"));response.setContentType("application/vnd.openxmlformats-officedocument.wordprocessingml.document");document.write(response.getOutputStream());}總結
以上是生活随笔為你收集整理的报表技术2(百万数据导入导出,POI操作word)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: js判断当前是pc端、移动端、IE浏览器
- 下一篇: 小红书种草模式有哪些?如何保证种草效果