본문 바로가기

TIL/Java

[Java] 입출력(IO) - 2. 보조스트림(BufferedInputStream, BufferedOutputStream, DataInputStream, DataOutputStream, ObjectInputStream, ObjectOutputStream,)

java.io 패키지의 입출력 스트림은 기본 스트림과 필터 스트림으로 분류할 수 있다.
기본 스트림은 외부 데이터에 직접연결이 되는 스트림이고
필터 스트림은 외부 데이터에 직접 연결하는 것이 아니라 기본 스트림에 추가로 사용할 수 있는 스트림이다.
주로 성능을 향상시키는 목적으로 사용되며 생성자를 보면 구분이 가능하다.

생성자 쪽에 매개변수로 다른 스트림을 이용하는 클래스는 필터 스트림이라고 볼 수 있다.

 

보조스트림

- 스트림의 기능을 향상시키거나 새로운 기능을 추가하기 위해서 사용

-보조 스트림은 실제 데이터를 주고 받는 스트림이 아니기 때문에 입출력 처리가 불가능

- 기반 스트림을 먼저 생성한 후 이를 이용하여 보조 스트림을 생성

 

종류

- 입출력성능 (BufferedInputStream / BufferedOutputStream)

- 기본 데이터 타입 출력 (DataInputStream / DataOutputStream)

- 객체 입출력 (ObjectInputStream / ObjectOutputStream) 

FileInputStream fis = new FileInputStream("sample.txt"); //기반스트림 생성
BufferedInputStream bis = new BufferedInputStream(fis);	//보조스트림 생성
bis.read();						//보조스트림으로부터 데이터 읽어옴

=>한 줄로 한다면

 BufferedInputStream bis = new BufferedIntputStream(new FileInputStream("sample.txt"));

 

성능 향상 보조 스트림

- 느린 속도로 인해 입출력 성능에 영향을 미치는 입출력 소스를 이용하는 경우 사용

- 입출력 소스와 직접 작업하지 않고 버퍼에 데이터를 모아 한꺼번에 작업을 하여 실행 성능을 향상(입출력 횟수 줄임)

-BufferedInputStream/Reader

-BufferedOutputStream/Writer

 

		BufferedWriter bw = null;
		
		try {
			bw = new BufferedWriter(new FileWriter("src/com/greedy/section03/filterstream/testBuffered.txt"));
			
			bw.write("안녕하세요\n");
			bw.write("반갑습니다\n");
			
			/* 버퍼를 이용하는 경우 버퍼가 가득 차면 자동으로 내보내기를 하지만
			 * 버퍼가 가득 차지 않은 상태에서는 강제로 내보내기를 해야 한다.
			 * 이 때 flush()를 해주면 파일에 기록된다.*/
			bw.flush();
			
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			/* close()를 호출하면 내부적으로 flush()를 하고나서 자원을 반납한다. */
			if(bw != null) {
				try {
					bw.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
		
		/* BufferedReader
		 * 버퍼에 미리 읽어온 후 한 줄 단위로 읽어들이는 기능을 제공하며
		 * 기본 스트림보다 성능을 개선시킨다.
		 * */
		
		BufferedReader br = null;
		try {
			br = new BufferedReader(new FileReader("src/com/greedy/section03/filterstream/testBuffered.txt"));
			
			/* readLine()을 추가로 제공한다.
			 * 버퍼의 한 줄을 읽어와서 문자열로 반환한다.
			 * */
			String temp;
			while((temp = br.readLine()) != null) {
				System.out.println(temp);
			}
			
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			if(br != null) {
				try {
					br.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}

형변환 보조 스트림

- 기본 스트림이 byte 기반 스트림이고, 보조 스트림이 char 기반 스트림인 경우에 사용

- 자바에서는 콘솔이나 키보드 같은 표준 입력 입출력 장치로부터 데이터를 입출력하기위한 스트림을 표준 스트림 형태로 제공하고 있다. System 클래스의 in, out, err가 대상 데이터의 스트림을 의미한다.

- System.in (InputStream) : 콘솔로부터 데이터를 입력 받는다. 

- Syste.out (PrintStream) : 콘솔로 데이터를 출력한다.

- System.err (PrintStream) : 콘솔로 데이터를 출력한다.

=> 즉 자주 사용되는 자원에 대해 미리 스트림을 생성해 두었기 때문에 개발자가 별도로 스트림을 생성하지 않아도 된다.

BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

- System.in을 InputStreamReader로 변환하여 바이트기반 스트림을 문자 기반 스트림으로 변환 후 버퍼를 이용한 보조스트림과 연결

BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));

이런 표준 스트림 중 콘솔로부터 읽어오는 기반 스트림이 InputStream인데 Buffer를 이용해서 성능을 향상시키고 싶은 경우에 형변환 보조스트림을 사용할 수 있다.

 

		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));	//형변환
		
		try {
			System.out.print("문자열 입력 : ");
			String value = br.readLine();	//스캐너와 동일
			System.out.println("value : " + value);
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			if(br != null) {
				try {
					br.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
		
		/* 출력을 위한 것도 동일한 방식으로 사용할 수 있다. */
		BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
		
		try {
			bw.write("java oracle jdbc");
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			if(bw != null) {
				try {
					bw.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}

기본 데이터 타입 보조 스트림

- 기본 자료형 별 데이터를 읽고 쓰기 가능하도록 기능을 제공

  (단, 입력된 자료형의 순서와 출력될 자료형의 순서는 일치해야함)

- 외부 데이터로부터 읽어오는 데이터를 바이트형으로만 읽어오면 정수, 문자, 문자열 등 여러 데이터 타입을 취급하는 경우 별도로 처리를 해주어야 한다.

Ex) 정수 입력 받아 처리하려면 parsing을 해주어야 한다.
- 데이터 자료형 별로 처리하는 기능을 추가한 보조 스트림을 제공하고 있다. (DataInputStream/DataOutputStream)

		/* 데이터형별로 파일에 기록하는 DataOutputStream 인스턴스 생성*/
		DataOutputStream dout = null;
		
		try {
			dout = new DataOutputStream(new FileOutputStream("src/com/greedy/section03/filterstream/score.txt"));
			
			/* 파일에 자료형별로 기록 */
			dout.writeUTF("김영희");
			dout.writeInt(95);
			dout.writeChar('A');
			dout.writeUTF("김철수");
			dout.writeInt(87);
			dout.writeChar('B');
			dout.writeUTF("홍길동");
			dout.writeInt(73);
			dout.writeChar('C');
			
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			if(dout != null) {
				try {
					dout.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
		
		/* 데이터형 별로 읽어오는 DataInputStream 인스턴스 작성 */
		DataInputStream din = null;
		
		try {
			din = new DataInputStream(new FileInputStream("src/com/greedy/section03/filterstream/score.txt"));
			
			while(true) {
				/* 파일에 기록한 순서대로 읽어들이지 않은 경우 에러 발생 또는 올바르지 않은 값을 읽어온다. */
				System.out.println(din.readUTF() + ", " + din.readInt() + ", " + din.readChar());
				/* 무한루프로 읽어들이게 되면 파일에 더이상 읽을 것이 없는 경우 EOException을 발생시킨다.
				 * catch블럭에 EXFException을 핸들링하는 코드를 추가한다. */
			}

		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch(EOFException e){
			System.out.println("파일 읽기를 완료하였습니다.");
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			if(din != null) {
				try {
					din.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	
	}

객체 입출력 보조 스트림

- 객체 단위로 파일 또는 네트워크로 입출력 할 수 있는 기능

 

객체 단위로 입출력을 하기 위한 ObjectInputStream/ObjectOutputStream을 살펴보자

 

MemberDTO 클래스

- 객체로 입출력을 하기 위해서는 반드시 직렬화 처리를 해야 한다.
- 직렬화 대상 클래스에 Serializable 인터페이스만 구현하면 직렬화가 필요한 상황인 경우 해당 인터페이스를 상속 받았을 시 데이터를 직렬화 처리한다.

 

직렬화

- 자바 시스템 내부에서 사용되는 객체 또는 데이터를 외부에서도 사용할 수 있도록 바이트(byte) 형태로 데이터를 변환하는 기술

- Serializable 인터페이스implements하여 구현- 반대로 바이트로 변환된 데이터를 다시 객체로 변환하는 기술을 역직렬화라고 한다.

- serialVersionUID 필드 : 직렬화된 클래스와 같은 클래스임을 알려주는 식별자

컴파일 시 JVM이 자동으로 serialVersionUID 정적필드를 추가해주기 때문에 별도로 작성하지 않아도 오류는 나지 않지만 자동 생성시 역직렬화에서 예상하지 못한 InvaliedClassException을 유발할 수 있으므로 명시해줄 것을 권장한다. 

Ex) private static final long serialVersionUID = -6433332838482L;

public class MemberDTO implements java.io.Serializable{
	
	private static final long serialVersionUID = 7171399052371122105L;
	/* 회원 정보를 관리하기 위한 용도의 DTO 클래스*/
	private String id;
	private String pwd;
	private String name;
	private String email;
	private int age;
	private char gender;
	/* 직렬화 대상에서 제외하고 싶은 필드의 경우 transient 키워드를 이용할 수 있다. */
	private transient double point;
	
	public MemberDTO() {}

//매개변수생성자 코드생략

//setter/getter 코드생략

//toString() 오버라이딩 코드생략


 Application4 클래스

public class Application4 {

	public static void main(String[] args) {
		
		MemberDTO[] outputMembers = {
				new MemberDTO("user01", "pass01", "김영희", "young777@greedy.com", 25, '여', 1250.7),
				new MemberDTO("user02", "pass02", "김철수", "chul999@greedy.com", 27, '남', 1221.6),
				new MemberDTO("user03", "pass03", "유관순", "yoo123@greedy.com", 18, '여', 1234.3)
		};
		
		ObjectOutputStream objOut = null;
		
		try {
			objOut = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream("src/com/greedy/section03/filterstream/testObjectStream.txt")));
		
			for(int i = 0; i < outputMembers.length; i++) {
				objOut.writeObject(outputMembers[i]);
			}
			
			objOut.flush();
		
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			if(objOut != null) {
				try {
					objOut.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
		
		/* 실행해보면 java.io.NotSerializableException이 발생한다.
		 * 이는 직렬화 처리를 해주지 않아서 발생하는 에러이다. */
		
		MemberDTO[] inputMembers = new MemberDTO[3];
		
		ObjectInputStream objIn = null;
		
		try {
			
			objIn = new ObjectInputStream(new BufferedInputStream(new FileInputStream("src/com/greedy/section03/filterstream/testObjectStream.txt")));
		
//			/* 읽어온 Object가 해당하는 Class가 없을 경우 ClassNotFoundException이 발생할 수 있다. */
//			while(true) {
//				System.out.println(objIn.readObject());

			for(int i = 0; i < inputMembers.length; i++) {
				inputMembers[i] = (MemberDTO) objIn.readObject();
			}
			
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch(EOFException e) {
			System.out.println("파일을 모두 읽어왔습니다.");
		} catch (IOException e) {
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} finally {
			if(objIn != null) {
				try {
					objIn.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
		
		for(MemberDTO member : inputMembers) {
			System.out.println(member);
			
		}
	}