대용량 Excel 파일 읽기(50w+ 지원) (3)

대용량 Excel 파일 읽기(50w+ 지원)


1 온라인 메모리 넘침 문제 프레젠테이션


환경 준비
  • 큰 excel 파일 준비(xlsx 크기 10M 이상)
  • jvm의 heap를 500m로 축소(JVM 매개변수-Xmx500m) 아날로그 OOM
  • 에 사용
  • 매개변수를 사용하여 OOM에서 dump 메모리 스냅샷-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=d://dump.hprof
  • 의존 관리
    		
    		<dependency>
    			<groupId>org.apache.poigroupId>
    			<artifactId>poi-ooxmlartifactId>
    			<version>4.1.0version>
    		dependency>
    
    		
    		<dependency>
    			<groupId>xercesgroupId>
    			<artifactId>xercesImplartifactId>
    			<version>2.11.0version>
    		dependency>
    

    실행 코드
    /**
     *  excel 
     *
     * @author Leon
     * @version 2020/5/5 20:07
     */
    public class ReadExcelDemo
    {
    	public static void main(String[] args) throws Exception
    	{
    		FileInputStream is = new FileInputStream("d:\\test.xlsx");
    		XSSFWorkbook wb = new XSSFWorkbook(is);
    		// TODO with wb
    		System.out.println("ok");
    	}
    }
    

    실행 결과
    java.lang.OutOfMemoryError: Java heap space
    Dumping heap to d://dump.hprof ...
    Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    	at java.io.ByteArrayOutputStream.(ByteArrayOutputStream.java:77)
    	at org.apache.poi.util.IOUtils.toByteArray(IOUtils.java:147)
    	at org.apache.poi.util.IOUtils.toByteArray(IOUtils.java:121)
    	at org.apache.poi.openxml4j.util.ZipArchiveFakeEntry.(ZipArchiveFakeEntry.java:47)
    	at org.apache.poi.openxml4j.util.ZipInputStreamZipEntrySource.(ZipInputStreamZipEntrySource.java:53)
    	at org.apache.poi.openxml4j.opc.ZipPackage.(ZipPackage.java:106)
    	at org.apache.poi.openxml4j.opc.OPCPackage.open(OPCPackage.java:301)
    	at org.apache.poi.ooxml.util.PackageHelper.open(PackageHelper.java:37)
    	at org.apache.poi.xssf.usermodel.XSSFWorkbook.(XSSFWorkbook.java:303)
    	at com.concurrent.excel.ReadExcelDemo.main(ReadExcelDemo.java:20)
    

    dump.hprof 파일 분석
    jdk 자체 jvisualvm 를 사용하여 이 파일을 엽니다.다음 그림:
    [외부 체인 이미지 저장 실패, 원본 사이트에 도난 방지 체인 메커니즘이 있을 수 있으므로 그림을 저장하여 직접 업로드하는 것을 권장합니다(img-arAxXn4Z-1588769902970)(./asserts/001.png)]
    원인 요약: xlsx를 처리할 때 데이터를 메모리에 완전히 읽어서 메모리가 넘칩니다.필자의 경험에 의하면 10m의 excel이 백그라운드에서 완전히 처리되는 상황은 적어도 2GB 메모리가 필요하다. 이 확대의 배수는 매우 무섭다. 어쩐지 메모리가 넘치더라니.

    2 작은 메모리로 초대형 excel 데이터 읽기 실현


    지난 소절에서 우리가 OOM에 포지셔닝된 이유는 이런 상황에 대해 POI 정부도 해결 방안을 제시했다.이벤트 구동 모드(Event API)입니다.
  • 홈페이지 관련 링크: POI 홈페이지 링크 및 예시 코드
  • 홈페이지 관련 링크: POI issue 목록 14번째
  • 예제 코드
    이 코드는 직접 복사해서 사용하거나 조금만 개조하면 프로젝트에서 사용할 수 있다.
    
    /**
     *  : excel 
     *
     * @author Leon
     * @version 2020/5/5 20:07
     */
    public class EventReadExcelDemo
    {
    	public static void main(String[] args) throws Exception
    	{
    		new EventReadExcelDemo().processOneSheet("d:\\test.xlsx");
    		System.out.println("ok");
    	}
    
    	public void processOneSheet(String filename) throws Exception {
    		OPCPackage pkg = OPCPackage.open(filename);
    		XSSFReader r = new XSSFReader(pkg);
    		SharedStringsTable sst = r.getSharedStringsTable();
    
    		XMLReader parser = fetchSheetParser(sst);
    
    		//  sheet
    		InputStream sheet2 = r.getSheet("rId1");
    		InputSource sheetSource = new InputSource(sheet2);
    		parser.parse(sheetSource);
    		sheet2.close();
    	}
    
    	public XMLReader fetchSheetParser(SharedStringsTable sst) throws SAXException {
    		XMLReader parser =
    			XMLReaderFactory.createXMLReader(
    				"org.apache.xerces.parsers.SAXParser"
    			);
    		ContentHandler handler = new SheetHandler(sst);
    		parser.setContentHandler(handler);
    		return parser;
    	}
    
    	private static class SheetHandler extends DefaultHandler {
    		private SharedStringsTable sst;
    		private String lastContents;
    		private boolean nextIsString;
    
    		private SheetHandler(SharedStringsTable sst) {
    			this.sst = sst;
    		}
    
    		// handler
    		public void startElement(String uri, String localName, String name,
    		                         Attributes attributes) throws SAXException {
    			// c =>  
    			if(name.equals("c")) {
    				System.out.print(attributes.getValue("r") + " - ");
    				//  
    				String cellType = attributes.getValue("t");
    				if(cellType != null && cellType.equals("s")) {
    					nextIsString = true;
    				} else {
    					nextIsString = false;
    				}
    			}
    			lastContents = "";
    		}
    
    		// handler
    		public void endElement(String uri, String localName, String name)
    			throws SAXException {
    			if(nextIsString) {
    				int idx = Integer.parseInt(lastContents);
    				lastContents = new XSSFRichTextString(sst.getEntryAt(idx)).toString();
    				nextIsString = false;
    			}
    
    			// v =>  
    			if(name.equals("v")) {
    				System.out.println(lastContents);
    			}
    		}
    
    		// handler
    		public void characters(char[] ch, int start, int length)
    			throws SAXException {
    			lastContents += new String(ch, start, length);
    		}
    	}
    }
    
    

    실행 결과
    ....
    E153797 - A153798 - xxxxx 
    B153798 - 2953
    C153798 -  
    D153798 -  
    ...
    

    우리는 15만 줄까지 운행했고 메모리 넘침도 발생하지 않았다는 것을 발견했다.

    3 매듭


    같은 방법으로 흐르는 xlsx 파일을 읽을 수 있지만 내부 데이터만 읽을 수 있고 수정 작업을 할 수 없습니다.
    나중에 큰 파일을 쓰는 방법을 소개할 거예요.

    좋은 웹페이지 즐겨찾기