왜? 뭐하러? 비동기 작업이 만들기도 어렵고, 버그 발생 시 처리하기도 힘든데...
맞는 말이기도 하지만, 메모리와 CPU에서 처리하는 작업도 비동기 작업으로 처리하는데, 네트웍이나 DISK IO라면 수 천배, 수 만배 느린데, 이것을 비동기로 처리해서, 다른 작업을 처리해 줄 수 있다면 시스템 전체의 작업 효율이 엄청 높아질 것입니다.
Redis 가 초기에 Master/Slave replication 방식을 동기식으로만 지원하다가 비동기 방식도 지원하게 된 이유도 같은 이치라 보시면 됩니다.
자 이제 본격적으로 비동기 방식의 파일 읽기/쓰기를 해보기로 합시다. JDK 1.7 NIO 2 에 보면 기존 NIO 보다 확장되고 향상된 기능이 많습니다.
아직도 JDK 1.7을 쓰지 않는 시스템이나 NIO2 를 쓰지 않는 시스템을 보면 아쉬움이 크네요.
다들 얼른 빨리 업그래이드해서 고성능 기능을 활용하면 좋을텐데 하는 맘입니다.
우선 NIO 2 를 이용하는 방식 중 작은 파일을 다룰 때에는 버퍼링되는 스트림을 사용하는 것이 가장 쉽고, 편합니다.
BufferedReader, BufferedWriter 사용 법은 Files 클래스를 사용하면 끝납니다.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@Test | |
public void bufferedStreams() throws Exception { | |
Path path = Paths.get("channel.txt"); | |
try (BufferedWriter writer = Files.newBufferedWriter(path, UTF8, StandardOpenOption.CREATE, StandardOpenOption.APPEND)) { | |
for (int i = 0; i < 100; i++) { | |
writer.write(TEST_TEXT); | |
} | |
writer.flush(); | |
} | |
List<String> lines = Files.readAllLines(path, Charset.forName("UTF-8")); | |
for (String line : lines) | |
System.out.println(line); | |
Files.deleteIfExists(path); | |
} |
Files.newBufferedWriter, Files.readAllLines 등 내부적으로 BufferedReader, BufferedWriter를 사용합니다. 버퍼링을 통해, DiskIO 작업 횟수를 적게, 한꺼번에 처리하도록합니다.
다음으로는 대용량의 데이터를 파일로 처리할 경우 비동기 방식으로 처리해주는 AsynchronousFileChannel 의 사용법에 대해 보겠습니다. 이름에서 보시다시피 비동기 방식으로 파일의 데이터를 처리합니다.
대용량 데이터를 쓰는 예
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
AsynchronousFileChannel fc = null; | |
try { | |
fc = AsynchronousFileChannel.open(path, | |
StandardOpenOption.CREATE, | |
StandardOpenOption.WRITE); | |
ByteBuffer buffer = ByteBuffer.wrap(StringTool.replicate(TEST_TEXT, 1000).getBytes(UTF8)); | |
Future<Integer> result = fc.write(buffer, 0); | |
while (!result.isDone()) { | |
System.out.println("Do something else while writing..."); | |
} | |
System.out.println("Write done: " + result.isDone()); | |
System.out.println("Bytes write: " + result.get()); | |
} finally { | |
assert fc != null; | |
fc.close(); | |
} |
데이터를 쓰는 경우도 많은 데이터를 쓰는 동안 다른 일을 처리할 수 있으므로, Future.isDone() 을 검사하여 다른 작업을 처리할 수도 있고, 다른 작업을 처리한 후 Future.get() 을 통해 작업 결과를 알 수 있습니다.
대용량 데이터를 읽는 예
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
try { | |
fc = AsynchronousFileChannel.open(path, | |
StandardOpenOption.READ, | |
StandardOpenOption.DELETE_ON_CLOSE); | |
ByteBuffer buffer = ByteBuffer.allocate((int) fc.size()); | |
Future<Integer> result = fc.read(buffer, 0); | |
while (!result.isDone()) { | |
System.out.println("Do something else while reading..."); | |
} | |
System.out.println("Read done: " + result.isDone()); | |
System.out.println("Bytes read: " + result.get()); | |
buffer.flip(); | |
byte[] bytes = buffer.array(); | |
List<String> lines = readAllLines(bytes, UTF8); | |
for (String line : lines) { | |
System.out.println(line); | |
} | |
// System.out.print(UTF8.decode(buffer)); | |
buffer.clear(); | |
} finally { | |
assert fc != null; | |
fc.close(); | |
} |
주의사항 : 테스트 코드라 읽고 난 후 파일을 삭제하게 해 놨으니, 실전에서는 필요에 따라 옵션을 지정해 주세요.
마지막으로 byte[]로 읽어드린 내용이 텍스트라면, List
라인 처리는 OS 시스템마다 다 다른데, BufferedReader 가 잘 되어 있더군요 ㅋ
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public List<String> readAllLines(byte[] bytes, Charset charset) { | |
if (bytes == null || bytes.length == 0) | |
return new ArrayList<String>(); | |
try (ByteArrayInputStream is = new ByteArrayInputStream(bytes); | |
Reader in = new InputStreamReader(is, charset); | |
BufferedReader br = new BufferedReader(in)) { | |
List<String> lines = new ArrayList<String>(); | |
while (true) { | |
String line = br.readLine(); | |
if (line == null) | |
break; | |
lines.add(line); | |
} | |
return lines; | |
} catch (Exception e) { | |
throw new RuntimeException(e); | |
} | |
} |
댓글 없음:
댓글 쓰기