1. 스트림(Stream)
자바에서 데이터는 스트림을 통해 입출력된다. 스트림은 단일 방향으로 연속적으로 흘러가는 것을 말한다.
프로그램이 출발지인지(출력 스트림) 또는 도착지인지(입력 스트림)에 따라서 사용하는 스트림의 종류가 결정되며, 스트림을 사용하기 위해서는 java.io.* 패키지를 import 해줘야 한다.
1.1 입출력 스트림 종류
java.io 패키지에는 여러 가지 종류의 스트림 클래스를 제공하기 때문에 스트림을 사용하기 위해서는 해당 패키지를 import 해야한다. 이러한 스트림 클래스는 크게 두 종류로 구분되며, 최상위 클래스를 통해 종류를 판단할 수 있다.
1) 바이트(byte) 기반 스트림 (1byte) : 그림, 멀티미디어 등의 바이너리 데이터를 읽고 출력할 때 사용
2) 문자(character) 기반 스트림 (2byte) : 문자 데이터를 읽고 출력할 때 사용
1.2 바이트 출력 스트림 : OutputStream
바이트 기반 출력 스트림의 최상위 클래스로 모든 바이트 기반 출력 스트림 클래스는 OutputStream 클래스를 상속받는다.
7번째 줄과 같이 new 연산자를 사용해 FileOutputStream 생성자를 호출하여 "C://Temp/test1.db"와 연결된 스트림 객체를 만들어준다. 이 경우 프로그램과 해당 파일을 순회하는 스트림 객체가 만들어져 참조변수 os를 이용해 사용할 수 있다. 그 후 13~15번째 줄처럼 write() 메소드에 값이 저장된 변수 a, b, c를 인자로 주어 연결된 파일에 값을 출력한다. 그 후 출력 버퍼에 잔류하는 모든 바이트를 출력하는 flush() 메소드를 호출하고, 마지막으로 close() 메소드를 호출하여 출력 스트림을 닫는다.
앞의 예시와 다르게 해당 예시는 배열의 모든 바이트를 출력하는 방법이다. 9번째 줄과 같이 배열을 생성한 후 write() 메소드에 배열을 인자로 주어 연결된 파일에 출력한다. 출력 스트림을 사용한 경우 flush() 메소드를 사용하여 혹시라도 출력 버퍼에 잔류하고 있을 모든 바이트를 출력해야 하며 close() 메소드를 사용하여 출력 스트림을 닫아야 한다.
1.3 바이트 입력 스트림 : InputStream
바이트 기반 입력 스트림의 최상위 클래스로 모든 바이트 기반 입력 스트림은 InputStream 클래스 상속을 받아 만들어진다.
7번째 줄과 같이 new 생성자를 이용해 C:/Temp/test1.db와 연결된 FileInputStream 입력 스트림 객체를 만들어 준다. 10번째 줄과 같이 read() 메소드를 이용해 1byte씩 읽어온다. 이때 byte가 없고 끝이면 -1을 반환하기 때문에 파일의 끝에 도달하면 if문 안으로 들어가 while문을 종료한다. 마지막으로 입력 스트림을 닫는 close() 메소드를 호출한다.
7번째 줄과 같이 특정 파일과 연결된 입력 스트림 FileInputStream 객체를 만들어 준다. 그 후 9번째 줄과 같이 배열에 읽어온 바이트를 저장하기 위한 배열을 선언한다. 12번째 줄과 같이 read() 메소드에 인자로 배열을 넣어준다. 이때 인자로 넣어준 배열에 읽어온 값들이 저장되고 읽어온 바이트의 수가 리턴되어 readbyteNum에 저장된다. 이때 읽어온 바이트 수가 있기 때문에 if문이 실행되지 않고 for문이 실행된다. for문은 읽어온 바이트 수 만큼 반복되고 배열에 저장된 바이트를 출력한다. for문이 끝나고 다시 while문이 반복된다. 그 다음 read() 메소드를 통해 파일을 읽을 경우 파일 끝에 도달했기 때문에 -1이 반환되어 다음 if문이 실행되어 while문이 종료된다. 마지막으로 입력 스트림을 닫는 close() 메소드를 호출하면서 마무리한다.
1.4 문자 출력 스트림 : Writer
문자 기반 출력 스트림의 최상위 클래스로 모든 문자 기반 출력 스트림 클래스는 Writer 클래스를 상속받아 만들어진다.
7번째 줄과 같이 new 연산자를 이용해 생성자 호출하여 test7.txt 파일과 연결된 출력 스트림 FileWriter 객체를 만들어 준다. 그 후 출력 스트림 객체의 주소를 레퍼런스 변수인 writer에 대입하여 준다. 13~15번 째 줄과 같이 write() 메소드를 사용하여 한 문자씩 출력하여 준 뒤 flush()를 통해 출력 버퍼에 잔류하는 모든 문자를 출력하고 close()를 마지막으로 출력 스트림을 닫아준다.
7번째 줄과 같이 파일과 연결된 FileWriter 객체를 생성하고 객체의 주소를 레퍼런스 변수 writer에 대입한다. 9번째 줄과 같이 char 타입의 배열을 생성해준 후 11번째 줄과 같이 write() 메소드를 사용해 배열을 출력한다. 이때 첫 번째 인자값은 배열의 주소를 담고 있고 두 번째 인자값은 출력할 배열의 인덱스, 세 번째 인자값은 두 번째 인자값으로 부터 몇 개를 출력할 것인지의 의미를 가지고 있다. 결과적으로 연결된 파일에 B, C, D가 출력된다.
1.5 문자 입력 스트림 : Reader
문자 기반 입력 스트림의 최상위 클래스로 모든 문자 기반 입력 스트림은 Reader 클래스의 상속을 받아 만들어진다.
7번째 줄과 같이 test7.txt 파일과 연결된 입력 스트림 FileReader 객체를 생성 후 레퍼런스 변수 reader에 저장한다. 10번째 줄과 같이 read() 메소드를 이용해 1개의 문자를 int 타입으로 읽어온다.(A=65, B=66, C=67) 때문에 12번째 줄과 같이 char 타입으로 변환시 문자를 얻을 수 있다. 9~13번째 줄은 while 문으로 반복해서 문자(2byte)를 읽어오는데, 만약 파일 끝에 도달하면 read()는 -1을 리턴하기 때문에 11번째 줄에서 break를 만나 반복문을 종료한다. 마지막으로 close()로 입력 스트림을 닫는다.
7번째 줄과 같이 new 연산자를 이용해 생성자를 호출하여 test9.txt 파일과 연결된 문자 입력 스트림 FileReader 객체를 생성한다. 그 후 레퍼런스 변수 reader에 문자 입력 스트림의 주소를 대입한다. 12번째 줄과 같이 read() 메소드에 인자값으로 문자 배열을 주면 배열의 길이만큼 문자를 읽고 배열에 저장하고 난 후에 읽은 문자 수를 반환하여 readCharNum에 저장한다. 이때 파일 끝에 도달하면 read는 -1을 리턴하기 때문에 14번째 줄에서 if에서 true가 되어 break를 만나 반복문을 종료하게 된다.
2. 보조 스트림
다른 스트림과 연결되어 편리한 기능을 제공하는 스트림으로 자체적으로 입출력을 수행할 수 없기 때문에 입출력 소스와 바로 연결되는 InputStream, OutputStream, Reader, Writer 등에 연결하여 수행한다. 아래와 같은 경우 프로그램은 입력 스트림으로부터 직접 데이터를 읽지 않고, 보조 스트림에서 제공하는 기능을 이용하여 데이터를 읽는다. 출력 스트림도 마찬가지이다.
보조스트림 변수 = new 보조스트림(연결 스트림) ;
*소스 스트림이 바이트 기반 스트림이면서 입출력 데이터가 문자일 경우 Reader 와 Writer로 변환해서 사용하는 것이 편리함
2.1 OutputStreamWriter
바이트 기반 출력 스트림에 연결되어 문자 출력 스트림인 Writer로 변환하는 보조 스트림이다.
//FileOutputStream fos = new FileOutputStream("C:/Temp/test1.txt");
//보조 스트림은 자체적으로 수행될 수 없기에 이와 같이 출력 스트림이 있어야 함. 자세한 사용 법은 아래에서 다룸
Writer writer = new OutputStreamWriter(바이트 기반 출력 스트림);
2.2 InputStreamReader
바이트 기반 입력 스트림에 연결되어 문자 입력 스트림인 Reader로 변환하는 보조 스트림
//FileInputStream fis = new FileInputStream("C:/Temp/test1.txt");
Reader reader = new InputStreamReader(fis);
main에서 7번째 줄로 인해 write함수가 호출되고 인자값으로 "문자 변환 스트림을 사용합니다."가 주어진다. 13번째 줄과 같이 test1.txt와 연결된 출력 스트림 객체 FileOutputStream를 생성한다. 그 후 new 연산자를 이용해 OutputStreamWriter의 생성자를 호출하고 생성자의 인자값으로 출력 스트림 객체의 번지가 저장된 변수를 준다. 이때 출력할 문자가 2byte이기 때문에 보조 스트림을 2byte를 사용하는Writer를 이용한다. 이 과정을 통해 FileOutputStream에 보조 스트림OutputStreamWriter가 연결된다. 그 뒤 15번째 줄과 같이 write() 메소드를 이용해 OutputStreamWriter 보조 스트림을 이용해서 문자를 출력한다. -> write(String str)이므로 문자열 전체를 보냄(출력 스트림을 사용할 때는 flush와 close 꼭 해주기!!)
다시 main으로 돌아와 13번째 줄에서 read 함수가 호출된다. 26번째 줄과 같이 test1.txt와 연결된 입력 스트림 객체 FileInputStream가 생성된다. 그 후 보조스트림 new 연산자의 인자로 FileInputStream 객체를 주어 InputStreamReader 보조 스트림을 연결한다. 28번째 줄에는 읽어오는 문자를 저장할 char 타입의 배열을 생성한다. 그 후 29번째 줄과 같이 보조스트림의 번지를 담고있는 변수에 접근 연산자를 이용해 read() 메소드의 인자로 배열을 주면 배열에 유니코드 형식으로 문자가 입력되고 읽어온 문자의 수가 반환되어 readCharNum에 대입된다. 31번째 줄과 같이 배열의 0인덱스에서 읽어온 값만큼 String 타입의 data에 저장되고 return을 만나 반환되어 main의 data에 대입된다. 마지막으로 main의 14번째 줄을 통해 읽어온 값이 출력된다.
3. 성능 향상 보조 스트림
자바에는 메모리 버퍼를 추가로 제공하여 프로그램의 실행 성능을 향상 시킬 수 있는 성능 향상 보조 스트림이 있다. 바이트 기반 스트림은 BufferInputStream, BfferedOutputStream이 있고 문자 기반 스트림은 BufferReader, BufferedWriter가 있다. 이러한 보조 스트림들은 프로그램에서 전송한 데이터를 내부 버퍼에 샇아두었다가 버퍼가 꽉 차면 한꺼번에 보내는 방식이다.
아래 사용 예시와 같이 생성자의 인자로 준입력 스트림을 주어 연결하여 사용할 수 있다.
BufferedOutStream bos = new BufferedOutputStream(바이트 기반 출력 스트림);
BufferedWriter bw = new BufferedWriter(문자 기반 출력 스트림);
8번째, 14번째 줄과 같이 클래스명.class.getResource("파일").getPath()를 통해 입력 스트림이 읽어올 파일이 위치한 절대 경로를 originalFilePath1과 originalFilePath2에 문자열로 저장한다. 10번째, 16번째 줄과 같이 출력 스트림이 문자를 출력할 파일의 위치를 targetFilePath1, targetFilePath2에 문자열로 저장한다. 성능 테스트를 위해 fis(FileInputStream)와 fos(FileOutputStream)는 기반 스트림만을 사용하고 fis2(FileInputStream)와 fos2(FileOutputStream)는 성능 향상 보조 스트림 bis(BufferedInputStream)와 bos(BufferedOutputStream)와 연결한다.
그 후 22번째, 25번째 줄과 같이 copy() 함수를 호출하고 인자로 스트림 객체를 준다. 31번째, 38번째 줄과 같이 수행 시간을 측정하기 위해 nanoTime() 메서드를 사용한다. while문에 들어가 매개변수 is(입력 스트림 객체의 주소가 들어감)를 이용해 read() 메소드로 파일을 읽어오고 os(출력 스트림 객체의 주소가 들어감)를 이용해 write() 메소드로 파일을 출력한다. 마지막으로 end-start 구문을 통해 복사에 걸린 시간을 main에서 리턴받고 성능을 확인한다. 그 결과 버퍼를 사용했을 때가 더 빠른 결과가 나온다는 것을 알 수 있다.
4. 객체 입출력 보조 스트림 (ObjectOutputStream, ObjectInputStream)
객체 입출력 보조 스트림을 연결하면 메모리에 생성된 객체를 파일 또는 네트워크로 출력할 수 있다.
자바는 java.io.Serializable 인터페이스를 구현한 객체만 직렬화한다. 때문에 아래와 같이 클래스를 선언 시 implements Serializable을 추가해야 한다.
public class XXX implements Serializable {...}
5. 입출력 관련 API
자바에서 제공하는 System 클래스의 in 정적 필드를 1바이트씩 읽어올 수 있는 InputStream 객체에 연결하고, 2바이트씩 읽어올 수 있는 Reader로 변환한 후 다시 BufferedReader를 연결한다. (7~9번째 줄) while문으로 들어가 readLine()로 인해 라인 단위, enter를 만날 때 까지 인력을 받아 lineStr에 저장하고 저장된 값이 q 또는 quit인지를 확인 후 아니라면 입력 받은 문자열을 출력한다.
'Develop' 카테고리의 다른 글
[Dev Environment]오라클 SQL Developer 설치 및 데이터베이스 연결 (2) | 2023.09.09 |
---|---|
[Dev Environment] Intellij(인텔리제이) 자동 빌드 설정 - 2023.2 ver (0) | 2023.08.14 |
[Concept] JAVA (제네릭스, 컬렉션 프레임워크, Set, Map, Stack, Queue) (0) | 2022.07.04 |
[Concept] JAVA (java.lang 패키지, java.system 패키지, 동기화, 스레드) (0) | 2022.06.22 |
[Concept] JAVA (인터페이스, 중첩 클래스, 익명 객체, 예외) (0) | 2022.06.16 |