1. 스트림 ( Stream )
스트림이란 프로그램과 I/O 객체를 연결하여 데이터를 송수신 하는 길을 말합니다.
InputStream은 데이터를 읽어 들이는 객체이고, OutputStream은 데이터를 써서 보내는 객체입니다.
데이터를 어떤 방식으로 전달하느냐에 따라 스트림은 두가지로 나뉩니다.
- 바이트 스트림( Byte Stream )
- binary 데이터를 입출력하는 스트림입니다.
- 이미지, 동영상 등을 송수신할 때 주로 사용합니다.
- 문자 스트림( Character Stream )
- 말 그대로 text 데이터를 입출력하는데 사용하는 스트림입니다.
- HTML 문서, 텍스트 파일을 송수신할 때 주로 사용합니다.
자바에서는 스트림과 관련하여 추상 클래스를 지원하고 있습니다.
- 바이트 스트림
- InputStream / OutputStream
- byte 기반 input / output stream의 최고 조상
- ByteArrayInputStream / ByteArrayOutputStream
- byte array( byte[] )에 대한 데이터를 입출력 하는 클래스
- FileInputStream / FileOutputStream
- 파일( File )에 대한 데이터를 입출력 하는 클래스
- 문자 스트림
- Reader / Writer
- Character 기반 input / output stream의 최고 조상
- FileReader / FileWriter
- 문자 기반의 파일을 입출력 하는 클래스
2. 보조 스트림
보조 스트림이란 "프로그램에서" 파일을 읽기/쓰기 할 수 있도록 해주며, 위에서 소개한 클래스들은 주 스트림으로써 "외부에서" 파일 읽기/쓰기를 수행합니다.
다음 클래스들은 보조 스트림의 종류들입니다.
- FilterInputStream / FilterOutputStream
- byte 기반 보조 스트림의 최고 조상
- BufferedInputStream / BufferedOutputStream
- 입출력 효율을 높이기 위해 버퍼( byte[] )를 사용하는 보조스트림
- BufferedReader / BufferedWriter
- 입출력 효율을 높이기 위해 버퍼( char[] )를 사용하는 보조스트림
- 라인 단위의 입출력에 용이
- InputStreamReader / OutputStreamReader
- byte 기반 스트림을 character 기반 스트림처럼 쓸 수 있도록 함
- 인코딩 변환 가능
이 밖에도 Java에서 제공하는 스트림의 종류는 매우 많은데, 전부 다룰 수는 없을 것 같습니다.
이 글에서는 위에서 소개한 클래스들을 사용하는 예제들을 살펴보려고 합니다.
3. ByteArrayStream
public class IOExample {
public static void main(String[] args) {
byte[] src = {0, 1, 2, 3};
byte[] dest = null;
try{
InputStream is = new ByteArrayInputStream(src);
OutputStream os = new ByteArrayOutputStream();
int data = -1;
while( (data = is.read()) != -1 ){
os.write(data);
}
dest = ((ByteArrayOutputStream)os).toByteArray();
System.out.println(Arrays.toString(dest)); // [0, 1, 2, 3]
}
catch (IOException e){
e.printStackTrace();
}
}
}
4. FileStream
public class FileCopy {
public static void main(String[] args) {
InputStream is = null;
OutputStream os = null;
try{
// ./는 현재경로를 의미합니다.
is = new FileInputStream("./asd.jpg");
os = new FileOutputStream("./sad.jpg");
int data = -1;
while( (data = is.read()) != -1 ){
os.write(data);
}
}
catch (FileNotFoundException e){
System.out.println("파일 없음");
e.printStackTrace();
}
catch (IOException e){
System.out.println("I/O 에러");
e.printStackTrace();
}
finally {
// 예외가 발생했을 때도 스트림을 닫아야 하므로 finally에서 스트림을 닫아줍니다.
try {
if( is != null ){
is.close();
}
if( os != null ){
os.close();
}
}
catch ( IOException e){
e.printStackTrace();
}
}
}
}
5. FileReader
public class FileReaderTest {
public static void main(String[] args) {
Reader reader = null;
InputStream is = null;
try{
reader = new FileReader("./hello.txt");
int count = 0;
int data = -1;
while( (data = reader.read()) != -1 ){
count++;
System.out.println((char)data);
}
System.out.println("읽은횟수 : " + count);
}
catch (FileNotFoundException e){
System.out.println("파일 없음");
e.printStackTrace();
}
catch (IOException e){
System.out.println("I/O 에러");
e.printStackTrace();
}
finally {
// 예외가 발생했을 때도 스트림을 닫아야 하므로 finally에서 스트림을 닫아줍니다.
if( reader != null ){
try {
is.close();
}
catch ( IOException e){
e.printStackTrace();
}
}
}
}
}
기본적으로 Character 기반 스트림은 UTF-8 인코딩이 되어있습니다.
6. 데코레이션 패턴
객체를 장식하는 디자인 패턴으로 실행 중에 클래스를 덧붙이는 패턴입니다. ( 참고 )
데코레이터 패턴을 사용하면 부모 클래스를 건드리지 않으면서 실행중에 원하는 기능을 추가할 수 있습니다.
ex) Parent = new Child( new Child() );
보조스트림을 사용하기 위해서는 데코레이션 패턴을 사용합니다.
외부의 파일은 Byte 기반 스트림 ( 주 스트림 - FileInputStream )으로 읽고, Character 기반 스트림 보조스트림 – InputStream )으로 인코딩하여 읽어들입니다.
즉, 실제적인 파일 읽기를 수행하는 객체는 보조스트림인 InputStream입니다.
데코레이션 패턴이 중요한 것은 아니므로, 아래 예제를 통해 보조스트림을 사용하는 방법만 다루도록 하겠습니다.
7. BufferedOutputStream
public class BufferedOutputStreamTest {
public static void main(String[] args) {
BufferedOutputStream bos = null;
try{
/* FileOutputStream으로 주 스트림을 생성하고, 보조스트림 BufferedOutputStream을 주 스트림에 할당합니다.
* 이것이 데커레이터 패턴이며, 파일을 읽어들이는 것은 주스트림인데, 실제로 파일을 다루는 것은 보조스트림이기 때문입니다.
*/
bos = new BufferedOutputStream(new FileOutputStream("./hello.txt"));
// hello.txt 파일에 문자 1~9까지 write
for (int i = 1; i <= 9; i++) {
bos.write(i);
}
// buffer가 끝나지 않은 상태에서 플러시를 강제로 하는 메서드입니다.
// 스트림을 close() 하면 자동으로 플러시됩니다.
// bos.flush();
}
catch (FileNotFoundException e){
System.out.println("파일 없음");
e.printStackTrace();
}
catch (IOException e){
System.out.println("I/O 에러");
e.printStackTrace();
}
finally {
// 예외가 발생했을 때도 스트림을 닫아야 하므로 finally에서 스트림을 닫아줍니다.
if( bos != null ){
try {
bos.close();
}
catch ( IOException e){
e.printStackTrace();
}
}
}
}
}
8. BufferReader
public class BufferReaderTest {
public static void main(String[] args) {
BufferedReader br = null;
try{
br = new BufferedReader(new FileReader("./src/io/BufferReaderTest.java"));
String line = null;
while( (line = br.readLine()) != null ){
System.out.println(line);
}
}
catch (FileNotFoundException e){
System.out.println("파일 없음");
e.printStackTrace();
}
catch (IOException e){
System.out.println("I/O 에러");
e.printStackTrace();
}
finally {
// 예외가 발생했을 때도 스트림을 닫아야 하므로 finally에서 스트림을 닫아줍니다.
if( br != null ){
try {
br.close();
}
catch ( IOException e){
e.printStackTrace();
}
}
}
}
}
9. InputStreamReader
public class InputStreamReaderTest {
public static void main(String[] args) {
Reader reader = null;
try{
/* file을 읽을 때 MS949로 인코딩해서 읽는 방식입니다.
* 인코딩을 변경하는 객체는 InputStreamReader라는 것을 전달하기 위해 MS949로 인코딩 했습니다.
*/
reader = new InputStreamReader(new FileInputStream("./hello.txt"), "MS949");
int data = -1;
while( (data = reader.read()) != -1 ){
System.out.println((char)data);
}
}
catch (UnsupportedEncodingException e) {
System.out.println("지원하지 않는 인코딩입니다.");
e.printStackTrace();
}
catch (FileNotFoundException e) {
System.out.println("파일 없음");
e.printStackTrace();
}
catch (IOException e) {
System.out.println("I/O 에러");
e.printStackTrace();
}
finally {
// 예외가 발생했을 때도 스트림을 닫아야 하므로 finally에서 스트림을 닫아줍니다.
if( reader != null ){
try {
reader.close();
}
catch ( IOException e){
e.printStackTrace();
}
}
}
}
}
10. File
public class PhoneList {
public static void main(String[] args) {
BufferedReader br = null;
/* File 객체는 예외가 발생하지 않음.
* File 객체는 스트림과 관련된 것이 아니라, 파일 정보에 대한 내용을 담고 있는 객체입니다.
*/
File file = new File("./hello.txt");
if (!file.exists()) {
System.out.println("파일이 존재하지 않습니다.");
return;
}
System.out.println(file.getName());
System.out.println(file.getAbsolutePath());
System.out.println(file.length());
System.out.println(new SimpleDateFormat("yyyy-mm-dd hh:mm:ss").format(file.lastModified()));
}
}
이상으로 자바에서 파일 입출력하는 다양한 클래스들과 그 예제들을 살펴보았습니다.
자바에서 입출력 프로그래밍의 핵심은 주스트림과 보조스트림을 구분하는 것입니다.
- 주스트림
- 파일을 읽음
- Byte 기반 스트림
- ByteArrayInputStream, FileInputStream, FilterInputStream
- Character 기반 스트림
- FileReader
- 보조스트림
- 프로그램에서 파일을 읽거나 쓰는 주체
그 밖에 주 스트림과 보조 스트림을 구분하는 방법은 API 문서를 보고, 상속 관계를 확인하거나 생성자를 확인하여 구별하면 됩니다.
'웹 프로그래밍 > ------ Java ' 카테고리의 다른 글
[Java] JDBC 사용하기 ( MySQL ) (12) | 2018.03.11 |
---|---|
[Java] 채팅 프로그램( 소켓 프로그래밍, 멀티 쓰레드 ) (31) | 2018.03.11 |
[Java] 핵심정리 (1) - String 객체를 중심으로 (2) | 2018.03.10 |