본문 바로가기
Back-end/JAVA

[JAVA] 파일 분할 - 용량 단위, 위에서 아래로 ↓

by 허도치 2020. 10. 22.
서론

  프로그램을 구동하다보면 로그가 쌓이기 마련이다. 일반적이라면 일정주기나 용량별로 파일을 분할하고 압축을 하지만, 로그가 많이 쌓이지 않는 프로그램이라면 한개의 파일로 관리할지도 모른다. 한개의 파일로 관리하면 백업하기도 편하다. 하지만, 어떠한 이유로 갑작스럽게 로그가 많이 쌓이게 되면 어떻게 될까? 당연히 용량이 급격하게 늘어날 것이다.

 

  최근 겪었던 상황이 딱 이러했다. 새로운  기능을 추가하였는데, 해당 기능에서 RAW 데이터를 로그에 저장하면서 파일 용량이 600MB를 넘어갔다. 처음에 원인을 몰랐을 때, 로그파일을 열어봐야하는데 메모장이나 노트패드로는 열리지 않아서 난감했다. 결국, 서브라임으로 열긴했는데 이 마저도 너무 느려서 답답했다.

 

  그래서, 파일을 용량 단위로 분할 해주는 기능을 JAVA로 구현해보았다.

 

 

 

소스코드
1. FileUtils.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.RandomAccessFile;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
 
public class FileUtils {
    
    /* Singleton */
    public static FileUtils instance = new FileUtils();
    
    /* 생성자1 */
    public static FileUtils getInstance() {
        if( instance != null ) {
            return instance;
        }
        return new FileUtils();
    }
    
    /**
     * 저장용량 단위별 처리량을 반환
     * @param unit  저장 용량 단위
     * @return      저장 용량 단위별 처리량
     */
    private static int getUnitCapacity(String unit) {
        if( unit == null ) { return 1; }
        unit = unit.toUpperCase();
        
        Map<String, Integer> units = new HashMap<String, Integer>();
        units.put("B"1);
        units.put("K"1024);
        units.put("M"1024*1024);
        units.put("G"1024*1024*1024);
        
        return units.get(unit);
    }
    
    /**
     * 파일 저장
     * @param filename  저장할 파일명(경로가 없을 경우 현재 경로에 저장)
     * @param text      파일에 저장할 내용
     * @param append    이어쓰기 모드여부. true: 이어쓰기, false: 다시쓰기
     * @param encoding  인코딩
     * @return          파일이 저장된 경로를 반환
     */
    public static String saveFile(String filename, String text, boolean append, Charset encoding) {
        OutputStreamWriter writer = null;
        File outfile = null;
        FileOutputStream outfile_stream = null;
        String savePath = null;
        
        try {
            outfile = new File(filename);
            outfile_stream = new FileOutputStream(outfile, append);
            
            writer = new OutputStreamWriter(outfile_stream, encoding);
            writer.write(text);
            writer.close();
            
            savePath = outfile.getAbsolutePath();
            
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if( writer != null ) {
                    writer.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        
        return savePath;    
    }
 
    /**
     * 파일 내용을 byte[]로 받으며, 내용은 UTF8로 저장
     */
    public static String saveFileUTF8(String filename, byte[] text, boolean append) {
        return  saveFile(filename, new String(text), append, StandardCharsets.UTF_8);
    }
    /**
     * 파일 내용을 String로 받으며, 내용은 UTF8로 저장 
     */
    public static String saveFileUTF8(String filename, String text, boolean append) {
        return  saveFile(filename, text, append, StandardCharsets.UTF_8);
    }
    
    /**
     * 파일을 위에서 아래로 읽음
     * @param file             읽어드릴 파일 객체
     * @param size             출력할 사이즈
     * @param unit             B: 바이트(8Bit), K: 킬로바이트(1024B), M: 메가파이트(1024KB)
     * @param split_cnt        분할 파일 최대 개수
     * @return                 마지막으로 조회된 데이터
     */
    public static void splitFile(File file, int size, String unit, int max) {
        // 
        final String PREFIX_FILE_DIR = "";
        final String PREFIX_FILE_FILENAME = "splited";
        final String PREFIX_FILE_EXT = "log";
        
        RandomAccessFile reader = null;
        byte[] buffer = null;
        
        try {
            // Read file
            reader = new RandomAccessFile(file, "r");
 
            // Variables
            int interval = size * getUnitCapacity(unit);
            long EOF = reader.length();
            int spos = 0;
            int epos = 0;
            int count = 0;
 
            String filename = null;
            String savepath = null;
            
            do {
                // Set Position
                spos = epos;
                epos += interval;
                count += 1;
                
                // Break Point
                if( epos > EOF ) {
                    epos = (int)EOF;
                }
                
                // 시작지점 커서 이동
                reader.seek(spos);
                
                buffer = new byte[epos-spos];
                reader.readFully(buffer);
 
                // Create New Flie
                filename = PREFIX_FILE_DIR+PREFIX_FILE_FILENAME+"_"+count+"."+PREFIX_FILE_EXT;
                savepath = saveFileUTF8(filename, buffer, false);
                
                // 파일 저장경로
                System.out.printf("savepath: %s, size: %d\n", savepath, buffer.length);
                
                // 파일최대건수
                if( max != 0 && count >= max ) {
                    break;
                }
            } while( epos < EOF );
            
        } catch ( Exception e ) {
            e.printStackTrace();
        } finally {
            try {
                if( reader != null ) {
                    reader.close();
                    reader = null;
                }
            } catch ( Exception e ) { }
        }
    }
 
    public static void main(String[] args) {
        // TODO: arguments로 받아서 처리
        String filepath = "C:\\logs\\20201021_stdout.log";
    
        // 파일 객체 생성
        File file = new File(filepath);
        
        // 100MB 단위로 모두 분할
        FileUtils.splitFile(file, 100"M"0);
    }
}
 
cs

 

 

 

마치며

  이번에 RandomAccessFile 클래스를 이용했는데, 읽는 속도는 느리긴하지만 파일을 맘대로 컨트롤하기 편해서 사용해보았다. 그리고, 지금은 위에서 아래로(오름차순) 파일을 읽어서 잘랐는데, 다음에는 아래에서 위로(내림차순) 읽어가면서 파일을 잘라볼 예정이다.

 

  과거의 데이터가 필요할 때는 이번에 만든 기능을 사용하고, 최신 데이터가 필요한 경우에는 다음 포스트에 다루는 기능을 활용하도록 하자.

 

댓글