본문 바로가기
JAVA/이재환의 자바 프로그래밍 입문

[Java] Ch.24 입출력 스트림

by ♡˖GYURI˖♡ 2023. 10. 26.
728x90

자바의 입출력 스트림

자바에서 스트림이란 자료 흐름이 물의 흐름과 같다는 의미에서 사용됨

처음에는 자바 입출력 모델에서 발생하는 데이터 입출력을 스트림이라고 했는데, 컬렉션 객체를 다루기 위한 스트림이 자바 8에서 추가되면서, 기존의 입출력 모델에서 발생하는 스트림을 입출력 스트림이라고 구분하여 부르게 되었음

 

자바 입출력 모델

  • 파일에서의 입출력
  • 키보드와 모니터의 입출력
  • 그래픽카드, 사운드카드의 입출력
  • 프린터, 팩스와 같은 출력 장치의 입출력
  • 인터넷으로 연결되어 있는 서버 또는 클라이언트의 입출력

 

입출력 장치는 매우 다양하기 때문에 장치에 따라 입출력 부분을 일일이 다르게 구현하면 프로그램 호환성과 생산성이 떨어질 수 밖에 없음

그래서 자바는 입출력 장치를 구분하지 않고 일관성 있게 프로그램을 구현할 수 있도록 위와 같은 자바 입출력 모델의 모든 입출력을 입출력 스트림을 통해 처리하는 기능을 제공함

 

입출력 스트림

  • 대상 기준에 따라 입력 스트림, 출력 스트림
  • 자료의 종류에 따라 바이트 단위 스트림, 문자 단위 스트림
  • 기능에 따라 기반 스트림, 보조 스트림(필터 스트림)

 

 

입출력 스트림의 구분

입력 스트림과 출력 스트림

  • 입력 스트림 : 대상으로부터 자료를 읽어들이는 스트림
  • 출력 스트림 : 대상으로 자료를 출력하는 스트림

스트림 종류

종류
입력 스트림 FileInputStream, FileReader, BufferedInputStream, BufferedReader 등
출력 스트림 FileOutputStream, FileWriter, BufferedOutputStream, BufferedWriter 등

 

바이트 단위 스트림과 문자 단위 스트림

  • 바이트 단위 스트림 : 동영상, 음악 파일 등을 읽고 쓸 때 사용
  • 문자 단위 스트림 : 바이트 단위로 자료를 처리하면 문자는 깨짐, 2바이트 단위로 처리하도록 구현된 스트림

스트림 종류

종류
바이트 스트림 FileInputStream, FileOutputStream, BufferedInputStream, BufferedOutputStream 등
문자 스트림 FileReader, FileWriter, BufferedReader, BufferedWriter 등

 

기반 스트림과 보조 스트림

  • 기반 스트림 : 대상에 직접 자료를 읽고 쓰는 기능의 스트림
  • 보조 스트림 : 직접 읽고 쓰는 기능은 없이 추가적인 기능을 더해주는 스트림, 항상 기반 스트림이나 또 다른 보조 스트림을 생성자 매개변수로 포함함

스트림 종류

종류
기반 스트림 FileInputStream, FileOutputStream, FileReader, FileWriter 등
보조 스트림 InputStreamReader, OutputStreamWriter, BufferedInputStream, BufferedOutputStream 등

 

 

파일 대상 입출력 스트림 생성

파일 대상 출력 스트림 생성

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;

public class Ex01_FileWrite1
{
    public static void main(String[] args) throws IOException 
    {
        OutputStream out = new FileOutputStream("data.txt");
        out.write(65);    // ASCII 코드 65 = 'A'
        out.close(); 
    }
}

파일을 하드디스크에 만들 때 예외가 발생할 수 있기 때문에 throws를 이용해 예외 처리를 넘기고 있음

 

입출력 스트림 예외 직접 처리

예외를 넘기지 않고 직접 처리하는 코드

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;

public class Ex02_FileWrite2
{
    public static void main(String[] args)
    {
        OutputStream out = null;

        try 
        {
            out = new FileOutputStream("data.txt");
            out.write(65);    // ASCII 코드 65 = 'A'
            //out.close();
        }
        catch (IOException e) 
        {
            
        }
        finally 
        {
            if (out != null) 
            {
                try 
                {
                    out.close(); 
                }
                catch (IOException e2) 
                {

                }
            }
        }
    }
}

에러가 나도 close() 메서드를 확실히 부르고 종료하기 위해 finally 구문 쪽으로 close() 메서드를 옮김

 

입출력 스트림 예외 처리 개선

try~with~resource를 적용하여 작성하면 보다 간단하게 처리할 수 있음

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;

public class Ex03_FileWrite3
{
    public static void main(String[] args)
    {
        try (OutputStream out = new FileOutputStream("data.txt"))
        {
            out.write(65);    // ASCII 코드 65 = 'A'
        }
        catch(IOException e)
        {
            e.printStackTrace();       
        }
    }
}

파일 리소스를 try 다음 소괄호 안에서 다루고 있음

이렇게 리소스를 열어서 스트림을 생성하면 따로 닫아주지 않아도 자바에서 자동으러 처리함

 

파일 대상 입력 스트림 생성

작성된 파일 읽기

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

public class Ex04_FileRead
{
    public static void main(String[] args)
    {
        try (InputStream in = new FileInputStream("data.txt")) 
        {
            int dat = in.read();
            System.out.println(dat);	// 읽은 데이터를 그냥 출력 -> 십진수
            System.out.printf("%c \n", dat);	// 문자 포맷 %c 지정 -> 글자
        } 
        catch(IOException e) 
        {
            e.printStackTrace();       
        }      
    }
}

 

바이트 단위 입력 및 출력 스트림 이용 파일 복사

한 바이트를 쓰고 한 바이트를 읽는 것을 연속적으로 처리하면 파일 복사도 가능

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.time.Duration;
import java.time.Instant;

public class Ex05_FileCopy1
{
    public static void main(String[] args)
    {
        String src = "./src/Ex04_FileRead.java";
        String dst = "FileRead1.txt";
		
        // try 다음 소괄호 내에 리소스 여러 개 지정 가능, 세미콜론으로 구분
        try (InputStream in = new FileInputStream(src);
            OutputStream out = new FileOutputStream(dst)) 
        {
            Instant start = Instant.now();	// 복사에 걸리는 시간 측정을 위해 현재 시각을 구해 옴

            int data;
            while(true) 
            {
                data = in.read();             
                if(data == -1)
                    break;             
                out.write(data);
            }
            
            Instant end = Instant.now();
            System.out.println("복사에 걸린 시간: " +
                Duration.between(start, end).toMillis());	// 1000ms = 1sec
        }
        catch(IOException e) 
        {
            e.printStackTrace();       
        }
    }
}

 

버퍼를 이용한 파일 복사

입출력 스트림의 데이터를 한 바이트씩 읽고 쓰는 방식은 입출력 I/O가 많이 발생

→ 메모리를 이용하여 버퍼에 저장해서 한 번에 읽고 쓰는 방식으로 시간 단축 가능

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.time.Duration;
import java.time.Instant;

public class Ex06_FileCopy2
{
    public static void main(String[] args)
    {
        String src = "./src/Ex04_FileRead.java";
        String dst = "FileRead2.txt";

        try (InputStream in = new FileInputStream(src);
            OutputStream out = new FileOutputStream(dst)) 
        {
            byte[] buf = new byte[1024];
            int len;

            Instant start = Instant.now();

            while (true) 
            {
            	// 버퍼 크기만큼 한 번에 읽기, 크기가 버퍼보다 작으면 그 크기까지만 읽음
                len = in.read(buf);	          
                if (len == -1)
                    break;            
                // 읽어들인 버퍼 크기만큼 출력 스트림에 보내 파일 작성
                out.write(buf, 0, len);	
            }
            
            Instant end = Instant.now();
            System.out.println("복사에 걸린 시간: " +
                Duration.between(start, end).toMillis());
        }
        catch(IOException e) 
        {
            e.printStackTrace();       
        }
    }
}

 

 

보조 스트림

버퍼링 기능을 제공하는 필터 스트림

보조 스트림은 단독으로 사용할 수 업고 기반 스트림에 더해서 같이 사용하게 됨

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.time.Duration;
import java.time.Instant;

public class Ex07_FileCopy3
{
    public static void main(String[] args)
    {
        String src = "./src/Ex04_FileRead.java";
        String dst = "FileRead3.txt";

	// 기반 스트림 : FileInputStream, 보조 스트림 : BufferedInputStream
        try (BufferedInputStream in = 
                   new BufferedInputStream(new FileInputStream(src));
             BufferedOutputStream out = 
                   new BufferedOutputStream(new FileOutputStream(dst))) 
        {
            Instant start = Instant.now();

            int data;
            while (true) 
            {
            	// 한 바이트씩 읽고 있지만 보조 스트림에 의해 내부적으로는 버퍼링이 되게 됨
                data = in.read();	
                if(data == -1)
                    break;             
                out.write(data);
            }

            Instant end = Instant.now();
            System.out.println("복사에 걸린 시간: " +
                Duration.between(start, end).toMillis());
        }
        catch(IOException e) 
        {
            e.printStackTrace();       
        }
    }
}

 

 

문자 스트림

FileReader나 FileWriter 클래스를 사용하게 되면 입출력 스트림에서 두 바이트씩 데이터를 처리해줌

import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;

public class Ex08_TextWrite
{
    public static void main(String[] args)
    {
        try (Writer out = new FileWriter("text.txt")) 
        {
            for (int ch = (int)'A'; ch < (int)('Z'+1); ch++)
                out.write(ch);
	    // 캐리지 리턴 값 저장 : 캐리지 리턴 \r은 현재 위치를 나타내는 커서를 맨 앞으로 이동시킴
            out.write(13);	
            // 라인 피드 값 저장 : 라인 피트 \n은 커서의 위치를 아랫줄로 이동시킴
            out.write(10);	
            // 둘을 합치면 엔터~~~~
            
            // 소문자 a 부터 z 까지
            for(int ch = (int)'A'+32; ch < (int)('Z'+1+32); ch++)
                out.write(ch);

            out.write(13);
            out.write(10);
            
            out.write("동해물과 백두산이 마르고 닳도록");
            out.write("\r\n");
            out.write("하느님이 보우하사 우리나라 만세");
            out.write("\r\n");
        }
        catch(IOException e)
        {
            e.printStackTrace();       
        }
    }
}

 

FileReader

import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;

public class Ex09_TextRead
{
    public static void main(String[] args)
    {
        try (Reader in = new FileReader("text.txt"))
        {
            int ch;

            while(true) 
            {
                ch = in.read();
                if (ch == -1)
                    break;
                System.out.print((char)ch);
            }
        }
        catch(IOException e) 
        {
            e.printStackTrace();       
        }
    }
}
  • 문자를 스트림으로부터 하나 읽어 int형 변수에 대입
  • 2바이트인 char형을 왜 4바이트인 int형에 저장하나요? → 데이터를 더 이상 읽을 수 없을 때 -1 을 반환받기 위해서
  • (-1은 char형 데이터 범위에 없는 값임)
  • 읽은 데이터를 char로 다시 형변환하여 화면으로 출력

 

BufferedWriter

문자 스트림도 버퍼링 처리를 위하여 기반 스트림에 보조 스트림을 더할 수 있음

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;

public class Ex10_BufferedWriter
{
    public static void main(String[] args)
    {
        String str1 = "동해물과 백두산이 마르고 닳도록";
        String str2 = "하느님이 보우하사 우리나라 만세.";

        try (BufferedWriter bw = 
                new BufferedWriter(new FileWriter("text2.txt"))) 
        {
            // 문자열의 크기만큼 버퍼링하여 한 번에 출력 스트림으로 파일에 저장
            bw.write(str1, 0, str1.length());	
            bw.newLine();	// 줄바꿈 문자를 스트림으로 저장
            bw.write(str2, 0, str2.length()); 
        }
        catch(IOException e) 
        {
            e.printStackTrace();       
        }
    }
}

 

BufferedReader

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class Ex11_BufferedReader
{
    public static void main(String[] args)
    {
        try (BufferedReader br = new BufferedReader(new FileReader("text2.txt"))) 
        {
            String str;

            while (true) 
            {
                str = br.readLine();
                if (str == null)
                    break;
                System.out.println(str);
            }
        }
        catch(IOException e) 
        {
            e.printStackTrace();       
        }
    }
}

 

 

IO 스트림 기반의 인스턴스 저장

자바 가상 머신의 메모리에 있는 객체 데이터를 바이트 형태로 변환하는 기술인 직렬화 기능을 이용하면 객체 자체를 저장할 수도 있음

 

직렬화(serialization)

객체의 상태를 그대로 저장하거나 다시 복원(deserialization)하는 것

ObjectInputStream과 ObjectOutputStream을 사용하여 파일에 쓰거나 네트워크로 전송할 수 있음

생성자 설명
ObjectInputStream(InputStream in) InputStream을 생성자의 매개변수로 받아 ObjectInputStream 생성
ObjectOutputStream(OutputStream out) OutputStream을 생성자의 매개변수로 받아 ObjectOutputStream 생성

 

  • 직렬화는 객체의 내용 중 private이 선언된 부분이 있더라도 외부로 내용이 유출되는 것이므로 프로그래머가 직렬화 의도를 표시해야 함
  • 이 때 사용하는 것이 java.io.Serializable 인터페이스
  • 이 인터페이스는 구현할 추상 메서드가 없음
  • 직렬화 의도를 밝히기 위해 인터페이스를 적용하는 것이기 때문에 마커 인터페이스(marker interface)라 부름

객체를 저장하는 클래스

public class Ex12_Unit implements java.io.Serializable 
{
    private static final long serialVersionUID = 1L;
    private String name;

    public Ex12_Unit(String name) 
    {
        this.name = name;
    }

    public String getName() 
    {
        return name;
    }
}

serialVersionUID는 직렬화에 사용되는 고유 아이디인데, 선언하지 않으면 JVM에서 디폴트로 자동 생성

하지만 사용하지 않으면 이클립스에서 워닝이 보이게 되므로 선언하고 사용함

 

ObjectOutputStream

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

public class Ex13_ObjectOutputStream
{
    public static void main(String[] args)
    {
        Ex12_Unit unit1 = new Ex12_Unit("Marine");
        Ex12_Unit unit2 = new Ex12_Unit("Medic");

        try (ObjectOutputStream oos = 
                new ObjectOutputStream(new FileOutputStream("Object.bin"))) 
        {
            oos.writeObject(unit1);
            oos.writeObject(unit2);
        }
        catch(IOException e) 
        {
            e.printStackTrace();       
        }
    }
}

문자를 저장한 것이 아니고 객체를 저장한 것이기 때문에 에디터에서 일반적인 텍스트 문서처럼 열어볼 수 없음

 

ObjectInputStream

ObjectOutputStream을 이용하여 객체를 저장한 경우 ObjectInputStream으로 읽어서 객체를 복원해야 정보를 읽을 수 있음

import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;

public class Ex14_ObjectInputStream
{
    public static void main(String[] args)
    {
        try (ObjectInputStream ois = 
                new ObjectInputStream(new FileInputStream("Object.bin"))) 
        {
            Ex12_Unit unit1 = (Ex12_Unit) ois.readObject();
            System.out.println(unit1.getName());

            Ex12_Unit unit2 = (Ex12_Unit) ois.readObject();
            System.out.println(unit2.getName());            
        }
        catch(ClassNotFoundException e) 
        {
            e.printStackTrace();       
        }
        catch(IOException e) 
        {
            e.printStackTrace();       
        }
    }
}