온라인 자바 스터디 #13 - I/O 입출력 스트림
by coco3o목표
자바의 Input과 Ontput에 대해 학습하세요.
학습할 것 (필수)
- 스트림 (Stream) / 버퍼 (Buffer) / 채널 (Channel) 기반의 I/O
- InputStream과 OutputStream
- Byte와 Character 스트림
- 표준 스트림 (System.in, System.out, System.err)
- 파일 읽고 쓰기
스트림(Stream) / 버퍼(Buffer) / 채널(Channel) 기반의 I/O
I / O란?
- Input과 Output의 약자로 입·출력을 의미한다.
- 입출력의 간단한 예로 키보드로 텍스트를 입력하고, 모니터로 입력한 텍스트를 출력하는 것임
스트림 (Stream)
Input ㅡ> Output
위의 식에서 ' ㅡ> '은 스트림을 의미함
Input , Output 구분없이 어느 한쪽에서 다른 쪽으로 데이터를 보내려면 일종의 다리역할( ㅡ> )을 하는 친구가 필요하다.
그 친구가 바로 스트림( ㅡ> ) 이다.
추가적으로 스트림의 어원은 연속적인 데이터의 흐름을 물에 비유하여 붙여진 이름이라고 한다.
가장 큰 특징으로는 물이 한방향으로 흐르는 것과 같이 스트림은 단방향통신만 가능하다.
즉, 하나의 스트림으로 입출력을 동시에 처리할 수 없다.
만약 입출력을 동시에 처리하고 싶으면 입력 스트림 1개, 출력 스트림 1개 총 2개의 스트림을 생성하면 된다.
[정리]
- 단방향으로 연속적이게 흘러가는 형태 : 출발지에서 나와서 도착지로 들어감
- 프로그램이 출발지냐 도착지냐에 따라 스트림 종류가 다름
(1) 데이터를 입력 받을 때 (도착지) : 입력 스트림(InputStream)
(2) 데이터를 출력할 때 (출발지) : 출력 스트림(OutputStream)
- 프로그램이 네트워크 상의 다른 프로그램과 데이터 교환을 위해 두 스트림이 모두 필요함
스트림은 단방향 통신의 특징을 갖기 때문에, 하나의 스트림으로 입출력을 동시에 할 수 없다.
버퍼 (Buffer)
버퍼란 데이터를 전송하는 저 · 고속 장치간의 속도차이를 줄여주는 역할을 하는 중간 저장소라고 생각하면 된다.
I/O에서 버퍼는 보조스트림이라고도 불리며 위와 같이 데이터를 묶어서 전송하는 역할을 하게 된다.
이는 CPU와 외부장치간의 속도차이를 줄여주어 입출력에 대한 속도를 향상시켜주는 기능을 한다.
* 보조스트림 : 입출력 대상이 되는 파일이나 네트워크를 직접 읽거나 쓸 수 없는 스트림(단순 보조기능만을 수행하는 스트림)
Buffer를 사용하는 이유
하드디스크의 속도는 매우 느리다.
또한 하드디스크 뿐 아니라 키보드, 모니터와 같은 외부장치의 속도는 시간이 많이 걸리는 작업이다.
고로 버퍼링없이 키보드가 눌릴 때마다 눌린 정보를 바로 목적지를 이동시켜주는 것보다
중간에 Memory Buffer를 두어서 data를 한데 묶어서 이동시키는 것이 효율적이고 빠르다.
그냥 전송하게 되면 CPU와 성능 차이가 많이 나서 비효율적이다.
unBuffered / Buffered
버퍼를 쓰지 않는 입출력을 unBuffered I/O라고 한다.
read나 write는 운영체제에 의하여 요청 즉시 처리된다만 이것은 매우 비효율적인 방식이다.
입출력 요청은 디스크 접근이나 네트워크 접근과 같은 매우 시간이 많이 걸리는 동작을 요구한다.
디스크 > (한 번 읽을 때 끝까지 읽어 놓는다.) > 버퍼(데이터 저장 창고) > (하나씩 꺼내 쓰고 다 떨어지면 다시 디스크에서 읽는다) > 프로그램
자바에선 오버헤드를 줄이기 위하여 버퍼링된 스트림(buffered I/O)를 제공한다.
버퍼 입력 스트림은 버퍼 메모리 영역에서 데이터를 읽고 버퍼가 비었을 때만 하드디스크나 네트워크에서 읽는다.
버퍼 출력 스트림은 데이터를 버퍼에 쓰는데 버퍼가 가득 찼을 경우에만 하드디스크나 네트워크에 쓴다.
'버퍼가 없는' 스트림을 '버퍼가 있는' 스트림으로 변경하려면 버퍼 스트림 객체를 생성하면서 '생성자의 인수'로 버퍼가 없는
스트림 객체를 전달하면 된다. 간단히 아래 예제와 같다.
inputStream = new BufferedReader(new FileReader("abc.txt") );
outputStream = new BufferedWriter(new FileWriter("def.txt") );
기존의 FileReader, FileWriter를 BufferedReader, BufferedWriter를 이용하여 버퍼가 있는 스트림으로 변경했다.
자바에서 버퍼를 추가하는 4개의 버퍼 스트림이 제공된다.
바이트 스트림 | 문자 스트림 | |
입력 | BufferedInputStream | BufferedReader |
출력 | BufferedOutputStream | BufferedWriter |
BufferedReader
일정량 사이즈로 '한 번에' 읽어온 후 버퍼에 보관. 사용자가 요구할 때 버퍼에서 읽어오게 한다.
Scanner의 버퍼 사이즈는 1024 chars VS bufferedReader의 버퍼 사이즈는 8192 chars
- enter만 경계로 인식
- 받은 데이터가 String으로 고정. 입력받은 데이터를 타입 변환/파싱 해야 함
- 많은 데이터를 입력받을 경우 효율 좋음
Buffer
BufferedReader br = new BufferedReader(new InputStreamReader(System.in) ); //선언
String s = br.readLine( ); // String
int i = Integer.parseInt(br.readLine( ) ); // Int
1. BufferReader는 오직 String으로만 값을 받지만, 큰 데이터에 있어서는 Scanner보다 효율이 좋다.
2. 값을 오직 String으로 받기 때문에 String이 아닌 다른 값으로 값을 받을 때에는 형변환이 필요하다.
3. br.readLine( )으로 값을 받고, 형변환을 해주어야 한다는 사실만 상기하면 기억하기 쉽다.
4. public static void main(String args[]) throws IOException BufferedReader 는 예외처리를 꼭 해주어야 한다.
try & catch 문을 활용해서 하기도 하지만, 대부분 IOException을 활용해서 한다.
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out) ); //선언
String s = "abcdef"; // 출력할 문자열
bw.write(s + "\n"); // 출력
bw.flush( ); // 남아있는 데이터를 모두 출력시킴
bw.close( ); // 스트림을 닫음
버퍼 스트림은 버퍼를 가지고 있기에 버퍼가 다 채워지지 않았어도 버퍼를 쓰는 경우가 있기 때문에
bw.flush( ) 메소드를 이용하여 버퍼에 있는 데이터를 강제로 출력장치로 보낼 수 있다.
BufferedWirter의 경우 사용을 마치면 close( )를 하여 자원할당을 해제하여야 한다.
또한 println의 ln처럼 자동개행이 없기 때문에 개행을 하려면 \n을 통해 해주어야 한다.
채널 (Channel)
채널은 스트림의 향상된 버전으로 단방향이 아닌 양방향으로 접근이 가능하다.
또한 스트림과 다르게 채널은 비동기적인 특징으로 닫고 중단할 수 있다.
채널의 종류
종류 | 설명 |
FileChannel | 파일 입출력 채널 |
Pipe.SinkChannel | 파이프에 데이터를 출력하는 채널 |
Pipe.SourceChannel | 파이프로 부터 데이터를 입력받는 채널 |
ServerSocketChannel | 클라이언트의 연결 요청을 처리하는 서버 소켓 채널 |
SocketChannel | 소켓과 연결된 채널 |
DatagramChannel | DatagramSocket과 연결된 채널 |
NIO의 Channel 클래스
NIO(New I/O)의 Channel은 Buffer에 있는 내용을 다른 어디론가 보내거나 다른 어딘가에 있는 내용을 Buffer로 읽어들이기 위해 사용된다. 예를 들면 네트워크 프로그래밍을 할 때 ByteBuffer로 Packet을 작성 후 Socket으로 흘려 보낼 때나,Socket을 통해 들어온 내용을 BtyeBuffer에 저장할 때 Channel을 사용한다.이런 Channel을 ServerSocketChannel 이나 Socket Channel 이라고 한다.
FileChannel
ServerSocketChannel이나 SocketChannel의 경우 Selector를 이용하여 Non-Blocking 하게 입출력을 수행 할 수 있지만,FileChannel은 Blocking만 가능하다. 이 점은 운영체제나 시스템 마다 File 입출력시 Non-Blocking을 지원해주지 않는 시스템이 있어 그런 것이라고 한다.
FileChannel은 File에 있는 내용을 ByteBuffer로 불러오거나 ByteBuffer에 있는 내용을 File에 쓰는 역할을 한다.
이에 대해 알아보기 전에 알아야 할것은
- Channel은 직접 인스턴스화 할 수가 없다.
- InputStream/OutputStream에서 만들어야 한다.
FileChannel을 얻는 방법은 다음과 같다.
FileInputStream fis = new FileInputStream("test.txt");
FileChannel cin = fis.getChannel();
FileOutputStream fos = new FileOutputStream("test.txt");
FileChannel cout = fos.getChannel();
RandomAccessFile raf = new RandomAccessFile("test.txt", "rw");
FileChannel cio = raf.getChannel();
위에서 보는 것과 같이 InputStream이나 OutputStream을 통해 FileChannel을 얻을 수 있다.
단순히 Input,OutputStream 외에도 RandomAccessFile과 같이 FileHandling하는 객체에도 getChannel()메소드만 있다면 FileChannel을 얻을 수 있다.
FileInputStream이나 FileOutputStream의 경우, 순차적으로 읽을 땐 적당하지만,
파일 내용을 이리저리 탐색하면서 처리할때는 매우 불편하고 효율적이지 않을 것이다.
따라서 이런 경우엔 파일 임의의 지점에서 읽거나 쓸 수 있는 RandomAccessFile 클래스를 사용하자.
FileChannel에서 읽고 쓰는 방법은 다음과 같다.
FileInputStream fis = new FileInputStream("input.txt");
FileOutputStream fos = new FileOutputStream("Output.txt");
ByteBuffer buf = ByteBuffer.allocateDirect(10);
FileChannel cin = fis.getChannel();
FileChannel cout = fos.getChannel();
cin.read(buf); // channel에서 읽어 buf에 저장
buf.flip();
cout.write(buf); // buf의 내용을 channel에 저장
read함수를 쓰면, position위치에서부터 limit 위치까지의 내용을 FileInputStream의 내용으로 채워진다.
write함수를 쓰면, position위치에서부터 limit 위치까지의 내용을 FileOutputStream에 출력한다.
InputStream과 OutputStream 그리고 Byte와 Character 스트림
Java I/O 패키지 주요 클래스 및 설명
Java.io. 패키지의 주요 클래스 | 설명 |
File | 파일 시스템의 파일 정보를 얻기 위한 클래스 |
Console | 콘솔로부터 문자를 입출력하기 위한 클래스 |
InputStream / OutputStream | 바이트 단위 입출력을 위한 최상위 입출력 스트림 클래스 |
FileInputStream / FileOutputStream | 바이트 단위 입출력을 위한 하위 스트림 클래스 |
DataInputStream / DataOutputStream | |
ObjectInputStream / ObjectOutputStream | |
PrintStream | |
BufferedInputStream / BufferedOutputStream | |
Reader / Writer | 문자 단위 입출력을 위한 최상위 입출력 스트림 클래스 |
FileReader / FileWriter | 문자 단위 입출력을 위한 하위 스트림 클래스 |
InputStreamReader / OutputStreamWriter | |
PrinterWriter | |
BufferedReader / BufferedWriter |
바이트 단위 스트림(Byte Stream)
InputStream, OutputStream 둘다 바이트 기반 입출력 스트림의 최상위 클래스로 추상 클래스이다.
관련된 모든 바이트 기반 입출력 스트림은 이 클래스를 상속받아서 만들어진다.
바이트단위로 데이터를 전송하며 입출력 대상에 따라 제공하는 클래스가 다르다.
그림, 멀티미디어, 문자 등 모든 종류의 데이터를 주고 받을 수가 있다.
InputStream OutputStream |
입출력을 위한 바이트 스트림의 최상위 추상 클래스 |
FileInputStream FileOutputStream |
파일 입출력을 위한 바이트 스트림 클래스 |
DataInputStream DataOutputStream |
자바 기본형(primitive) 데이터를 입출력 하기 위한 클래스 |
BufferedInputStream BufferedOutputStream |
입출력 스트림에 버퍼링 기능을 추가한 클래스 |
PrintStream | System.out을 통해 콘솔로 출력하기 위한 클래스 |
InputStream의 메소드
메소드 | 설명 |
int available() | 현재 읽을 수 있는 바이트 수를 반환한다 |
void close() | 현재 열려있는 InputStream을 닫는다 |
void mark(int readlimit) | InputStream에서 현재의 위치를 표시해준다 |
boolean markSupported() | 해당 InputStream에서 mark()로 지정된 지점이 있는지에 대한 여부를 확인한다 |
abstract int read() | InputStream에서 한 바이트를 읽어서 int값으로 반환한다 |
int read(byte[] b) | byte[] b 만큼의 데이터를 읽어서 b에 저장하고 읽은 바이트 수를 반환한다 |
int read(byte[] b, int off, int len) | len만큼 읽어서 byte[] b의 off위치에 저장하고 읽은 바이트 수를 반환한다 |
void reset() | mark()를 마지막으로 호출한 위치로 이동 |
long skip(long n) | InputStream에서 n바이트만큼 데이터를 스킵하고 바이트 수를 반환한다 |
OutputStream의 메소드
메소드 | 설명 |
void close() | OutputStream을 닫는다 |
void flush() | 버퍼에 남아있는 출력 스트림을 출력한다 |
void write(byte[] b) | 버퍼의 내용을 출력한다 |
void write(byte[] b, int off, int len) | b배열 안에 있는 시작 off부터 len만큼 출력한다 |
abstract void write(int b) | 정수 b의 하위 1바이트를 출력한다 |
문자 단위 스트림(Character Stream)
Reader, Writer 둘 다 문자 데이터 기반 입출력의 최상위 클래스이다.
관련된 모든 텍스트 기반 입출력은 이 클래스를 상속받아서 만들어 진다.
문자 데이터를 입출력할 때 사용하는 문자 기반의 스트림이다.
오로지 문자 데이터를 주고 받기 위해 특화되어있다.
문자 기반 스트림은 기존의 바이트 기반 스트림에서
InputStream 을 Reader로, OutputStream을 Writer로 변경하면 사용할 수 있다.
Reader Writer |
입출력을 위한 문자 스트림의 최상위 추상 클래스 |
FileReader FileWriter |
파일 입출력을 위한 문자 스트림 클래스 |
BufferedReader BufferedWriter |
입출력 스트림에 버퍼링 기능을 추가해주는 스트림 |
PrintWriter | 출력을 위한 동작을 지원하는 문자 스트림 |
InputStreamReader OutputStreamWriter |
바이트와 문자 변환을 위한 입출력 스트림 |
보조 스트림
스트림의 기능을 보완하기 위해 나온 녀석이다. (입출력 성능 속도 향상, 데이터 포멧 등)
1. 실제 데이터를 주고 받지 않는다.
2. 데이터를 주고 받을 수 없기 때문에 먼저 스트림을 생성한 후 사용해야 한다.
아래의 코드는 보조 스트림 활용 예시이다.
DataInputStream dataInputStream = new DataInputStream( new FileInputStream("test.txt") );
표준 스트림 (System.in, System.out, System.err)
- Java에서 콘솔과 같은 표준 입출력 장치를 위해 System이라는 표준 입출력 클래스를 정의하고 있다.
- java.lang 패키지에 포함되어 있는 System 클래스는 표준 입출력을 위해 다음과 같은 클래스 변수를 제공한다.
System 클래스
클래스 변수 | 입출력 스트림 | 설명 |
System.in | InputStream | 표준 입력 스트림 / 키보드로 데이터 입력 받음 |
System.out | PrintStream | 표준 출력 스트림 / 모니터로 데이터 출력 시킴 - println() / print() / printf() |
System.err | PrintStream | 표준 에러 출력 스트림 |
표준 출력 스트림
- print()
- println()
- printf()
public class Example01 {
public static void main(String[] args) {
System.out.print("print");
System.out.println("println");
System.out.print("print와 println의 차이점입니다.");
}
}
printprintln
print와 println의 차이점입니다.
Process finished with exit code 0
public class Example01 {
public static void main(String[] args) {
System.out.printf("%d %d %d %d %d", 1,2,3,4,5);
}
}
1 2 3 4 5
Process finished with exit code 0
표준 입력 스트림
System.in은 콘솔로부터의 입력을 받는 표준 스트림을 가리키는 상수이다.
InputStream으로 byte 단위로 입력 받을 수 있다.
아래와 같이 System.in.read();는 int 형으로 입력 받는다.
import java.io.*;
public class Example01 {
public static void main(String[] args) throws IOException{
System.out.print("입력 : ");
int ch = System.in.read();
System.out.println("입력한 문자의 아스키 코드 값 : " + ch);
}
}
입력 : A
입력한 문자의 아스키 코드 값 : 65
Process finished with exit code 0
import java.io.*;
public class Example01 {
public static void main(String[] args) throws IOException{
System.out.print("입력 : ");
int ch = System.in.read();
System.out.println("출력 : " + (char)ch);
}
}
입력 : A
출력 : A
Process finished with exit code 0
여기서 한글을 입력한다면 아래와 같은 결과를 볼 수 있다.
입력 : 하
출력 : í
Process finished with exit code 0
- 한글은 유니코드로 2byte 단위이기 때문에 InputStream 으로는 1byte 밖에 읽지 못해 원하지 않는 값이 출력 된다.
InputStream 은 ByteStream 으로 한글을 입력받는데 있어 어려움이 있다.
이 경우에는 CharacterStream 인 InputStreamReader를 활용할 수 있다.
import java.io.*;
public class Example01 {
public static void main(String[] args) throws IOException{
System.out.print("입력 : ");
InputStreamReader inputStreamReader = new InputStreamReader(System.in);
int ch = inputStreamReader.read();
System.out.println("출력 : " + (char)ch);
}
}
입력 : 하
출력 : 하
Process finished with exit code 0
만약 2byte가 넘는 데이터를 읽고 싶다면 BufferedReader 를 활용 하자.
import java.io.*;
public class Example01 {
public static void main(String[] args) throws IOException{
System.out.print("입력 : ");
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
String ch = bufferedReader.readLine();
System.out.println("출력 : " + ch);
}
}
입력 : 하이하이 안녕하세요
출력 : 하이하이 안녕하세요
Process finished with exit code 0
혹은 Scanner 클래스를 사용할 수도 있다.
하지만, BufferedReader가 실무에서 더 중요하고 많이 쓰인다고 한다.
표준 에러 출력 스트림
표준 에러 출력 장치를 가리키는 상수이다.
파일 읽고 쓰기
File Write
파일을 쓸 때는 아래의 객체를 사용한다.
FileWriter, BufferedWriter
FileWriter 객체와 BufferedWrite 객체를 생성해준다.
여기서 FileWriter 객체를 생성할 때 생성자에 true를 주면 파일 이어서 쓰기가 가능하다.
default는 false (false로 하면 파일을 새로 쓰게 됨)
실행을 해보면, 파일 생성 후 파일 생성여부를 체크하도록 구현해두어
아래처럼 콘솔창에 "coco3o.txt 파일이 있습니다" 문자열이 출력된다.
coco3o.txt 파일이 해당 자바 프로젝트 경로에 파일이 생성된다.
File Read
파일을 읽을때는 아래 객체를 사용한다.
FileReader, BufferedReader
FileReader의 생성자에는 파일경로를 작성한다.
지금은 자바프로젝트 경로에 있으므로 파일명만 작성해도 읽어낼 수 있으나, 다른 경로에 있을 경우 상대경로를 입력해주면 된다.
아래와 같이 coco3o.txt 파일을 정상적으로 읽어 내었다.
파일에 문자열이 2번 작성된 것은 위의 File Writer를 같이 실행하였기 때문
references :
blog.naver.com/swoh1227/222244309304
eincs.com/2009/08/java-nio-bytebuffer-channel/
velog.io/@jaden_94/13%EC%A3%BC%EC%B0%A8-%ED%95%AD%ED%95%B4%EC%9D%BC%EC%A7%80-IO
'🌈Programming > Java' 카테고리의 다른 글
온라인 자바 스터디 # 15 - 람다식 (0) | 2021.03.04 |
---|---|
온라인 자바 스터디 #14 - 제네릭(Generic) (0) | 2021.02.25 |
[Java] String, StringBuffer, StringBuilder 차이와 장단점 (1) | 2021.02.04 |
온라인 자바 스터디 #12 - 어노테이션(Annotation) (0) | 2021.02.01 |
[Java] static 변수와 static 메소드 (0) | 2021.01.27 |
블로그의 정보
슬기로운 개발생활
coco3o