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
에 추상화해둠(추상 클래스)- 일관된 방식으로 데이터를 주고받을 수 있다.
- 유연하고, 확장가능하고, 재사용가능하고, 예외 처리도 표준화된다.
InputStream
FileInputStream
,ByteArrayInputStream
,SocketInputStream
read()
,read(byte[])
,readAllBytes()
OutputStream
FileOutputStream
,ByteArrayOutputStream
,SocketOutputStream
write(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 |