일/Spring2018. 5. 3. 13:50

웹 어플리케이션 개발에서 대용량 엑셀 다운로드는 항상 골칫거리이다.

한 10만 레코드 정도만 받으려고 해도

1) 일단 레코드를 얻어서 객체로 들고 있는 단계에서 OutOfMemory가 나기도 하고

2) 이걸 간신히 객체로 만든 후에 POI 나 JXL 라이브러리로 Workbook을 생성해 

    데이터를 채워나가다가 OutOfMemory가 나기도 한다. (같은 데이터가 중복되는 셈이니 2배가 되어)

3) 게다가 메모리에 담고 있는 걸 요청자에게 송신하는 시간동안 꼼짝없이 메모리를 잡고 있게 된다.

 

대용량 컨텐츠를 보낼 때는 조금씩 끊어 보내면서 flush를 할 수 있어야 하는데 이게 안되는 게 문제.

 

그래서 보통 다음과 같이 하나하나씩 해결해 나간다.

- 위의 3)번은 일단 파일로 저장하고 이 파일을 다운로드 시켜주는 방식이 가능하다.

- 위의 2)번의 경우 Workbook을 생성해 Row를 채우는 데 쓰인 객체는 해제시켜줄 수 있겠다.

- 아예 Workbook을 생성하지 않고 HTML 로 보내면서 Microsoft Excel 어플리케이션이 열 수 있게 

  응답헤더만 조정하는 방법도 있다.

- 내게 시키는 고객과 잘 협의가 가능하다면 CSV (Comma Separated Values) 포맷으로 다운로드도 

  가능할 것이다. 고객 만족도는 엄청 떨어지겠지만.

 

그런데 이것들을 제대로 해결해 주는 녀석이 등장.

 

Apache POI 3.8-beta3 (2011년 6월) 버전부터 임시파일을 활용하여 메모리 사용량을 최저로 유지하는 기능이 추가되었다.

(Excel 2007부터 지원되는 xslx, 혹은 ooxml 포맷만 가능)

 

확인을 위해 테스트 소스를 작성해 보았다.

 

pom.xml 을 다음과 같이 구성하여 apache-poi를 사용 가능하게 한다.

 

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0http://maven.apache.org/xsd/maven-4.0.0.xsd">

  <modelVersion>4.0.0</modelVersion>

  <groupId>test</groupId>

  <artifactId>test-poi-memory</artifactId>

  <version>0.0.1-SNAPSHOT</version>

  <name>test-poi-memory</name>

  <dependencies>

   <dependency>

   <groupId>org.apache.poi</groupId>

   <artifactId>poi-ooxml</artifactId>

   <version>3.10-beta2</version>

   </dependency>

  </dependencies>

</project>

 

 

다음과 같이 소스를 짜 보았다.

 

public class ExcelWriteTest {

 

    public static void main(String[] args) throws Exception{

        OutputStream os = new FileOutputStream("c:/tmp/excelfile.xlsx");

 

        // streaming workbook 생성

        Workbook wb = new SXSSFWorkbook(100); // 100 row마다 파일로 flush

        for(int sheetNum = 0; sheetNum < 6; sheetNum++){ // 시트 6개

            Sheet sh = wb.createSheet();

            Row heading = sh.createRow(1);

            String[] columns = new String[]{"ID", "NAME", "GENDER", "ADDR", "TEL"};

 

            for(int i = 0; i < columns.length; i++) {

                Cell cell = heading.createCell(i+1);

                cell.setCellValue(columns[i]);

            }

 

            for (int rowNum = 2; rowNum < 1048560; rowNum++){

                Row row = sh.createRow(rowNum);

                row.createCell(1).setCellValue("123123123123");

                row.createCell(2).setCellValue("JOHN_CORNER_MARIE_TYPE_PILE_END");

                row.createCell(3).setCellValue("MALE");

                row.createCell(4).setCellValue("Dongjin-Gu, Pyeonsoo-dong, Daeum-chon 2348-112");

                row.createCell(5).setCellValue("010-8422-9548");

            }

        }

 

        wb.write(os);

        os.close();

        ((SXSSFWorkbook)wb).dispose();

}

 

row의 내용들은 의미없이 양만 채운 것이고, 결과적으로 실행하고 나면 100메가 정도짜리 엑셀 파일이 만들어지게 조정했다.




그걸 만드는 몇 분 동안 VisualVM 으로 메모리 사용량을 체크했는데 실행 내내 다음과 같이 2MB 안쪽으로 밖에 쓰지 않는다.



이걸 만들 때 주의할 것은, 시트(!)가 임시 저장소 (윈도우즈의 경우 환경변수 %TEMP% 로 정해진 위치, 유닉스라면 /tmp 일 듯) 에 압축되지 않은 채로 생기는데, 이게 꽤 양을 잡아먹는데다 위 코드의 마지막 줄 dispose() 를 실행하지 않으면 JVM이 종료된 후에도 파일이 사라지지 않는다.


 

그러므로 dispose()는 꼭 해 주는 걸 권장한다.

다만 dispose()를 하게 되면 그 Workbook 객체는 쓸 수 없는 상태가 되므로 가능한 한 마지막(!)에..

 

... 이제 이걸 가지고 다음과 같이 하면 다운로드가 끝난다.

1) 예제처럼 (임시)파일로 저장하고 dispose()한 후, 이를 다운로드 시키고 파일을 삭제한다.

2) 아예 workbook 객체를 가지고 다운로드까지 한 후 dispose() 한다.

 

위 방법 중 1)번은 개발구조 상 layer가 명시적으로 나누어져 있어 layer 간 건네줄 정보가 String(파일위치) 이라는 등 한정적이거나, 파일을 생성후 다운로드 하는 짧(지만 짧지 않)은 시간 동안 디스크 공간 점유가 염려될 때 사용할 수 있겠다. 2)번은 그런 제약이 없을 경우 쓸 수 있고.

 

둘 다 모듈로 일반화 시키려면 적절한 코딩은 필요하고.. 특히 예외사항 (파일 생성 중 오류가 난다거나 파일 생성과 관계없는 곳에서 오류가 날 경우 임시파일의 뒤처리 등등) 에 대한 대응이 잘 짜여 있어야 하겠다.


Posted by JayCeeP
일/office2018. 2. 23. 14:22

워드에서 문서구조로 편히 목차를 확인할려고

 

종종 다단계 번호매기기를 즐겨 쓴다.

 

근데 매번 양식 만들 때마다 한두개씩 기능이 가물가물해서 10~20분씩 삽질하니...

 

더이상은 귀찮다. 정리해 두자.

 

설명은 MS Office Word 2007 버전을 기준으로 한다.

 

 

1. 새문서 열기

 

2. 스타일 목록(1)을 활성화 시키고 새스타일(2)을 클릭

 

3-1. 스타일 목록에 표시될 이름(1)을 입력하고,

3-2. 스타일형식(2)은 '단락'으로 유지

3-3. 스타일기준(3)은 '제목1'로 변경

3-4. 다음 단락의 스타일(4)은 '표준'으로

3-5. 확인(5)을 누르고 '1순위제목'이라는 스타일을 완성

3-6. 3-1~3-6의 과정처럼 '2순위제목'과 '3순위제목'도 만든다. 스타일기준(3) 각각 다르니 유의.

 

4-1. 다시 새스타일을 클릭하고,

4-2. 이름(1)을 입력하고,

4-3. 스타일형식을 '목록'(2)으로 선택

 

5-1. 서식적용대상(1)을 '수준1'을 선택하고,

5-2. 글꼴들(2)을 조정한다.

5-3. 단계가 3단계였으니 나머지 '수준2'와 '수준3'도 서식적용대상(1)에서 선택하여 마찬가지로 글꼴을 조정.

 

6-1. 서식(1)을 클릭하고,

6-2. 번호매기기(2)를 선택.

 

7-1. 자세히(1)를 클릭하여 옵션목록을 확장.

 

8-1. 수준(1)에서 '수준1' 선택하고

8-2. 번호서식(2)을 조정해 준다.

8-3. 그리고 단계에 연결할 스타일(3)에 먼저 만들었던 '1순위제목'을 선택하여 연결

8-4. 8-1~8-3의 과정처럼 '수준2'에 '2순위제목'을, '수준3'에 '3순위제목'을 입력해준다.

8-5. 마지막으로 창 모두 확인(4)을 클릭하여 스타일을 저장.

 

9. 목록에서 생긴 스타일을 확인할 수 있다. 적용해서 사용하면 끝.

  



출처: http://damduc.tistory.com/290

Posted by JayCeeP
일/coding2018. 1. 20. 15:39

여태 개발을 하면서 습관적으로 반복문을 for문만을 사용했다. 알고리즘 공부하다보니 때로는 while을 사용하는 것이 소스 간결함에서 유리할 수 있다는 것을 알았다. 소스가 간결하다는 것은 해석하기도 좋다는 것이다. 이것을 비교하여 정리해본다.


 

 for

while 

사용법 

 for(반복 판단할 변수 초기화/일반적으로 int i=0;

      반복 조건(boolean 형태);

      반복 판단할 변수 처리/일반적으로 i++){

....


구문 처리

...

}

while(반복 여부 판단할 수 있는 boolean 형태/구문 내에 사용하는 변수나 조건으로 판단 할 수 있음){ 

....

구문 처리

또는

구문 처리+반복 여부 판단할 변수 처리

....

}

단순 반복문 구현 적합 

비교적 간단

비교적 귀찮음.

반복 여부 조건이 내용을 구현하면서 달라지는 경우를 고려하여 설계할 경우 for에 비하여 비교적 귀찮음. 또는 반복여부할 변수를 따로 관리해야하니 귀찮음 >> 개발자 자원(시간)이 소요됨.

알고리즘 구현 적합 

for()와 if()문, break문 사용 시 인자 내용 변경등을 고려해야 하기 때문에 소스의 양이 많아지고 가독성이 떨어질 수 있음.

break시점을 생각하지 않고 단순 구현만을 할 경우 필요이상으로 반복하여 퍼포먼스 차이 발생 가능.

비교적 간결

서로 비교한 결론 

단순 반복일 경우 for 문을 사용하는 것이 소스 해석이 더 편하다. 

for안에 if를 사용 && 특정부분에서 break 시키는 것을 원할 경우 while이 더 소스가 간결하다.


결론

-단순 반복문 사용 시 >> for문이 쓰기도 편하고 해석하기도 편하다.

-반복 조건이 유동적으로 변경되는 알고리즘 구현 >> for, if문 break를 사용할 경우 for문보다 while문이 더 간결하게 작성할 수 있다. 이런 경우에 for문 사용 시 반복 조건 및 내부에 여러 변수와 break처리가 쓰이므로 소스가 길어지고 이에 따라 해석이 더 어려워지는 경우가 발생한다.(절대적인 기준은 아님)

Posted by JayCeeP