[After Java Semina] 자바 입출력

Jiwon-Woo·2021년 9월 11일
0

After Java Semina

목록 보기
5/5

1. 자바 입출력과 스트림

1.1 스트림이란?

자바에서 데이터를 스트림을 통해 입출력된다. 이때, 스트림이란 데이터가 이동하는것을 지칭한다. 자바에서는 당연히 클래스를 통해 스트림을 사용할 수 있고, java.io패키지에 저장되어있다.
표준 입출력 이외에 스트림은 크게 바이트기반 스트림과 문자 기반 스트림으로 구분된다.

  • 바이트기반 스트림 : 그림, 멀티미디어 등 바이너리데이터(0110...)데이터를 읽고, 출력할때 사용하는 스트림.
  • 문자기반 스트림 : 문자데이터를 읽고 출력하는데 특화된 스트림.

1.2 스트림의 특징

  • 스트림의 양끝에는 각각 입출력 장치와 응용프로그램이 연결된다. 자바 응용프로그램은 입출력 스트림과만 연결되고, 입출력 스트림이 입출력 장치의 제어를 담당한다.
  • 스트림은 단방향이기 때문에 입력 스트림은 입력만을, 출력 스트림은 출력만을 담당한다.
  • 스트림을 통해 흘러가는 기본 데이터 단위는 바이트(1byte)나 문자(2byte)이므로, 스트림은 크게 문자 스트림과 바이트 스트림으로 나뉜다.
  • 스트림은 FIFO 구조이다.

2. 표준 입출력

2.0 System 클래스

public final class System {
	...
	public **static** final InputStream in = null;
	...
}

호스트의 기능 및 시스템과 관련된 기능들을 사용할 수 있게 하는 클래스.

특히 여기에서는 JVM이라는 가상머신에서 호스트 컴퓨터의 입출력장치와 스트림을 만들어주는 System.in, System.out, System.err와 같은 필드를 주로 사용할것이다.

참고 : Oracle Docs - System, Morph's House

2.1 System.in

public static final InputStream in = null;

System.in은 표준 입력 스트림이다. 자바에서 콘솔을 통해 입력을 받을때는 System.in 정적 필드를 사용한다. 다른 입출력 스트림과 마찬가지로 java.in에서 import하며, InputStream타입이다.

System.in을 InputStream 변수에 참조시켜 사용하거나, 보조 스트림에 대입하여 사용.
+) 예외처리문(try-catch문 안에서 사용해야한다)

→ 방향이 없는 쌩 스트림에 System.in을 대입하여 표준입력으로 방향을 만들어 준다고 생각.

  • System.in 예제 _ 입력한 문자열을 그대로 반환하는 예제.
    package systeminexer;

    import java.io.BufferedReader;
    import java.io.InputStream;
    import java.io.InputStreamReader;
    import java.io.Reader;

    public class SystemInTest {
        public static void main(String[] args) {
            InputStream iS = System.in;
            Reader reader = new InputStreamReader(iS);
            BufferedReader br = new BufferedReader(reader);
            try {
                String lineStr = br.readLine();
                System.out.println(lineStr);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

(뒤에서 다룰 내용들이긴하지만 간략하게.)
InputStreamReader객체에 System.in필드를 매개변수로 넣어서, 표준입력과 연결시키고 Reader객체에 연결한다. Reader를 이용하면 1byte대신 2byte의 문자(한글)을 받을 수 있게된다. 이 reader를 다시 BufferedReader객체에 넣어주면 버퍼를 사용할 수 있게 된다.

  • System.in 만 사용해본 예제 코드 - 개행전까지 입력받기
    package stream.inputstream;

    import java.io.IOException;

    public class SystemInTest {

        public static void main(String[] args) throws IOException {
            System.out.println("Input Test!");

            int input;

            try {
                while ((input = System.in.read()) != -1) {
    //                System.out.flush();
                    if (input != 10)
                        System.out.println((char)input);
                    else // 입력받은 문자가 개행이면, 입력받는 것을 종료
                        break;
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            System.out.println("The End!");
        }

    }
    Input Test!
    123
    1
    2
    3
    The End!

2.2 그 외 입력클래스

2.2.1 Scanner

// 생성자
public Scanner(InputStream source) {
    this(new InputStreamReader(source),WHITESPACE_PATTERN);
}

java.util에 있는 클래스. 생성자의 매개변수로 InputStream를 받는다. InputStream형의 source를 InputStreamReader로 다시 인스턴스화하여 넘겨주기 때문에, 2byte의 한글도 읽을 수 있다. 그리고 입력종료 시점(구획 문자)을 공백문자로 지정한다(WHITESPACE_PATTERN).

+) 위의 코드에서 this는 아래처럼 private로 숨겨져있는 생성자이다.

private Scanner(Readable source, Pattern pattern) {
	...
}

Scanner은 next...메소드 시리즈를 통해 입력을 받는다. 이때 내부에서 정규식을 깐깐하게 검사하고 입력으로 넣어주게 되므로 속도가 상당히 느리다고 한다...

참고 및 자세한 정보 : st-lab 자바 입력 뜯어보기

자주쓰는 Scanner 함수들

  • next자료형() - nextBoolean(), nextInt(), nextString()...
    들어오는 입력(토큰)을 해당 자료형으로 해석하여 반환한다.

  • next(), next(Pattern pattern)
    입력받은 String을 공백문자(스페이스 포함) 직전까지 그대로 반환하는 메소드. 이때 공백문자는 버퍼에 그대로 남아있게된다. pattern을 매개변수로 넣으면 패턴과 일치하는 경우 문자열을 반환한다.

  • nextLine()
    개행을 만날때까지 입력받은 String을 반환하는 메소드. 개행까지 버퍼에서 읽지만, 출력은 하지 않는듯하다.

  • hasNext자료형()
    입력된 내용이 해당 자료형으로 해석될 수 있으면 true를 반환, 아닌 경우 false를 반환.

  • 참고 - 버퍼에 의한 next()와 nextLine()사용시 문제점

        package scanner.exer;

        import java.io.InputStreamReader;
        import java.util.Scanner;

        public class ScannerTest {
            public static void main(String[] args) {
                Scanner scanner = new Scanner(System.in);
                String a = scanner.next();
                String b = scanner.nextLine();
                String c = scanner.next();
                System.out.println(">> a : " + a);
                System.out.println(">> b : " + b);
                System.out.println(">> c : " + c);
            }
        }
// 입력
aaa
bbbb
        
>> a : aaa
>> b : 
>> c : bbbb

next()는 버퍼를 읽고, 개행직전까지 내용을 가져온다. 따라서 버퍼에는 개행이 그대로 남아있어서 nextLine()에서 버퍼를 읽었을 때 바로 개행을 만나게 되어 입력을 의도하지않은대로 받게 된다.

2.2.2 Console

System.in 을 사용하지 않고 console 창 내용을 읽을 수 있는 클래스. console에서 직접 입력받을 때 사용. 이 클래스를 사용했을 때는 평소처럼 run 하지 않고 콘솔창에서 class 파일을 직접 실행시킨다. 한글도 깨지지 않고 읽을 수 있다.

Console 클래스 메서드

메서드설명
String readLine()문자열을 읽습니다.
char[] readPasword()사용자에게 문자열을 보여주지 않고 읽습니다.
Reader reader()Reader 클래스를 반환합니다.
PrintWriter writer()PrintWriter 클래스를 반환합니다.

Console 클래스 예제

  • 현재 실행시킬 클래스 파일(ConsoleTest.class)의 경로
/Users/jiwonwoo/IdeaProjects/inout/out/production/inout/stream/inputstream/ConsoleTest
  • ConsoleTest.java
    package stream.inputstream;

    import java.io.Console;

    public class ConsoleTest {
        public static void main(String[] args) {
            Console console = System.console();

            System.out.print("이름 : ");
            String name = console.readLine();

            System.out.print("직업 : ");
            String job = console.readLine();

            System.out.print("비밀번호 : ");
            char[] pass = console.readPassword();
            String strPass = new String(pass);

            System.out.println(name);
            System.out.println(job);
            System.out.println(pass);
        }
    }
  • console 창 결과
    # 클래스 파일이 속해있는 패키지의 상위폴더로 이동
    jiwonwoo@MacBookAir inout % pwd
    /Users/jiwonwoo/IdeaProjects/inout/out/production/inout

    # 패키지 이름까지 포함하여 클래스 파일 실행
    jiwonwoo@MacBookAir inout % java stream.inputstream.ConsoleTest
    # 콘솔에서 입력받는 부분
    이름 : 안녕
    직업 : 학생
    비밀번호 : 
    # 입력 받은 것 그대로 출력
    안녕
    학생
    12345

3. 바이트 단위 스트림

3.1 InputStream

바이트 단위 입력을 위한 최상위 입출력 스트림 클래스.

메서드설명
read()입력 스트림으로부터 1바이트를 읽고 읽은 바이트를 리턴합니다.
read(byte[ ] b)입력 스트림으로부터 읽은 바이트들을 매개값으로 주어진 바이트 배열b에 저장하고 실제로 읽은 바이트 수를 리턴합니다.
read(byte[] b, int off, int len)입력 스트림으로부터 len개의 바이트만큼 읽고 매개값으로 주어진 바이트 배열 b[off]부터 len개까지 저장합니다. 그리고 실제로 읽은 바이트 수인 len개를 리턴합니다. 만약 len개를 모두 읽지 못하면 실제로 읽은 바이트 수를 리턴합니다.
close()사용한 시스템 자원을 반납하고 입력스트림을 닫습니다.

InputStream 예제

    import java.io.InputStream;
    import java.io.OutputStream;
    import java.io.IOException;
    public class InputStreamTest1{
    	public static void main(String []args) 
    	{
    		int i = 0;		
    		InputStream inputStream  = null;
    		// OutputStream outputStream = System.out;
    		try{
    			inputStream = System.in;
    			while ((i = inputStream.read()) != -1)
    			{
    				System.out.println((char)i);
    			}
    		}
    			catch(IOException e)
    			{
    				System.out.println(e);
    			}
    	}
    	
    }

3.2 OutputStream

바이트 단위 출력을 위한 최상위 입출력 스트림 클래스.

메서드설명
write(int b)출력 스트림으로부터 1바이트를 보냅니다.(b의 끝 1바이트)
write(byte[ ] b)출력 스트림으로부터 주어진 바이트 배열 b의 모든 바이트를 보냅니다.
write(byte[ ] b, int off, int len)출력 스트림으로 주어진 바이트 배열 b[off]부터 len개까지의 바이트를 보냅니다.
flush()버퍼에 잔류하는 모든 바이트를 출력합니다.
close()사용한 시스템 자원을 반납하고 출력 스트림을 닫습니다.

입력 스트림으로 부터 b[]크기의 자료를 b[]의 off 변수 위치부터 저장하며 len만큼 읽습니다. 읽은 자료의 바이트 수를 반환합니다.

OutputStream 예제

    import java.io.*;
    public class OutputStreamTest{
    	public static void main(String []args)
    	{
    		byte[]bs = new byte[26];
    		byte data = 65;
    		for (int i =0; i < bs.length; i++)
    		{
    			bs[i] = data++;
    		}
    		try (OutputStream fos = System.out)
    		{
    			fos.write(bs);
    		}
    		catch(IOException e)
    		{
    			System.out.println(e);
    		}
    		System.out.println("end"); 
    	}
    }
ABCDEFGHIJKLMNOPQRSTUVWXYZ

3.3 FileInputStream

  • 바이트 단위 입력용 하위 스트림
  • 추상 메서드를 포함한 추상 클래스로 하위 클래스가 구현하여 사용

주요 하위 클래스

스트림 클래스설명
FileInputStream파일에서 바이트 단위로 자료를 읽습니다.
ByteArrayInputStreamByte 배열 메모리에서 바이트 단위로 자료를 읽습니다.
FilterInputStream기반 스트림에서 자료를 읽을때 추가기능을 제공하는 보조 스트림의 상위 클래스 입니다.

메서드

메서드설명
int read()입력 스트림으로 부터 한 바이트의 자료를 읽습니다. 읽은 자료의 바이트 수만큼 반환 합니다.
int read(byte b[])입력 스트림으로 부터 b[]크기의 자료를 b[]에 읽습니다. 읽은 자료의 바이트 수를 반환 합니다.
int read(byte b[], int off, int Len)읽은 자료의 바이트 수를 반환합니다.,입력 스트림으로 부터 b[]크기의 자료를 b[]의 off 변수 위치부터 저장하며 len만큼 읽습니다.
void close()입력 스트림과 연결된 대상 리소스를 닫습니다.

FileInputStream Example1

    import java.io.FileInputStream;
    import java.io.FileReader;
    import java.io.IOException;

    public class FileInputStreamTest
    {
    	public static void main(String[]args)
    	{
    		FileReader fis = null;
    		int i = 0;
    		try{
    			fis = new FileReader("input.txt");
    			while ((i = fis.read()) != -1)
    			{
    				System.out.println((char)i);
    			}
    		}
    			catch(IOException e)
    			{
    				System.out.println(e);
    			}
    			finally
    			{
    				try{

    					fis.close();
    				}
    				catch(IOException e)
    				{
    					System.out.println(e);
    				}
    				catch(NullPointerException e)
    				{
    					System.out.println(e);
    				}
    			}
    //			System.out.println("end");

    	}
    }
abcasdfa asd 
asdf a
adfadf ad

FileInputStream Example2

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

    public class FileInputStreamTest2{
    	public static void main(String[]args)
    	{
    		try(FileInputStream fis = new FileInputStream("input2.txt"))
    		{
    			byte []bs = new byte[10];
    			int i = 0;
    			while ((i = fis.read(bs)) != -1)
    			{
    				// for (byte b : bs)
    				// {
    				// 	System.out.println((char)b);
    				// }
    				for (int j = 0; j < i; j++)
    				{
    					System.out.println((char)bs[j]);
    				}
    			}
    		}catch (IOException e)
    		{
    			System.out.println(e);
    		}
    	}
    }
abcdefghijklmnopadfi

3.4 FileoutPutStream

  • 바이트 단위 출력용 하위 스트림
  • 추상 메서드를 포함한 추상 클래스로 하위 클래스가 구현하여 사용

주요 하위 클래스

스트림 클래스설명
FileOutputStream바이트 단위로 파일에 자료를 씁니다.
ByteArrayOutputStreamByte 배열에 바이트 단위로 자료를 씁니다.
FilterOutputStream기반 스트림에서 자료를 쓸때 추가 기능을 제공하는 보조 스트림의 상위클래스

메서드

메서드설명
void write(int b)한 바이트를 출력
void write(byte[] b)b[] 배열에 있는 자료 출력
void write(byte[] b, int off, int Len)b[] 배열에 있는 자료의 off 위치부터
void flush()출력을 위해 잠시 자료가 머무르는 출력 버퍼를 강제로 비워 자료를 출력
void close()출력 스트림과 연결 대상 리소스를 닫습니다. 출력 버퍼가 비워짐

FileOutputStream Example

    import java.io.FileOutputStream;
    import java.io.IOException;
    public class FileOutStreamTest{
    	public static void main(String[]args)
    	{
    		byte [] bs = new byte[26];
    		byte data = 65;
    		for (int i = 0; i < bs.length; i++)
    		{
    			bs[i] = data++;
    		}

    		try (FileOutputStream fos = new FileOutputStream("output2.txt"))
    		{
    			fos.write(bs);
    		}
    		catch(IOException e)
    		{
    			System.out.println(e);
    		}
    		System.out.println("end"); 
    	}
    }
ABCDEFGHIJKLMNOPQRSTUVWXYZ

flush() 와 close()메소드

  • 출력 버퍼를 비울때 flush() 메서드를 사용
  • close() 메서드 내부에서 flush() 가 호출 되므로 close() 메서드가 호출 되면 출력 버퍼가 비워짐
  • flush() && close()
    import java.io.*;

    import org.w3c.dom.ls.LSException;

    public class InputStreamTest
    {	
    	public static void main(String args[]) throws IOException {
    		System.out.println("아무 글이나 입력하시고 Enter를 쳐주세요");
    		System.out.println(" 'S'를 입력하면 프로그램이 종료됩니다.");
    		int ch;
    		InputStream in = System.in;
    		OutputStream out = System.out;
    		while((ch=in.read()) != -1) {
    			if(ch == 'S') {
    			byte[] arr= new byte[4];
    			arr[0] = 83;
    			arr[1] = 84;
    			arr[2] = 79;
    			arr[3] = 80;
    			out.write(arr);
    			out.flush();
    			out.close();
    			in.close();
    			System.exit(-1);
    			}
    			System.out.println("Char: "+(char)ch+", Available: "+in.available());
    		}
    	
    	}
    }

4. 문자 단위 스트림

4.1 Reader

문자(char) 단위로 읽는 스트림 중 최상위 스트림. 바이트 스트림이 바이트 단위로 읽는 것과 달리 문자는 char형의 크기, 2byte 씩 읽으므로, 한글도 깨지지 않고 잘 읽을 수 있게 된다.

Reader 주요 하위 클래스

스트림 클래스설명
FileReader파일에서 문자 단위로 읽는 스트림 클래스
InputStreamReader바이트 단위로 읽은 자료를 문자로 변환해주는 보조 스트림 클래스
BufferedReader문자로 읽을 때 배열을 제공하여 한꺼번에 읽을 수 잇는 기능을 제공해주는 보조 스트림 클래스

Reader 메서드

메서드설명
int read()파일로부터 한 문자를 읽고 읽은 값을 반환한다
int read(char[] buf)파일로부터 최대 buf 길이만큼 문자를 읽어서 buf에 저장,읽은 길이를 반환
int read(char[] buf, int off, int len)파일로부터 최대 buf 길이만큼 문자를 읽어서 buf에 저장,단 buf[off] 부터 len개의 문자를 저장,읽은 길이를 반환
void close()스트림과 연결된 파일 리소스를 닫는다

4.2 FileReader

FileReader의 생성자

메서드설명
FileReader(String name)파일 이름(경로 포함)을 매개변수로 받아 입력 스트림 생성
FileReader(File f)File 클래스 정보를 매개변수로 받아 입력 스트림 생성

FileReader 생성자의 매개변수가 경로를 포함한 파일 이름이라면 현재 작업 디렉토리 이후부터 명시하면 된다.

// 현재 작업 디렉토리 구하는 방법
String path = System.getProperty("user.dir");
System.out.println("Working Directory = " + path);
Working Directory = /Users/jiwonwoo/IdeaProjects/inout

input.txt

hello
hi
this is input

현재 input.txt 의 절대 경로는 아래와 같다.

/Users/jiwonwoo/IdeaProjects/inout/out/production/inout/stream/inputstream/input.txt

read() 예제

    package stream.inputstream;

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

    public class FileReaderTest {
        public static void main(String[] args) {
            try(FileReader fr = new FileReader("out/production/inout/stream/inputstream/input.txt")) {
                int i;
                while((i = fr.read()) != -1) {
                    System.out.print((char)i);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    hello
    hi
    this is input

read(char[] buf) 예제

    package stream.inputstream;

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

    public class FileReaderTest {
        public static void main(String[] args) {
            try(FileReader fr = new FileReader("out/production/inout/stream/inputstream/input.txt")) {
                int input;
                char[] buf = new char[5];
                while((input = fr.read(buf)) != -1) {
    //                System.out.print((char)input);
                    for (int i = 0; i < input; i++)
                        System.out.print(buf[i]);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    hello
    hi
    this is input

buf의 길이와 read한 문자의 개수는 다를 수 있다.

read(char[] buf, int off, int len) 예제

    package stream.inputstream;

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

    public class FileReaderTest {
        public static void main(String[] args) {
            try(FileReader fr = new FileReader("out/production/inout/stream/inputstream/input.txt")) {
                int input;
                char[] buf = new char[5];
                int off = 2;
                int len = 3;

                buf[0] = '0';
                buf[1] = '1';
                buf[2] = '2';
                buf[3] = '3';
                buf[4] = '4';

                while((input = fr.read(buf, off, len)) != -1) {
                    System.out.println("input : " + input);
                    System.out.print("buf : ");
                    for (int i = 0; i < off + input; i++)
                        System.out.print(buf[i]);
                    System.out.println();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    input : 3
    buf : 01hel
    input : 3
    buf : 01lo

    input : 3
    buf : 01hi

    input : 3
    buf : 01thi
    input : 3
    buf : 01s i
    input : 3
    buf : 01s i
    input : 3
    buf : 01npu
    input : 1
    buf : 01t

4.3 Writer

문자 단위로 출력하는 스트림 중 최상위 스트림.

Writer 주요 하위 클래스

스트림 클래스설명
FileWriter파일에서 문자 단위로 출력하는 스트림 클래스
OutputStreamWriter파일에 바이트 단위로 출력한 자료를 문자로 변환해주는 보조 스트림 클래스
BufferedWriter문자로 쓸 때 배열을 제공하여 한꺼번에 쓸 수 있는 기능을 제공해주는 보조 스트림

Writer 메서드

메서드설명
int write(int c)한 문자를 파일에 출력
int write(char[] buf)문자 배열 buf의 내용을 파일에 출력
int write(char[] buf, int off, int len)문자 배열 buf의 off 인덱스 부터 len 개의 문자를 파일에 출력
int write(String str)문자열 str을 파일에 출력
int write(String buf, int off, int len)문자열 str의 off 번째 문자부터 len개의 문자를 파일에 출력
void flush()파일에 출력하기 전에 자료가 있는 공간(출력 버퍼)를 비워 출력
void close()파일과 연결된 스트림을 닫고 출력 버퍼를 비움

4.4 FileWriter

FileWriter의 생성자

메서드설명
FileWriter(String name)파일 이름(경로 포함)을 매개변수로 받아 출력 스트림 생성
FileWriter(String name, boolean append)파일 이름(경로 포함)을 매개변수로 받아 출력 스트림 생성. append 값이 true면 파일 스트림을 닫고 다시 생성할 때 파일의 끝에서 이어서 출력하고, false면 같은 상황에서 파일을 덮어씀
FileWriter(File f)File 클래스 정보를 매개변수로 받아 출력 스트림 생성
FileWriter(File f, boolean append)File 클래스 정보를 매개변수로 받아 출력 스트림 생성. append 값이 true면 파일 스트림을 닫고 다시 생성할 때 파일의 끝에서 이어서 출력하고, append 값이 false면 같은 상황에서 파일을 덮어씀

FileWriter 예제1 (append : false)

    package stream.inputstream;

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

    public class FileWriterTest {
        public static void main(String[] args) {
            try(FileWriter fw = new FileWriter("writer.txt", false)) {
                char[] buffer = new char[26];

                for(int i = 0; i < buffer.length; i++)
                    buffer[i] = (char)(65 + i);
                fw.write(97);
                fw.write("\n");
                fw.write(buffer);
                fw.write("\n");
                fw.write(buffer, 5, 5);
                fw.write("\n");
                fw.write("한글한글");
                fw.write("안녕하세요", 0, 2);
            } catch (IOException e) {
                System.out.println("Error!");
            } finally {
                System.out.println("The End!");
            }
        }
    }

writer.txt

    a
    ABCDEFGHIJKLMNOPQRSTUVWXYZ
    FGHIJ
    한글한글안녕

FileWriter 예제2 (append : true)

    package stream.inputstream;

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

    public class FileWriterTest {
        public static void main(String[] args) {
            try(FileWriter fw = new FileWriter("writer.txt", true)) {
                char[] buffer = new char[26];

                for(int i = 0; i < buffer.length; i++)
                    buffer[i] = (char)(97 + i);
                fw.write(65);
                fw.write("\n");
                fw.write(buffer);
                fw.write("\n");
                fw.write(buffer, 0, 10);
                fw.write("\n");
                fw.write("한글한글");
                fw.write("안녕하세요", 2, 3);
            } catch (IOException e) {
                System.out.println("Error!");
            } finally {
                System.out.println("The End!");
            }
        }
    }

writer.txt

    a
    ABCDEFGHIJKLMNOPQRSTUVWXYZ
    FGHIJ
    한글한글안녕A
    abcdefghijklmnopqrstuvwxyz
    abcdefghij
    한글한글하세요

5. 보조 스트림

5.1 보조 스트림이란?

이미지 출처 : https://kkk-kkk.tistory.com/

직접 읽거나 쓰는 기능은 없지만 입출력 단위를 바이트에서 문자로 바꾸는 등 입출력 스트림에 보조 기능을 추가하는 스트림이다. 다른 스트림을 감싸고 있다고 하여 Wrapper 스트림이라고도 하고, 디자인 패턴에서는 데코레이터(decorator)라고 부른다.

일반적으로 프로그램은 주 입력스트림에서 직접데이터를 읽지 않고, 주 입력스트림으로 데이터를 받고, 보조스트림의 기능을 이용해(데이터 가공?) 데이터를 받는다. 출력도 이와 같다.

보조 스트림의 주된 목적은 데이터 변환, 출력형식 지정, 성능 향상 등이다.

5.2 FilterInputStream과 FilterOutputStream

FilterInputStream과 FilterOutputStream은 보조 스트림의 상위 클래스이다. 보조 스트림은 위에서 언급했든 자체 입출력 기능이 없기 때문에 항상 다른 입출력 스트림과 함께 행동해야한다. 두 클래스의 유일한 생성자는 다음과 같으며, 하위 보조 스트림 클래스는 이 생성자를 통해 생성된다.

Filter 보조 스트림 클래스의 생성자

생성자설명
protected FilterInputStream(InputStream in)생성자의 매개변수로 InputStream을 받음
public FilterOutputStream(OutputStream out)생성자의 매개변로 OutputStream을 받음

FilterInputStream 생성자

public class FilterInputStream extends InputStream {

    protected volatile InputStream in;

    protected FilterInputStream(InputStream in) {
        this.in = in;
    }

}

FilterOutputStream 생성자

public class FilterOutputStream extends OutputStream {

    protected OutputStream out;

    public FilterOutputStream(OutputStream out) {
        this.out = out;
    }

}

FilterInputStream의 생성자는 접근제어자가 protected이기 때문에 인스턴스를 생성해서 사용할 수 없고 상속을 통해서 오버라이딩되어야 한다. 그리고 생성자 내에 있는 volatile 키워드는 변수가 CPU cache가 아닌 Main memory에 저장되는 것을 보장하는 키워드이다.

Filter 클래스를 살펴보면 InputStream 혹은 OutputStream 클래스를 상속 받아 구현되었고, 내부에 InputStream 혹은 OutputStream 클래스 변수가 선언되어 있는 것을 볼 수 있다. 그리고 생성자의 매개변수로 받은 스트림을 내부 변수에 할당하고 사용하는 것을 알 수 있다.

이처럼 보조 스트림은 외부에서 다른 스트림을 받아야한다. 이때 매개변수로 받는 스트림은 입출력 스트림을 감싼 또다른 보조 스트림일 수도 있다. 핵심은 자체 입출력이 가능한 스트림의 포함 여부이다.

5.3 InputStreamReader와 OutputStreamWriter

바이트 단위를 사용하는 스트림은 한글 입출력이 불가능하다. 가능하다면 문자단위의 입출력 스트림을 이용하는 게 좋지만 표준 입출력 스트림이나 소켓 스트림의 경우, 바이트 단위로만 입출력이 가능하다. 그러므로 생성된 바이트 스트림을 문자로 변환해주는 보조 스트림이 필요한데, 이때 사용하는 것이 InputStreamReader와 OutputStreamWriter 클래스이다.

InputStreamReader 클래스의 생성자

생성자설명
InputStreamReader(InputStream in)InputStream을 매개변수로 받아 Reader를 생성
InputStreamReader(InputStream in, Charset cs)InputStream과 Charset을 받아 Reader를 생성
InputStreamReader(InputStream in, CharsetDecoder dec)InputStream과 CharsetDecoder를 매개변수로 받아 Reader를 생성.
InputStreamReader(InputStream in, String charsetName)InputStream과 Charset 의 이름을 String로 받아 Reader를 생성.

InputStreamReader와 OutputStreamWriter의 생성자는 일반적으로 InputStream과 Charset을 매개변수로 받는다. Charset 이란 문자를 표현하는 인코딩 방식을 의미하며, 만약 Charset를 명시하지 않으면 시스템이 기본으로 사용하는 Charset이 적용된다.

Charset을 이용한 인코딩/디코딩처리

  • Charset - 캐릭터셋을 나타내는 클래스, encode()와 decode() 메서드 포함
  • CharsetEncoder - 캐릭터를 바이트로 변환해주는 인코더
  • CharsetDecoder - 바이트 데이터로부터 캐릭터를 생성해주는 디코더

OutputStreamWriter 클래스의 생성자는 첫번째 매개변수가 OutputStream이라는 것만 다를 뿐 형태는 거의 동일하다.

InputStreamReader 예제 코드

    package stream.decorator;

    import java.io.IOException;
    import java.io.InputStreamReader;

    public class InputStreamReaderTest {
        public static void main(String[] args) {

            try (InputStreamReader isr = new InputStreamReader(System.in)) {
                int ret;
                System.out.println("=== InputStreamReader Test ===");
                System.out.print("입력 : ");
                while ((ret = isr.read()) != -1) {
                    if (ret == 10)
                        break;
                    else
                        System.out.print((char)ret);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            System.out.println("\n==============================");
        }
    }
    === InputStreamReader Test ===
    입력 : hello, 한글도 잘 입력됩니다.
    hello, 한글도 잘 입력됩니다.
    ==============================

OutputStreamWriter 예제 코드1

    package stream.decorator;

    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.io.OutputStreamWriter;

    public class OutputStreamWriteTest {
        public static void main(String[] args) {

            try (OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("osw.txt"))) {
                char[] charArr = new char[3];

                charArr[0] = '안';
                charArr[1] = '녕';
                charArr[2] = '!';

                osw.write(charArr);
                osw.write(10);
                osw.write("한글도 잘 출력되나요?");
                
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

osw.txt

    안녕!
    한글도 잘 출력되나요?

OutputStreamWriter 예제 코드2 (System.out.write)

    package stream.decorator;

    import java.io.FileInputStream;
    import java.io.IOException;
    import java.io.InputStreamReader;
    import java.io.OutputStreamWriter;

    public class OutputStreamWriteTest {
        public static void main(String[] args) {

            try (InputStreamReader isr1 = new InputStreamReader(new FileInputStream("osw.txt"));
                 InputStreamReader isr2 = new InputStreamReader(new FileInputStream("osw.txt"));
                 OutputStreamWriter osw = new OutputStreamWriter(System.out)) {

                int ret;

                System.out.println("=== System.out.write(int b)로 출력 ===");
                while ((ret = isr1.read()) != -1) {
                    System.out.write(ret);
                }

                System.out.println("\n\n=== OutputStreamWriter 보조스트림 사용 ===");
                while ((ret = isr2.read()) != -1) {
                    osw.write((char)ret);
                }

            } catch (IOException e) {
                e.printStackTrace();
            }

        }
    }
    === System.out.write(int b)로 출력 ===
    HU!
    \� � �%��?

    === OutputStreamWriter 보조스트림 사용 ===
    안녕!
    한글도 잘 출력되나요?

5.4 Buffered 스트림

지금까지 본 입출력 스트림은 바이트나 문자 단위였다. 그러나 입출력 단위가 바이트나 문자단위일 경우, 그만큼 입출력을 여러번 반복해야하기 때문에 프로그램 수행 속도가 느려질 수 밖에 없다. 속도를 개선하기 위해 스트림에 들어온 입출력을 버퍼에 저장하여 한번에 출력하는 기능을 제공하는 보조 스트림 클래스가 있는데, 바로 Buffered 스트림이다. Buffered 스트림은 내부적으로 8192바이트의 배열을 가지고 있다.

버퍼링 기능을 제공하는 보조 스트림 클래스

생성자설명
BufferedInputStream바이트 단위로 읽는 스트림에 버퍼링 기능 제공
BufferedOutputStream바이트 단위로 출력하는 스트림에 버퍼링 기능 제공
BufferedReader문자 단위로 읽는 스트림에 버퍼링 기능을 제공
BufferedWriter문자 단위로 출력하는 스트림에 버퍼링 기능을 제공

BufferedInputStream 클래스의 생성자

생성자설명
BufferedInputStream(InputStream in)InputStream를 매개변수로 받아 BufferedInputStream 생성. 버퍼의 디폴트 사이즈는 8192byte 이다.
BufferedInputStream(InputStream in, int size)InputStream과 버퍼 크기를 매개변수로 받아 BufferedInputStream 생성

BufferedInputStream 클래스와 생성자

    public class BufferedInputStream extends FilterInputStream {

    		private static int DEFAULT_BUFFER_SIZE = 8192;
    		protected volatile byte[] buf;

    		public BufferedInputStream(InputStream in) {
            this(in, DEFAULT_BUFFER_SIZE);
        }

        public BufferedInputStream(InputStream in, int size) {
            super(in);  // super(in) == FilterInputStream(InputStream in)
            if (size <= 0) {
                throw new IllegalArgumentException("Buffer size <= 0");
            }
            buf = new byte[size];
        }

    }

Buffer 스트림 사용여부에 따른 시간 차이

  • 바이트 단위 스트림
    package stream.decorator;

    import java.io.FileInputStream;
    import java.io.FileNotFoundException;
    import java.io.FileOutputStream;
    import java.io.IOException;

    public class FileCopyTest {
        public static void main(String[] args) {
            long ms = 0;

            try (FileInputStream fis = new FileInputStream("frontend_jwoo.zip");
                 FileOutputStream fos = new FileOutputStream("copy.zip")) {
                ms = System.currentTimeMillis();
                int ret;
                while ((ret = fis.read()) != -1) {
                    fos.write(ret);
                }
                ms = System.currentTimeMillis() - ms;
            } catch (IOException e) {
                e.printStackTrace();
            }
            System.out.println(ms);
        }
    }
    354691
  • Buffer 스트림 사용
    package stream.decorator;

    import java.io.*;

    public class FileCopyTest {
        public static void main(String[] args) {
            long ms = 0;

            try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream("frontend_jwoo.zip"));
                 BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("copy2.zip"))) {
                ms = System.currentTimeMillis();
                int ret;
                while ((ret = bis.read()) != -1) {
                    bos.write(ret);
                }
                ms = System.currentTimeMillis() - ms;
            } catch (IOException e) {
                e.printStackTrace();
            }
            System.out.println(ms);
        }
    }
    2383

소켓 통신에서 스트림 사용하기

소켓이란 통신에 사용하는 네트워크 연결 리소스로, 자바에서는 Socket 클래스로 이용 가능하다. Socket 클래스는 getInputStream() 메서드와 getOutputStream() 메서드를 제공하는데, 각각 InputStream과 OutputStream을 반환해준다. 이때 이 스트림은 바이트 단위이므로, 버퍼링을 제공해주는 Buffered 보조 스트림을 추가해주는 것이 좋을 것이다.

  • Socket 활용 방법
    Socket socket = new Socket();

    InputStream is = socket.getInputStream();
    OutputStream os = socket.getOutputStream();

    BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
    BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));

5.5 DataInputStream과 OutputStream

앞에서 다룬 스트림은 텍스트 형식의 자료를 다루었지만, 지금 다룰 DataInputStream과 DataOutputStream은 메모리에 저장된 0과 1을 그대로 읽거나 쓰기 때문에 자료형의 크기가 그대로 보존되며, 각 자료형 별 메서드가 있어 자료형에 따라 읽거나 쓸 수 있다.

Data 보조 스트림 클래스의 생성자

생성자설명
DataInputStream(InputStream in)InputStream를 매개변수로 받아 DataInputStream 생성
DataOutputStream(OutputStream out)OutputStream를 매개변수로 받아 DataOutputStream 생성

읽은 그대로 써야하므로 DataInputStream 클래스의 read 메서드와 DataOutputStream의 write 메서드는 서로 대응된다.

DataInputStream 클래스의 메서드

생성자설명
btye readByte()1 바이트를 읽고, 읽은 값을 반환
boolean readBoolean()읽은 자료가 0이 아니면 true, 0이면 false 반환
char readChar()한 문자를 읽어 반환
short readShort()2 바이트를 읽어 정수 값을 반환
int readInt()4 바이트를 읽어 정수 값을 반환
long readLong()8 바이트를 읽어 정수 값을 반환
float readFloat()4 바이트를 읽어 실수 값을 반환
double readDouble()8 바이트를 읽어 실수 값을 반환
String readUTF()수정된 UTF-8 인코딩 기반으로 문자열을 읽어 반환

DataOutputStream 클래스의 메서드

생성자설명
void writeByte(int v)1 바이트의 자료를 출력
void writeByte(boolean v)1 바이트의 값을 출력
void writeChar(int v)2 바이트를 출력
void writeShort(int v)2 바이트를 출력
void writeInt(int v)4 바이트를 출력
void writeLong(ling v)8 바이트를 출력
void writeFloat(float v)4 바이트를 출력
void writeDouble(double v)8 바이트를 출력
void writeUTF(String str)수정된 UTF-8 인코딩 기반으로 문자열을 출력

자료형을 그대로 읽고 쓰는 스트림이기 때문에 같은 정수라도 자료형에 따라 다르게 처리한다. 즉, writeByte(200), 1 바이트로 쓰인 200과, writeInt(200), 4바이트로 쓰인 200은 다르다.

Data 클래스 예제 코드

    package stream.decorator;

    import java.io.*;

    public class DataStreamTest {
        public static void main(String[] args) {
            // 데이터 쓰기
            try(FileOutputStream fos = new FileOutputStream("data.txt");
                DataOutputStream dos = new DataOutputStream(fos)) {
                dos.writeByte(127);
                dos.writeByte(-128);
                dos.writeByte(128);
                dos.writeByte(-129);
                dos.writeChar('나');
                dos.writeInt(128);
                dos.writeInt(-129);
                dos.writeFloat(3.14f);
                dos.writeUTF("english, 한글");
            } catch (IOException e) {
                e.printStackTrace();
            }
            // 데이터 읽기
            try(FileInputStream fis = new FileInputStream("data.txt");
                DataInputStream dis = new DataInputStream(fis)) {
                System.out.println(dis.readByte());
                System.out.println(dis.readByte());
                System.out.println(dis.readByte());
                System.out.println(dis.readByte());
                System.out.println(dis.readChar());
                System.out.println(dis.readInt());
                System.out.println(dis.readInt());
                System.out.println(dis.readFloat());
                System.out.println(dis.readUTF());
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    127
    -128
    -128
    127
    나
    128
    -129
    3.14
    english, 한글

6. 직렬화

6.1 직렬화와 역직렬화

직렬화(直列化, serialization)
직렬화(直列化) 또는 시리얼라이제이션(serialization)은 컴퓨터 과학의 데이터 스토리지 문맥에서 데이터 구조나 오브젝트 상태를 동일하거나 다른 컴퓨터 환경에 저장(이를테면 파일이나 메모리 버퍼에서, 또는 네트워크 연결 링크 간 전송)하고 나중에 재구성할 수 있는 포맷으로 변환하는 과정이다. - 위키백과

객체를 저장하거나 메모리, 데이터베이스 혹은 파일로 옮기려면 어떻게 해야할까? 이럴 때 필요한 것이 직렬화다. 직렬화란 객체를 바이트 스트림으로 바꾸는 것, 즉 객체에 저장된 데이터를 스트림에 쓰기(write) 위해 연속적인 serial 데이터로 변환하는 것이다.
직렬화의 주된 목적은 객체를 상태 그대로 저장하고 필요할 때 다시 생성하여 사용하는 것이다.
...
역직렬화Deserialization는 직렬화의 반대말로, 네트워크나 영구저장소에서 바이트 스트림을 가져와서 객체가 저장되었던 바로 그 상태로 변환하는 것이다(convert it back to the Object with the same state).
직렬화하면서 생긴 바이트 스트림은 플랫폼에 독립적이다(platform independent). 직렬화된 객체는 다른 플랫폼에서 역직렬화가 가능하다는 뜻이다. - lunay0ung의 글

직렬화란 자바 시스템 내부의 객체, 데이터 등을 외부 자바 시스템에서 사용할 수 있는 byte형태로 데이터(파일 등)를 변환하는 기술. 시스템적으로 이야기하면 JVM의 힙, 스택등에 상주한 객체 데이터를 파일형태로 변환하는 기술이다.

역직렬화는 직렬화와 반대로 byte로 저장된 데이터를 스트림을 통해 읽고 다시 객체로 변환하는 기술을 통칭하는 용어이다. 시스템적으로 이야기하면 byte형태의 데이터를 객체로 변환하여 JVM으로 상주시키는 것이다.

참고 : 우아한형제들 기술블로그

직렬화 할수 있는 클래스를 만들기 위해서는 java.io.Serializable 인터페이스를 구현해야한다.

public class Member **implements Serializable** {
	...
}

Serializable

    import java.io.FileOutputStream;
    import java.io.FileInputStream;
    import java.io.IOException;
    import java.io.ObjectInputStream;
    import java.io.ObjectOutputStream;
    **import java.io.Serializable;**

    class Person **implements Serializable**{
    	String name;
    	**transient** String title;		//transient 저장하고 싶지 않을때, null 값으로 저장이 된다.
    	public Person(){};
    	public Person(String name, String title)
    	{
    		this.name = name;
    		this.title = title;
    	}
    	public String toString()
    	{
    		return name + "," + title;
    	}
    }
    public class SerializationTest {
    	public static void main(String[]args) throws ClassNotFoundException
    	{
    		Person person1 = new Person("진성대", "매니저");
    		try(FileOutputStream fos = new FileOutputStream("serial.dat"); ObjectOutputStream oos = new ObjectOutputStream(fos))
    		{
    			 **oos.writeObject(person1); // 직렬화,** 인텔리제이 기준 프로젝트 루트폴더에 저장됨.
    		}
    		catch (IOException e)
    		{
    			System.out.println(e);
    		}
    		try(FileInputStream fis = new FileInputStream("serial.dat"); ObjectInputStream ois = new ObjectInputStream(fis))
    		{
    			**Object obj = ois.readObject(); // 역직렬화.**
    			if (obj instanceof Person)
    			{
    				**Person p = (Person)obj;**
    				System.out.println(p);
    			}
    		}
    		catch(IOException e)
    		{
    			System.out.println(e);
    		}
    	}
    }
진성대,null

직렬화하는데 사용하는 또다른 인터페이스 Externalizable

Serializationable 인터페이스는 자료를 읽고 쓰는데 필요한 부분을 따로 구현할 필요가 없지만, Externalizable 인터페이스는 프로그래머가 직접 구현해야한다. 읽는 데 사용하는 readExternal(ObjectInput in) 메서드와 쓰는데 사용하는 writeExternal(ObjectOutput out) 메서드를 구현해야하며, 복원할 때 디폴트 생성자가 호출되기 때문에 디폴트 생성자를 추가해주어야한다. 만약 write 할때 클래스 내의 멤버변수를 모두 적지 않았다면, 복원할 때 디폴트 생성자 내에서 초기화된 변수 값으로 설정된다.

  • Book 클래스
    package bookmanagementprogram;

    import java.io.*;

    public class Book implements Externalizable {

    		private static final long serialVersionUID = -5525783142235723859L;
        protected String sName;
        transient protected String sAuthor;
        protected int iPage;
        protected String sGenre;

        public Book() {
            sName = "제목";
            sAuthor = "작가";
            iPage = 0;
    //        sGenre = "장르";
        }

        public Book(String name, String author, int page, String genre) {
            sName = name;
            sAuthor = author;
            iPage = page;
            sGenre = genre;
        }

        public String getName() {
            return sName;
        }

        public String getAuthor() {
            return sAuthor;
        }

        public int getPage() {
            return iPage;
        }

        public String getGenre() {
            return sGenre;
        }

        @Override
        public String toString() {
            return "도서 이름 : " + this.sName + "\n도서 저자 : " + this.sAuthor + "\n도서 페이지 수: " + this.iPage + "\n도서 장르 : " + this.sGenre;
        }

        @Override
        public void writeExternal(ObjectOutput out) throws IOException {
            out.writeUTF(sName);
            out.writeUTF("작자 미상");
            out.writeInt(iPage);
    //        out.writeUTF(sGenre);
        }

        @Override
        public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
            sName = in.readUTF();
            sAuthor = in.readUTF();
            iPage = in.readInt();
    //        sGenre = in.readUTF();
        }
    }

Externalizable

    package stream.serialization;

    import bookmanagementprogram.Book;

    import java.io.*;

    public class ExternalizableTest {
        public static void main(String[] args) throws ClassNotFoundException {
            Book book = new Book("이노아카", "jwoo", 42, "42Seoul");

            try(FileOutputStream fos = new FileOutputStream("serial.out");
            ObjectOutputStream oos = new ObjectOutputStream(fos)) {
                oos.writeObject(book);
            } catch (IOException e) {
                e.printStackTrace();
            }

            try(FileInputStream fis = new FileInputStream("serial.out");
            ObjectInputStream ois = new ObjectInputStream(fis)) {
                Book b = (Book)ois.readObject();
                System.out.println(b);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    package stream.serialization;

    import bookmanagementprogram.Book;

    import java.io.*;

    public class ExternalizableTest {
        public static void main(String[] args) throws ClassNotFoundException {
            Book book = new Book("이노아카", "jwoo", 42, "42Seoul");

            try(FileOutputStream fos = new FileOutputStream("serial.out");
            ObjectOutputStream oos = new ObjectOutputStream(fos)) {
                oos.writeObject(book);
            } catch (IOException e) {
                e.printStackTrace();
            }

            try(FileInputStream fis = new FileInputStream("serial.out");
            ObjectInputStream ois = new ObjectInputStream(fis)) {
                Book b = (Book)ois.readObject();
                System.out.println(b);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    도서 이름 : 이노아카
    도서 저자 : 작자 미상
    도서 페이지 수: 42
    도서 장르 : null

SerialVersionUID를 사용한 버전관리

객체를 역직렬화할 때, 직렬화 당시의 클래스 상태와 다르면 오류가 발생한다. 즉, 멤버변수가 변경되거나 하면 역직렬화하는데 어려움이 있다. 하지만 serialVersionUID를 활용하면 클래스의 버전을 좀 더 쉽게 관리할 수 있다. 클래스에 수정사항이 발생했을때 버전 정보를 변경하면, 나중에 버전 정보만 가지고도 클래스가 수정이 되었는지 안되었는지 알 수 있다.

[intellij 플러그인] SerialVersionUID 생성 가이드


7. 그 외 입출력 클래스

7.1 File 클래스

  • 파일 개념을 추상화한 클래스
  • 입출력 기능은 없고 파일의 속성, 경로, 이름 등을 알 수 있음

7.2 RandomAccessFile 클래스

  • 입출력 클래스 중 유일하게 파일 입출력을 동시에 할 수 있는 클래스
  • 파일 포인터가 있어서 읽고 쓰는 위치의 이동이 가능함
  • 다양한 자료형에 대한 메서드가 제공됨

RandomAccessFileTest.java

    import java.io.FileNotFoundException;
    import java.io.IOException;
    import java.io.RandomAccessFile;

    public class RandomAccessFileTest{
    	public static void main(String []args) throws IOException
    	{
    		RandomAccessFile raf = new RandomAccessFile("random.txt", "rw");
    		raf.writeInt(100); //4
    		System.out.println(raf.getFilePointer());
    		raf.writeDouble(3.14); // 8
    		System.out.println(raf.getFilePointer());
    		raf.writeChar('a'); //2
    		System.out.println(raf.getFilePointer());
    		
    		raf.seek(0); // seek -> filepointer 이동
    		System.out.println("=====================");
    		int i = raf.readInt();
    		double d = raf.readDouble();
    		char c = raf.readChar();
    		System.out.println(i);
    		System.out.println(d);
    		System.out.println(c);
    		
    	}
    }
4
12
14
=====================
100
3.14
a

0개의 댓글