IO 기본 (1)
#Java/adv2
/스트림 시작1: 자바 프로세스의 데이터를 외부로 입출력 입력/출력 스트림(단방향)을 사용. byte 단위 입출력
- /스트림 시작 - 예제1
new FileOutputStream(파일경로): 파일에 데이터를 출력하는 스트림- 파일 없으면 자동 생성. 폴더는 자동 생성X
fos.write(int): byte 단위로 값 출력new FileOutputStream(파일경로, appned 유무):append옵션이true이면 기존 파일의 끝에 이어서,false(기본값)이면 지우고 처음부터 다시 작성
new FileInputStream(파일경로): 파일에서 데이터 읽어오는 스트림fis.read(): byte 단위로 읽어온다.- 파일의 끝(EOF)에 도달해서 읽을 내용 없으면 -1 반환
- byte가 아니라 int를 반환한다.
- byte는 (-128 ~ 127)
- 256개에 추가적으로 EOF를 위한 특별한 값 할당이 어려움. (8bit 한계)
- int를 사용하면 256 종류의 값 + EOF를 나타낼 수 있다.
- byte는 (-128 ~ 127)
fis.close(),fos.close()- 외부 자원을 사용하는 것이므로 GC 대상이 아님. 반드시 직접 닫아주자.
- 텍스트 편집기의 인코딩 방식에 따라 byte 값이 디코딩되어 보인다.
- /스트림 시작 - 예제2: 파일 읽을 때 while문을 통해 EOF 도달시점까지 읽을 수 있다.
/스트림 시작2: byte 하나씩이 아니라, byte 배열로 데이터를 다뤄보자.
- /스트림 시작 - 예제3
- 출력 스트림
fos.write(byte[]): byte 배열의 데이터를 한 번에 출력
- 입력 스트림
read(byte[], offset, lentgh):byte[]을 미리 만들어두고, 만들어둔byte[]에 한 번에 데이터를 읽어올 수 있다.- 여기서
byte[]는 버퍼가 된다. offset: 데이터가 기록되는byte[]오프셋(시작 위치). 디폴트는 0length: 읽어올 byte 최대 길이. 디폴트는byte[].length- 반환 값: 버퍼에 읽은 총 바이트 수. 끝에 도달하면 -1
- 여기서
- 출력 스트림
- /스트림 시작 - 예제4
- 입력 스트림
byte[] readBytes = fis.readAllBytes();- 스트림이 끝날 때 까지 모든 데이터를 한 번에 읽어온다.
- 입력 스트림
- /부분으로 나누어 읽기 vs 전체 읽기
read(byte[], offset, lentgh)- 스트림 내용을 부분적으로 읽거나, 읽으면서 같이 무언가 처리해야 하는 경우 적합.
- ex. 메모리 사용량 제어. 100MB 1MB씩 100번
- 스트림 내용을 부분적으로 읽거나, 읽으면서 같이 무언가 처리해야 하는 경우 적합.
readAllBytes()- 작은 파일이나 메모리에 모든 내용을 올려서 처리해야 하는 경우
- 편리하나, 메모리 사용량 제어가 불가능.
OutOfMemoryError발생 가능
- 자바는 파일, 네트워크, 콘솔 각각 데이터를 주고 받는 방식을
InputStream,OutputStream에 추상화해둠(추상 클래스)- 일관된 방식으로 데이터를 주고받을 수 있다.
- 유연하고, 확장가능하고, 재사용가능하고, 예외 처리도 표준화된다.
InputStreamFileInputStream,ByteArrayInputStream,SocketInputStreamread(),read(byte[]),readAllBytes()
OutputStreamFileOutputStream,ByteArrayOutputStream,SocketOutputStreamwrite(int),write(byte[])
- /메모리 스트림
new ByteArrayOutputStream()write()제공
new ByteArrayInputStream(baos.toByteArray());readAllBytes()제공
- /콘솔 스트림
PrintStream printStream = System.out;write()제공println(String),print(String)등 제공- Sout이 사실은
printStream의 메서드 println내부에서 문자열을 byte로 바꿔서 처리해준다.
- Sout이 사실은
- 이 스트림은 자바가 시작될 때 자동으로 만들어져서, 직접 생성하지 않는다.
- /예제1 - 쓰기: 1바이트씩
fos.write()로 쓰기 - /예제1 - 읽기: 1바이트씩
fis.read()로 읽기 write()나read()를 사용하면 시스템 콜을 명령이 전달되어 무거운 작업이 수행되는데, 이걸 수없이 반복하니 오래 걸린다.- OS와 HW 수준에서 어느정도 최적화가 되긴 하지만,
read(),write()호출 횟수부터 줄여야 시스템 콜 횟수도 줄어든다.
- OS와 HW 수준에서 어느정도 최적화가 되긴 하지만,
/파일 입출력과 성능 최적화2 - 버퍼 활용: 시스템 콜 한 번에 많은 데이터를 버퍼에 실어서 보내자.
byte[]를 통해 전달- /예제2 - 쓰기
fos.write(buffer);- 버퍼가 가득차면
write()로 작성한다. (예제는 버퍼 크기: 8KB) - 끝 부분에 오면 버퍼가 가득차지 않고 남아있을 수 있으므로, 남은부분을
write()한다. - 1바이트씩
write()하는 것 보다 훠~얼씬 빠르다.
- /버퍼의 크기에 따른 쓰기 성능
- 버퍼의 크기를 늘리면, 시스템 콜도 줄어들어 실행 시간이 줄어든다.
- 버퍼의 크기가 8KB를 넘으면 크게 빨라지지 않는다.
- 디스트에서 데이터를 읽고 쓰는 단위가 보통 4KB 또는 8KB이기 때문이다.
- 버퍼 크기는 4KB나 8KB로 잡자
- /예제2 - 읽기
while ((size = fis.read(buffer)) != -1)fis.read(): 읽은 사이즈를 반환해줌, buffer.legnth가 8192면, 8192씩 읽어온다.
byte[]버퍼 방식의 단점: 직접 버퍼를 만들고 관리해야 함.
/파일 입출력과 성능 최적화3 - Buffered 스트림 쓰기
BufferedOutputStream를 통해 버퍼 기능을 내부에서 처리해준다.new BufferedOutputStream(대상 OutputStream, BUFFER_SIZE);- 단순히 버퍼 기능만 제공한다. 따라서 반드시 대상
OutputStream이 있어야 한다.- ex) fos
- 생성자에 사용할 버퍼의 크기도 함께 전달할 수 있다.
bos.write()를 하면 시스템 콜이 나가는게 아니라, 먼저 버퍼에 쌓이도록 구현되어 있다.byte[]을 직접 다루지 않아도 된다. for문에 1바이트씩 써도, 내부적으로 버퍼가 가득차야 대상OutputStream에write()시스템 콜이 나간다.OutputStream을 상속받기 때문에 같은 기능을 사용할 수 있다.flush(): 버퍼가 다 차지 않아도 버퍼에 남아있는 데이터를 전달해준다.close(): 내부에서 먼저flush()를 호출하여 내부에 남아있는 데이터를 비우고 대상OutptStream의 자원을 정리하고,BufferedOutputStream도 닫힌다.- 대상 OutputStream을 직접 닫으면 안 된다.
flush()가 호출되지 않고, 자원도 정리되지 않는다.
- 대상 OutputStream을 직접 닫으면 안 된다.
- 버퍼의 크기만큼 데이터를 모아서 전달하기 때문에 빠르다.
- 단순히 버퍼 기능만 제공한다. 따라서 반드시 대상
- /기본 스트림, 보조 스트림
- 기본 스트림:
FileOutputStream과 같이 단독으로 사용할 수 있는 스트림 - 보조 스트림:
BufferedOutputStream과 같이 단독으로 사용할 수 없고, 보조 기능을 제공하는 스트림
- 기본 스트림:
/파일 입출력과 성능 최적화4 - Buffered 스트림 읽기
BufferedInputStream를 통해 버퍼 기능을 내부에서 처리해준다.new BufferedInputStream(대상 InputStream, BUFFER_SIZE)InputStream을 상속받기 때문에 같은 기능을 사용할 수 있다.bis.read()- 조회하면 버퍼에서 값을 읽어온다.
- 버퍼에 읽을 데이터가 부족하면 버퍼 크기만큼 대상
InputStream에서 값을 읽어온다. - 1바이트씩 데이터를 조회해도 실제로는 버퍼 크기만큼 퍼오기 때문에, 성능이 최적화 된다.
/버퍼를 직접 다루는 것 보다 BufferedXxx의 성능이 떨어지는 이유
BufferedXxx클래스는 모두 동기화 처리가 되어 있기 때문이다.BufferedXxx의read(),write()를 n번 호출하면 락을 걸고 푸는 것도 n번 호출된다.- 자바 초창기에 만들이진 클래스라, 멀티 스레드를 고려해서 만들어졌다.
- 스레드 safe 하지만, 성능이 약간 저하된다.
- 일반적인 상황이라면 이 정도 성능은 크게 문제가 되지는 않는다.
- 매우 큰 데이터를 다루고 성능 최적화가 중요하면 버퍼를 직접 다루는 방법을 고려해야 한다.
- 동기화 락이 없는
BufferedXxx클래스는 없다.
- 파일의 크기가 크지 않다면 간단하게 한 번에 쓰고 읽는 방법도 좋은 방법이다.
- ex)
fos.write(buffer); - ex)
byte[] bytes = fis.readAllBytes(); - 예제에서는 10MB로 지정했지만, 8KB랑 성능 차이가 거의 나지 않는다.
- ex)
'Java > IO, Network' 카테고리의 다른 글
| [Java] 51. 네트워크 - 프로그램(2) (0) | 2025.07.01 |
|---|---|
| [Java] 50. 네트워크 - 프로그램(1) (0) | 2025.07.01 |
| [Java] 49. File, Files (0) | 2025.07.01 |
| [Java] 48. 자바 I/O 활용 (0) | 2025.07.01 |
| [Java] 47. 자바 IO 기본(2) (0) | 2025.07.01 |