본문 바로가기
JAVA 정리노트

JAVA I/O, stream에 대하여

by Dodledd 2024. 2. 2.

I/O와 Stream이란?

I/O 는 Input / Output의 줄임말이다. 말 그대로 넣고 꺼내고,(쓰고 읽고) 라고 생각하면 편하다

Stream이란 데이터가 오고가는 통로다.

 

Stream의 특징

1.  단방향 :  무전기를 생각하면 된다. 무전기는 단방향통신, 즉 한 명은 말하기만되고 한 명은 듣기만 된다.

자바의 stream도 마찬가지다. 입력으로 통로를 만들면 입력만 가능하고 출력으로 통로를 만들면 통로만 가능하다.

만약 두 개 다 사용하고 싶다면 입력통로, 출력통로 두 개를 만들어줘야한다.! 

 

2. 선입선출(FIFO) : 말 그대로 먼저 넣은게 먼저 나온다. 단순하게 생각하면 된다.

 

3. 시간지연이 발생할 수 있다. 빛도 속도가 있어서 시간지연이 발생하는데 이 세상에 시간지연이 발생하지 않는 건 없다.

 

Stream의 종류들

통로의 사이즈로 구분할 수 있다.

바이트스트림(byte Stream) : 1byte만 왔다갔다 할 수 있는 통로이다. 입력은 Inputstream, 출력은 OutputStream이다.

 

문자 스트림 : 2byte만 왔다갔다 할 수 있는 통로이다. 입력은 Reader, 출력은 Writer이다.

 

또 외부 파일과 직접 연결유무로 구분할 수도 있는데

기반 스트림 : 외부파일과 직접적으로 연결된 통로로써 무조껀 필수적으로 열어야 한다. (주연)

보조 스트림 : 기반 스트림을 보조하는 친구이다. ex)속도가 빨라지거나 유용한 기능을 제공한다. (조연)

*보조 스트림은 단독으로 사용이 불가능하다, 조연이 주인공이 될 수 없는 논리이다.

 

바이트 스트림을 사용해보자

메서드명이 fileSave()니까 이건 입력이다. output=출력=쓰기=내보낸다 느낌이다.

public void fileSave() {
		FileOutputStream fout = null;
		
		try {
			//1. FileOutputStream 객체 생성 => 해당파일과의 연결통로 만들기
			//해당 파일이 없으면 새로만들어주고 통로연결 / 있으면 있는 파일에 통로연결
			/*
			 * true 미작성시 => 해당 파일이 존재할 경우 기존의 데이터를 덮어씌움(기본값이 false)
			 * true 작성시 => 해당파일이 존재할 경우 기존의 데이터에 이어서 작성
			 */
			fout = new FileOutputStream("a_byte.txt", true); 
			
			
			//2. 파일에 데이터를 출력해보자
			//   숫자를 출력하든 문자를 출력하든 실제로는 파일에 문자로 기록이된다(아스키코드표)
			//   (0~127)
			fout.write('9'); // 'a'문자가 저장
			fout.write('c'); // 'b'문자가 저장
			fout.write('강'); // 한글은 2byte짜리여서 꺠져서 저장됨  => 바이트스트림으로는 제한이 있다.
			
			byte[] arr = {102, 103, 104};
			fout.write(arr); // cde 문자가 저장됨
			
			// fout.write(byte[] b, int off, int len) : byte배열의 off인덱스부터 len개 만큼 출력
			fout.write(arr, 0, 2);
			
			
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally { // 이 안에 작성된 코드는 어떤 예외가 있더라도 마지막에 무조건 실행하고 빠져나가도록 한다.
			try {
				//3. 스트림을 다 이용했으면 무조건 반납하기!(반드시!)
				fout.close();
			} catch (IOException e) {
				e.printStackTrace();
			}

		}

 

 

 

위에서 작성한 것을 보고싶다면 ? 반대로 생각하면 된다. Input=입력=읽기=불러오기 느낌이다. 

public void fileRead() {
		
		//FileInputStream : 파일로부터 데이터를 1바이트 단위로 입력받는 스트림
		
		// 1. 스트림생성 (통로만들기)
		// 2. 스트림 통해서 입력받아옴(메소드활용)
		// 3. 사용이 끝난 스트림 반납
		
		FileInputStream fin = null;
		
		try {
			// 1. FileInputStream객체생성 ==> 해당 파일과 연결통로가 만들어짐
			fin = new FileInputStream("a_byte.txt");
			
			// 2. 파일로부터 데이터를 읽어들이고자 할 때 read메소드 사용
			//    1byte단위로 하나씩 읽어옴 / 숫자로 읽어들임
//			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()); // 파일의 끝을 만나는 순간 -1을 반환한다.
			
			
			
			while(true) {
				int value = fin.read();
				if (value == -1) {
					break;
				}
				System.out.print((char)value);
			}
			
			//반복출력 2. 
			int value = 0;
			while((value = fin.read()) != -1) {
				System.out.print((char)value);
			}		
			
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally { // 이 안에 작성된 코드는 어떤 예외가 있더라도 마지막에 무조건 실행하고 빠져나가도록 한다.
			try {
				//3. 스트림을 다 이용했으면 무조건 반납하기!(반드시!)
				fin.close();
			} catch (IOException e) {
				e.printStackTrace();
			}

		}
	}

 

 

문자 기반 스트림을 사용해보자

문자 기반 스트림은 바이트 스트림과 다른 점 두 개

1. 통로가 2배가 된다. 1byte -> 2byte

2. 스트림 명령어? 가 다르다 . 바이트에서 ~OutputStream = ~Writer 이고 ,  ~InputStream = ~Reader 느낌이 된다.

 

그럼 코드로 확인해보자

 

public void fileSave() {
		// FileWriter : 파일로 데이터를 2바이트 단위로 출력할 수 있는 스트림
		FileWriter fw = null;
		
		try {
			// 1. FileWriter 객체 생성
			fw = new FileWriter("b_char.txt"); // 해당파일이 없으면 새로 만들고 연결 / 있으면 그냥 연결
			
			// 2. write메소드를 활용하여 데이터 출력
			fw.write("와후 IO가 너무 재미있다."); //실제로는 해당 문자열의 문자가 하나씩 하나씩 전송되는 개념
			fw.write(" ");
			fw.write('A');
			fw.write("\n");
			
			char[] arr = {'a','p','p','l','e'};
			fw.write(arr);
		
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			try {
				fw.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	public void fileRead() {
		//FileReader : 파일로부터 데이터를 2바이트 단위로 입력받을 수 있는 스트림
		
		// 알아서 try가 다 처리되면 해당 스트림을 반납한다.
		try(FileReader fr = new FileReader("b_char.txt")) {
			// 1.FileReader객체생성 -> 파일로부터 데이터를 읽어들일 수 있는 통로 연결!
			
			
			// 2. read메소드를 이용해서 파일을 읽어들이자!
			// 파일의 끝을 만나는 순간 -1반환
			int value = 0;
			while((value = fr.read()) != -1) {
				System.out.print((char)value);	
			} 
			
			
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		/*
		 * try-with-resource구문 => 자원을 자동으로 반납해준다.
		 * 
		 * try(try에서 사용할 스트림객체 생성구문){
		 * 
		 * } catch(예외클래스 e){
		 * 
		 * }
		 */
		
		
		
	}

 

여기까지 봤으면 눈치챘어야 하는게 있다.

항상 try - catch문을 사용해서 예외처리를 해주는 것이다.

간단하게 생각하면 그냥 무조껀 붙여줘야한다는 느낌으로 받아들여진다.

조금 더 생각해보면 자바는 그냥 안정성을 매우 중요하게 생각하는 언어로 나는 !(자바가) 프로그램 입, 출력에 대한 오류에 대해 예외처리를 해 !! 라고 우리에게 말하고 있다고 생각하면된다.