Search

2025-02-10

InternalBuffer
1.
relocateAndLoad() 메서드에서 발생하는 IndexOutOfBoundsException 예외 발생
원인 - read(char[] buf, int off, int len) 에서 len 사이즈 고정으로 인하여 발생한 문제.
해결 - read(buf, off, len - off); 변경
2.
extendAndLoad() 무한 루프 문제 발생
원인 - relocateAndLoad() 실행시 begin가 잘못 갱신
기존 원인 코드 -
private void relocateAndLoad() { final int copyLength = limit - pos; System.arraycopy(buf, pos, buf, 0, copyLength); int cnt = read(buf, copyLength); state = cnt == -1 ? BufferState.LAST_LINE : BufferState.PROGRESSING; pos = 0; begin = limit; // limit갱신시 끝지점 즉 8192로 갱신되어 버림 limit = state == BufferState.LAST_LINE ? limit : copyLength + cnt; }
Java
복사
해결 -
private void relocateAndLoad() { final int copyLength = limit - pos; System.arraycopy(buf, pos, buf, 0, copyLength); int cnt = read(buf, copyLength); state = cnt == -1 ? BufferState.LAST_LINE : BufferState.PROGRESSING; pos = 0; begin = copyLength; // copyLength를 begin으로 갱신 limit = state == BufferState.LAST_LINE ? limit : copyLength + cnt; }
Java
복사
3.
isBeginAtLimit() 메서드 변경 → notBeginAtLimit()
이유 - 사용시 !isBeginAtLimit(); 형태로 사용함이 직관적이지 못하다고 생각함. notXX가 좀 더 직관적이라고 판단.
RecordParser
1.
normalParse() 제작
@Override public List<Record> parse(){ while (buffer.fill()){ // Optional<Function<Object, Object>> parse = // InternalBuffer2.ifLastRowOrElse(buffer, this :: lastRowParse, this :: normalParse); // if (parse.isEmpty()){ // break; // } // parse.get().apply(buffer); /** TODO 위의 코드 쪽으로 추가 예정 */ normalParse(buffer); // TODO Test 사용 용도 } return List.of(); } private List<Record> normalParse(InternalBuffer2 buffer){ boolean inQuote = false; int matchIndex = 0; while (buffer.notBeginAtLimit()){ char c = buffer.getCharAtBegin(); if (c == csvConfig.getQuote()) { inQuote = !inQuote; } if (!inQuote){ matchIndex = incrementIfMatch(csvConfig.getLineDelimiter(), c, matchIndex); if (matchIndex == csvConfig.getLineDelimiterLength()){ parse(matchIndex); break; } } buffer.getIncrementBegin(); } return null; //TODO 반환 값은 fieldParser 제작 후 } private void parse(int matchIndex){ // TODO parse라는 로직 따로 분리 int length = (matchIndex == 1 ? buffer.getBegin() + matchIndex : buffer.getBegin() + matchIndex - 1) - buffer.getPos(); System.out.println(new StringBuilder().append(buffer.getBuf(), buffer.getPos(), length)); buffer.setPos(buffer.getIncrementBegin()); }
Java
복사
최종 결과 코드
package parser3; import lombok.AccessLevel; import lombok.Getter; import java.io.IOException; import java.io.Reader; import java.io.UncheckedIOException; import java.util.Optional; import java.util.function.Function; @Getter public class InternalBuffer2 { @Getter(AccessLevel.NONE) private final Reader reader; private static final int DEFAULT_BUFFER_SIZE = 8192; private static final int READ_SIZE = DEFAULT_BUFFER_SIZE; private BufferState state = BufferState.INITIAL; private char[] buf = new char[DEFAULT_BUFFER_SIZE]; private int pos, begin, limit; private enum BufferState { INITIAL, PROGRESSING, EXTEND, RELOCATE, LAST_LINE; } public InternalBuffer2(Reader reader) { this.reader = reader; } /** * TODO isLastLine 추가 * RecordParser내에서 마지막 줄 여부 판단을 위해서 필요함 * <p> * 상태 전이 [] 반복, () 선택, {} 종료 * INITIAL -> [PROGRESSING <-> (EXTEND | RELOCATE)] -> LAST_LINE{종료} */ boolean fill() { while (true) { switch (state) { case INITIAL: int cnt = read(buf, pos); state = BufferState.PROGRESSING; if (cnt == -1){ return false; } limit = cnt; return true; case PROGRESSING: /** * if -> begin >= limit 일 경우 즉, 커서가 끝까지 갔는데 아무 변화 없을때 * state = (pos == 0) ? EXTEND : RELOCATE; * continue; * retrun true; */ if (begin >= limit) { state = pos == 0 ? BufferState.EXTEND : BufferState.RELOCATE; continue; } return true; case EXTEND: /** * 확장 및 read * extend() 확장 * cnt = read(begin) -> READ_SIZE 만큼 읽기 * if -> cnt == -1 LAST_LINE * else -> PROGRESSING and limit += cnt * continue; */ extendAndLoad(); return true; case RELOCATE: /** * 재배치 및 read * relocate() * begin = limit - pos; * pos = 0; * cnt = read(begin) // READ_SIZE 만큼 데이터 로드 * if -> cnt == -1 LAST_LINE * else -> PROGRESSING */ relocateAndLoad(); return true; case LAST_LINE: //TODO 라스트 삭제 예정 return false; } } } static <T, R> Optional<Function<T, R>> ifLastRowOrElse(InternalBuffer2 buffer, Function<T, R> action, Function<T, R> elseAction) { if (buffer.getPos() == buffer.getLimit()){ return Optional.empty(); } boolean isLastLine = buffer.getState() == BufferState.LAST_LINE && buffer.getPos() < buffer.getLimit(); return Optional.of(isLastLine ? action : elseAction); } char getCharAtBegin(){ return buf[begin]; } int getIncrementBegin(){ return begin++; } void setPos(int newPos){ this.pos = newPos; } boolean notBeginAtLimit(){ return begin < limit; } private int read(char[] buf, int off) { int read = 0; try { read = reader.read(buf, off, READ_SIZE - off); return read; } catch (IOException e) { System.out.println(read); throw new UncheckedIOException(e); } } private void extendAndLoad() { // TODO max size 예외처리 char[] newBuf = new char[buf.length * 2]; System.arraycopy(buf, 0, newBuf, 0, buf.length); buf = newBuf; int cnt = read(buf, begin); state = cnt == -1 ? BufferState.LAST_LINE : BufferState.PROGRESSING; limit = state == BufferState.LAST_LINE ? limit : limit + cnt; } private void relocateAndLoad() { final int copyLength = limit - pos; System.arraycopy(buf, pos, buf, 0, copyLength); int cnt = read(buf, copyLength); state = cnt == -1 ? BufferState.LAST_LINE : BufferState.PROGRESSING; pos = 0; begin = copyLength; limit = state == BufferState.LAST_LINE ? limit : copyLength + cnt; } }
Java
복사
package parser3; import java.io.Reader; import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.function.Function; import java.util.function.IntPredicate; import java.util.function.Predicate; public class RecordParser implements CSVParser<List<Record>> { private final InternalBuffer2 buffer; private final CSVConfig csvConfig; // TODO 차후 추가 private final CSVParser<List<String>> fieldParser; private int skipLine; public RecordParser(Reader reader, int bufferCapacity ,CSVConfig csvConfig) { if (bufferCapacity <= 0) throw new IllegalArgumentException("Buffer capacity cannot be less than 1."); this.buffer = new InternalBuffer2(reader); this.csvConfig = csvConfig; // TODO 차후 추가 this.fieldParser = new FieldParser(buffer, csvConfig); this.skipLine = csvConfig.getSkipLine(); } @Override public List<Record> parse(){ while (buffer.fill()){ // Optional<Function<Object, Object>> parse = // InternalBuffer2.ifLastRowOrElse(buffer, this :: lastRowParse, this :: normalParse); // if (parse.isEmpty()){ // break; // } // parse.get().apply(buffer); normalParse(buffer); } return List.of(); } private List<Record> normalParse(InternalBuffer2 buffer){ boolean inQuote = false; int matchIndex = 0; while (buffer.notBeginAtLimit()){ char c = buffer.getCharAtBegin(); if (c == csvConfig.getQuote()) { inQuote = !inQuote; } if (!inQuote){ matchIndex = incrementIfMatch(csvConfig.getLineDelimiter(), c, matchIndex); if (matchIndex == csvConfig.getLineDelimiterLength()){ parse(matchIndex); break; } } buffer.getIncrementBegin(); } return null; //TODO 반환 값은 fieldParser 제작 후 } private void parse(int matchIndex){ // TODO parse라는 로직 따로 분리 int length = (matchIndex == 1 ? buffer.getBegin() + matchIndex : buffer.getBegin() + matchIndex - 1) - buffer.getPos(); System.out.println(new StringBuilder().append(buffer.getBuf(), buffer.getPos(), length)); buffer.setPos(buffer.getIncrementBegin()); } /** * lastRow의 경우는 pos 부터 limit까지 넘겨버리면 됨 * 어차피 row 단위로 하기때문에 row가 존재함은 무조건 보장됨 * * normalParse -> return List<Record> * boolean inQuote = false; * int matchIndex = 0; * * -> while 조건 * char c = getCharAtBegin(); * if -> csvConfig.getQoute() == c * inQuote = !inQuote * * if -> !inQuote && mathcIndex == 1 * fieldParser -> 넘김 * matchIndex = 0; 초기화 * buffer.newPos(buffer.getIncrementBegin()); * break; * buffer.getIncrementBegin(); * */ @Override public boolean canParse() { if (skipLine > 0){ skipLine--; return false; } return true; } }
Java
복사