📗 self-study/📗 KH정보교육원 당산지원

[Java] 12_입출력[바이트 기반 스트림(== 좁은 통로)과 문자 기반 스트림(== 넓은 통로)의 입출력, 보조 스트림의 개요 및 입출력]

천재강쥐 2022. 8. 16. 11:58

*바이트 기반 스트림
바이트 스트림: 1byte 단위로만 입출력할 수 있는 좁은 통로(XXXInputStream / XXXOutputStream)
기반 스트림: 외부 매체와 직접적으로 연결되는 메인 통로
 
=> 외부 매체를 지정하고 그 외부매체와 직접적으로 연결되는 1byte 단위의 통로를 만들겠음
XXXInputStream: XXX 매체로부터 데이터를 입력받는 통로
 (외부 매체로부터 데이터를 가지고 오겠다. == 읽어들이겠다)
XXXOutputStream: XXX 매체로부터 데이터를 출력하는 통로
 (외부 매체에 데이터를 내보내겠다 == 쓰겠다)

 

DAO(Data Access Object)
: 데이터가 보관되어 있는 공간(외부 매체)과 직접 접근해서 데이터를 입출력하는 용도의 클래스 
기능에만 충실한 패키지: controller

데이터와 관련된 패키지: model
외부 데이터와 직접 연결할 패키지: dao

 

프로그램 ---> 외부매체(파일)
출력: 프로그램 내의 데이터를 파일로 내보내기 (즉, 파일에 기록하겠다. 파일로 저장하겠다.)

package com.kh.chap02.byte_.model.dao;

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

public class FileByteDao {
	
	public void fileSave() {
		
		// FileOutputStream: "파일"로 데이터를 1byte 단위로 출력하는 스트림
		
		FileOutputStream fout = null; // 0단계! 변수 선언 및 null 초기화
		
		try {	
			
			// 1. FileOutputStream 객체 생성 (== 연결 통로 만들기)
			// => 해당 파일과 직접 연결되는 통로를 만들겠음(파일명을 매개변수로 생성자 호출)
			// 	    해당 파일이 존재하지 않는다면 해당 파일이 생성되면서 연결 통로가 지어짐
			// 	    존재하는 파일이라면 연결 통로만 바로 지어짐 

			fout = new FileOutputStream("a_byte.txt"/*, false */); // a_byte.txt가 없으니까 오류 발생할 수 있다며 계속 빨간줄 뜸
			// => 기존에 해당 파일이 있을 경우 덮어씌워짐
			// => 이어쓰고 싶을 경우에는? 매개변수로 true를 같이 넘겨 주면 됨 (단, 기본값은 false임)
			
			// 2. 연결 통로로 데이터를 출력: write() 메소드 사용
			// => 1byte 범위만 전송 가능: -128 ~ 127까지의 숫자, 음수는 불가!(아스키코드 값에 음수 존재하지 않음)
			//		(단, 파일에 기록되는 건 해당 숫자의 고유한 문자임: 아스키코드)
			fout.write(97); // 'a'가 기록
			fout.write('b'); // 'b'가 기록
			// fout.write('김'); // 1byte짜리 통로에 2byte짜리의 한글이 들어가서 @라는 형태로 깨져서 출력됨!
							 // (바이트 스트림으로는 제한적)
			
			byte[] bArr = {99, 100, 101}; // 'c', 'd', 'e'가 기록
			fout.write(bArr);
			
			fout.write(bArr, 1, 2); // 'd', 'e'가 기록
			
			// 3. 스트림을 다 이용했으면 자원 반납하기 (반드시)
			// => 즉, 연결통로를 끊겠음
			// fout.close(); // 위에서 혹시 예외가 발생했을 경우 이 자리는 실행이 안 될 수도 있음! => finally 블록으로 위치 바꿔 줌
		
		
			} catch(FileNotFoundException e) { // try ~ catch 블럭으로 처리해 주기, 존재하지 않는 경로를 제시했을 때
				
			} catch (IOException e) { // 입출력 상황에서 어느 오류든지 발생했을 때
				e.printStackTrace();
			} finally { // 어떤 예외가 발생하든지 간에 반드시 실행할 구문을 작성하는 블럭
				
				// 3. 스트림을 다 이용했으면 자원 반납하기 (반드시)
				try {
					fout.close();
				} catch(IOException e) {
					e.printStackTrace();
				}

			}
	}


프로그램 <--- 외부매체(파일)
입력: 파일로부터 데이터를 가지고 오겠다 (즉, 읽어들이겠다)

public void fileRead() {
		
		// FileInputStream: 파일로부터 데이터를 1byte 단위로 입력받는 스트림
		
		// 0. 변수 선언 후 null로 초기화
		FileInputStream fin = null;
		
		try {
			// 1. 스트림 객체 생성하기 == 연결 통로 만들기
			fin = new FileInputStream("a_byte.txt"); // try ~ catch 블록에 넣기 
			// 파일 경로 제시, 입력받을 때에는 반드시 존재하는 파일명으로 제시해야 함!
			
			// 2. 읽어들이기 == 입력받기: read() 메소드 사용
			// => 단, 1byte 단위로 하나씩 읽어 옴
			/*
			System.out.println(fin.read()); // IOEcepton catch 블럭 추가하기
			System.out.println(fin.read());
			System.out.println(fin.read());
			System.out.println(fin.read());
			System.out.println(fin.read());
			System.out.println(fin.read());
			System.out.println(fin.read());
			// 파일 끝을 만나는 순간 fin.read() => -1 반환
			System.out.println(fin.read());
			System.out.println(fin.read());
			// 출력할 때는 문자로, 입력받을 때는 숫자로 받아짐
			// 입력 시 (char)로 강제 형변환 하여 문자로 입력받을 수 있음
			// println이 아닌 print로 연이어서 출력도 가능
			*/
			
			// 반복문으로  활용하기
			
				// 퐁당퐁당 출력됨
				/* 메소드를 한 번 돌릴 때마다 두 번 호출되는 구조라서 내가 원하는 값을 얻을 수 없음
				while(fin.read() != -1) { // fin.read()의 결과값이 -1이 아닐 경우에만 반복 돌리겠음
					System.out.println(fin.read());
				}
				*/
				
				// 해결방법 1. 무한 반복으로 매번 조건 검사 하기
				/*
				while (true) {
					
					int value = fin.read();
					
					if(value == -1) {
						break;
					}
					
					System.out.println(value);
				}
				*/
			
				// 해결방법 2. 조건식 내부에 변수 대입 구문을 활용하는 방법 (권장)
				int value = 0;
				while((value = fin.read()) != -1) {
					System.out.println(value);
				}
				
			// 3. 연결 통로 끊기 == 자원 반납하기 (무조건) => finally 블럭에 작성
			
		} catch (FileNotFoundException e) {
			e.printStackTrace();
			
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			
			// 3. 다 쓴 스트림 객체 반납 
			try {
				fin.close(); // try ~ catch 블록으로 묶기
			} catch (IOException e) {
				e.printStackTrace();
			}
		} // 이제 실행 클래스에 fileRead(); 하러 감
		
	}

}

// 출력물
97
98
99
100
101
100
101

read메소드 특성: 한 번에 하나의 문자만 출력 가능

 

*문자 기반 스트림
문자 스트림: 한 번에 2byte 짜리의 데이터가 이동할 수 있는 넓은 통로 (XXXReader / XXXwriter)
기반 스트림: 외부 매체와 직접적으로 연결되는 main 통로

 

=> 외부 매체를 지정하고 그 외부 매체와 직접적으로 연결되는 2byte짜리 넓은 통로를 만들겠음


프로그램 ---> 외부 매체(파일)
출력

package com.kh.chap03.char_.model.dao;

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

public class FileCharDao {

	public void fileSave() {
		
		// FileWriter: 파일로 데이터를 2byte 단위로 출력하는 스트림
		
		// 0. 변수 선언 및 null로 초기화
		FileWriter fw = null; // import
		
		// 1. 스트림 객체 생성 == 연결 통로를 만들겠음 (파일명 제시)
		try {
			fw = new FileWriter("b_char.txt");  // 파일명 제시 이유: 누구랑 연결 통로를 만들 건지 알려 주기 위해서!
												// 출력일 경우, 현재 존재하지 않는 파일명을 제시하더라도 파일 생성 후 연결 통로가 지어짐
			
			// 2. 출력 == 데이터 내보내기: write() 메소드 사용
			fw.write("와! IO 재미있다...ㅎ"); // 내부적으로 문자 한 개씩 끊어서 이동함
			fw.write('A');
			fw.write(97);
			fw.write("\n"); // 개행문자도 가능
			
			char[] cArr = {'k', 'i', 'w', 'i'};
			fw.write(cArr);
			
			// 3. 연결 통로 끊기 == 자원 반납(반드시)
			// => finally 블록에 작성(finally 블록을 추가하면 지역 변수 이슈가 생겨 0단계 거쳐야 함)
			
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			
			// 3. 다 쓴 자원 반납하기 (반드시)
			try {
				fw.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
}

// b_char.txt에 입력된 것
와! IO 재미있다...ㅎAa
kiwi

 

 

그럼 문자 스트림에서는 전체 문자열이 함께 이동하는 것일까?

아니여!

편의상 그렇게 보이는 것이지 내부적으로는 문자 한 개씩 끊어서 이동

 

프로그램 <--- 외부 매체(파일)
입력

	public void fileRead() {
		
		// FileReader: 파일로부터 데이터를 2byte 단위로 입력받는 스트림
		
		// 0. 변수 선언 및 초기화
		FileReader fr = null; // import
		
		// 1. 스트림 객체 생성 == 연결 통로를 짓겠음
		try {
			fr = new FileReader("b_char.txt"); // 입력 통로의 경우 무조건! 존재하는 경로로 제시해야 됨!
	

			// 2. 입력받기: read() 메소드 사용
				/*
				System.out.println(fr.read());
				System.out.println(fr.read());
				System.out.println(fr.read());
				System.out.println(fr.read());
				System.out.println(fr.read());
				System.out.println(fr.read());
				System.out.println(fr.read());
				System.out.println(fr.read());
				System.out.println(fr.read());
				System.out.println(fr.read());
				System.out.println(fr.read());
				System.out.println(fr.read());
				System.out.println(fr.read());
				System.out.println(fr.read());
				System.out.println(fr.read());
				System.out.println(fr.read());
				System.out.println(fr.read());
				System.out.println(fr.read());
				System.out.println(fr.read());
				System.out.println(fr.read());
				System.out.println(fr.read());
				// 파일 끝을 만나는 순간 fr.read() => -1 반환
				System.out.println(fr.read());
				*/
			// => 문자 기반 스트림도 마찬가지로 문서의 끝을 만났을 때 read 메소드의 반환값이 -1임!!
			
			// 반복문 활용
			/*
			while(fr.read() != -1) {
				System.out.println(fr.read());
			}
			// => 반복이 한 번 일어날 때마다 read() 메소드가 2번 호출되기 때문에 2, 4번째 등 퐁당퐁당으로 출력됨
			*/
			
			int value = 0;
			while((value = fr.read()) != -1) { // 소괄호로 우선순위 높이기!
				System.out.print((char)value);
			}
			
			// 3. 연결 통로 끊기 == 자원 반납 (반드시)
			
			} catch (FileNotFoundException e) {
			} catch (IOException e) {
				e.printStackTrace();
			} finally {
			
				// 3. 자원 반납 (반드시)
				try {
					fr.close();
				} catch(IOException e) {
					e.printStackTrace();
				}

			}
	}

 

입력도 마찬가지로 한 글자씩 이동함


문자 하나씩 옮기는 거 너무 시간 오래 걸리지 않아?

나중에 데이터가 아~주 많아진다면

이렇게 중간에 모두 모아놨다가 한 번에 옮기는 게 더 빠르지 않을까?

↖ 버퍼(buffer)

이러한 기능을 할 수 있게 하는 게 보조 스트림

 

 

*보조 스트림
기반 스트림(외부 매체와 직접 연결되어 있는 통로)의 부족한 기능들을 확장시킬 수 있는 스트림
보조 스트림은 단독으로 사용 불가(즉, 단독으로 객체 생성이 불가)

=> 입력용 기반 스트림일 경우에는 입력용 보조 스트림을 써야 하고
      출력용 기반 스트림일 경우에는 출력용 보조 스트림을 써야 함!

 

[ 표현법 ]

 // 1단계: 기반스트림 생성
기반스트림클래스명 기반스트림객체명 = new 기반스트림클래스명(파일명);

// 2단계: 기반스트림객체를 매개변수 삼아서 보조스트림객체 생성
보조스트림클래스명 보조스트림객체명 = new 보조스트림클래스명(기반스트림객체); 

 

 => 속도 성능 향상 목적의 보조스트림: BufferedXXX
                                                                      (버퍼 공간을 제공해서 한 번에 내용물을 모아뒀다가 한꺼번에 입출력 진행)

 

프로그램 ---> 외부 매체(파일)

package com.kh.chap04.assist.part01.buffered.model.dao;

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

public class BufferedDao {
	
	// BufferedWriter (출력용) / BufferedReader (입력용)
	
	public void fileSave() {
		
		// FileWriter: 2byte 단위로 파일에 데이터를 내보내는 기반 스트림
		// +
		// BufferedWriter: 버퍼라는 공간을 제공해 주는 보조 스트림(속도 향상)
		
		FileWriter fw = null;
		BufferedWriter bw = null;
		

		try {
			
			// 1. 기반스트림 객체 생성(메인 연결 통로를 만들겠음)
			fw = new FileWriter("c_buffer.txt"); // import, 지역변수 이슈로 변수 선언 및 초기화 하고 오기! try ~ catch
			
			// 2. 보조 스트림 객체 생성 (매개변수로 기반스트림객체를 제시)
			bw = new BufferedWriter(fw); // import
			
			// 3. 출력: BufferedWriter 객체에서 제공하는 write() 메소드 사용
			// fw.write(); // FileWriter 객체에서 제공하는 write() 메소드를 사용하면 성능 향상을 기대할 수 없음
			bw.write("안녕하세요.");
			bw.newLine(); // 개행을 넣어 주는 메소드
			bw.write("반갑습니다.\n");
			bw.write("저리가세요.");
			
			
			// 4. 자원 반납 (반드시) => finally 블록 안에 작성
			
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			
			// 4. 자원 반납 (반드시)
			// => 주의할 점: 반납해야 할 자원이 2개(fw, bw)
			// => 반납 순서: 객체 생성 순서의 "역순"으로 반납!
			// 			      즉, 생성 순서가 fw -> bw이므로, 반납 순서는 bw -> fw
			
			try {
				bw.close(); // bw cannot be resolved: 지역변수 이슈이므로 0단계 함께 진행해 줘야 함!
							// 변수 생성 후에는 Unhandled exception type IOException로 오류 메시지 바뀜! => try ~ catch문으로 처리
				fw.close();
			} catch (IOException e) {
				e.printStackTrace();
			} 
						
		}
		
	}

}

 

FileWriter에 대한 객체 생성을 따로 하지 않고 코드 한줄로 진행하는 법

// FileWriter fw = null;
		// BufferedWriter bw = null;
		
		// 0단계 변수 선언 및 초기화를 코드 한줄로 줄여서 표현
		BufferedWriter bw = null;
        
        .
        .
        .
        
        // 1. 기반스트림 객체 생성(메인 연결 통로를 만들겠음)
			// fw = new FileWriter("c_buffer.txt"); // import, 지역변수 이슈로 변수 선언 및 초기화 하고 오기! try ~ catch
			
			// 2. 보조 스트림 객체 생성 (매개변수로 기반스트림객체를 제시)
			// bw = new BufferedWriter(fw); // import
			
			// 1, 2단계 기반 스트림 객체, 보조 스트림 객체 생성을 코드 한줄로 줄여서 표현
			bw = new BufferedWriter(new FileWriter("c_buffer.txt"));
          
        .
        .
        .
        
			try {
				bw.close(); // bw cannot be resolved: 지역변수 이슈이므로 0단계 함께 진행해 줘야 함!
							// 변수 생성 후에는 Unhandled exception type IOException로 오류 메시지 바뀜! => try ~ catch문으로 처리
				// fw.close(); // 코드를 한줄로 줄이면 얘는 필요하지 않게 됨
			} catch (IOException e) {
				e.printStackTrace();
			}

 

프로그램 <--- 외부 매체(파일)

(FileWriter에 대한 객체 생성을 따로 하지 않고 코드 한줄로 진행)

	public void fileRead() {
		
		// FileReader: 파일과 직접적으로 연결해서 한번에 2byte 단위로 데이터를 입력할 수 있는 기반 스트림
		// +
		// BufferedReader: 버퍼라는 공간을 제공해 주는 보조 스트림 (속도 향상)
		
		// 0. 변수 선언 및 null 값으로 초기화
		BufferedReader br = null;
		

		try {
			
			// 1. 객체 생성 == 통로를 만들겠다
			
			br = new BufferedReader(new FileReader("c_buffer.txt"));
	
			// 2. 입력: BufferedReader 클래스에서 제공하는 메소드로 읽어들이기
					/*
					System.out.println(br.readLine()); // 줄 단위로 내용물을 읽어들임
					System.out.println(br.readLine());
					System.out.println(br.readLine());
					System.out.println(br.readLine()); // 문서의 끝을 만났을 때 null을 반환
					*/
			
					// 반복문 활용
					String value = null;
					// String value = ""; // 이렇게 초기화해도 무방함
					while((value = br.readLine()) != null) {
						System.out.println(value);
					}
					
					// 3. 연결 통로를 끊겠다 == 자원 반납(반드시) => finally 블럭에 작성
					
				} catch (FileNotFoundException e) {
					e.printStackTrace();
				} catch (IOException e) {
					e.printStackTrace();
				} finally {
					
					// 3. 자원 반납 (반드시)
					try {
						br.close();
					} catch (IOException e) {
						e.printStackTrace();
					}
			
			}
	}
    
    // 출력물
    안녕하세요.
    반갑습니다.
    저리가세요.
    // null // 반복문 활용 시 null 값은 안 나옴

 

*코드를 더 줄이고 싶다면?

=> try ~ with ~ resource 구문 (jdk7 버전 이상부터만 가능)
 
[ 표현법 ]
try(기반/보조 스트림 객체 생성) {
 예외가발생할법한구문;
} catch(예외클래스명 e) {
 해당예외발생시실행할구문;
}
 
=> 스트림 객체 생성 구문을 try(여기) 에 작성하게 되면
     스트림 객체 생성 후 해당 try 블럭 내용이 실행된 후 알아서 자원 반납이 됨

		try(BufferedReader br = new BufferedReader(new FileReader("c_buffer.txt"))){
			
			// 반복문 활용
			String value = null;
			while((value = br.readLine()) != null) {
				System.out.println(value);
			}
			
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
        
// 출력물
안녕하세요.
반갑습니다.
저리가세요.