TIL/Java

[Java] 입출력(IO) - 1. 입력스트림, 출력스트림

yndev 2022. 1. 13. 23:25

File클래스

- JDK 1.0부터 지원하는 API로 파일 처리를 수행하는 대표적인 클래스

- 대상 파일에 대한 인스턴스를 생성하고 파일 크기, 파일 속성, 파일 이름 등의 정보와 파일 생성 및 삭제 기능을 제공

 

File 클래스를 이용해서 인스턴스 생성

- 대상 파일이 존재하지 않아도 인스턴스를 생성할 수 있다.

- 최초 실행 시 새롭게 파일이 만들어지면 true가 반환되며, 파일이 한 번 생성되고 난 이후는 새롭게 파일을 만들지 않기 때문에 false를 반환한다.

- try-catch까지 작성 후에 실행해보면 true반환, 패키지 Refresh 한 후에 test.txt 파일 생성이 된다.

		File file = new File("src/com/greedy/section01/file/test.txt");
		
		try {
			/* createNewFile()을 통해 파일을 생성할 수 있고 성공 실패 여부를 boolean으로 반환한다. */
			boolean createSuccess = file.createNewFile();

			System.out.println("createSuccess : " + createSuccess);
			
		} catch (IOException e) {
			e.printStackTrace();
		}
		/* 생성한 파일의 정보 */
		System.out.println("파일의 크기 : " + file.length() + "byte");
		System.out.println("파일의 경로 : " + file.getPath());
		System.out.println("현재 파일의 상위 경로 : " + file.getParent());
		/* 절대 경로란 최상위 루트 위치부터의 경로를 의미한다. */
		System.out.println("파일의 절대 경로 : " + file.getAbsolutePath());
		
		/* 파일 삭제 
		 * 삭제 후 성공 실패 여부를 boolean으로 반환한다.
		 * */
		boolean deleteSuccess = file.delete();
		
		System.out.println("deleteSuccess : " + deleteSuccess);
	}


입출력(IO)

- Input과 Output의 약자로 컴퓨터 내부 또는 외부 장치와 프로그램 간의 데이터를 주고 받는 것

- 다양한 매체에 존재하는 데이터들을 사용하기 위해 입출력 데이터를 처리할 공통적인 방법으로 스트림을 이용

 

스트림(Stram)

- 데이터를 읽고 쓰기 위해서 자바에서 제공하는 클래스

- 모든 스트림은 단방향이며 각각의 장치마다 연결할 수 있는 스트림 존재

바이트단위처리 1byte단위                         문자단위처리 2-3byte단위

 

스트림 종류

(외울 필요까진..)

자바 프로그램과 연결되는 외부 데이터의 타입이 무엇인지는 클래스의 이름을 보고 유추가 가능하다.
InputStream/OutputStream/Reader/Writer를 빼고 남은 단어가 바로 외부 데이터의 타입이다.   
Ex) FileInputStream은 InputStream을 제거하고 남은 단어가 File이므로 외부 데이터는 File임을 알 수 있다.

FileInputStream

- 대상 파일이 존재하지 않는 경우 발생하는 FileNotFoundException에 대해 핸들링 해야 한다. 
- 인스턴스만 생성한 후 실행해보면 예외가 발생한 것을 볼 수 있다. 
- 파일을 직접 생성한 뒤 다시 실행하면 예외가 발생하지 않는데 스트림 인스턴스가 정상적으로 생성된 것이다.

- read()는 throws IOException 이므로 add catch clause to surrounding try 눌러서 catch block 추가
- read() : 파일에 기록된 값을 순차적으로 읽어오고 더 이상 읽어올 데이터가 없는 경우 -1 반환

 

- 자원 반납을 해야 하는 경우 
 1. 장기간 실행 중인 프로그램에서 스트림을 닫지 않는 경우 다양한 리소스에 누수(leak)가 발생한다.
 2. 뒤에서 배우는 버퍼를 이용하는 경우 마지막에 flush()로 버퍼에 있는 데이터를 강제로 전송해야 한다.
    만약 잔류 데이터가 남은 상황에서 추가로 스트림을 사용한다면 데드락(deadlock) 상태가 된다. 
    판단하기 어렵고 의도하지 않은 상황에서도 이런 현상이 발생할 수 있기 때문에 마지막에는 flush()를 
    무조건 실행해주는 것이 좋다.
    close() 메소드는 자원을 반납하며 flush()를 해주기 때문에 close()만 제대로 해주어도 된다.

		FileInputStream fin = null;
		
		try {
			fin = new FileInputStream("src/com/greedy/section02/stream/testInputStream.txt");
			
			int value;
			
//			while((value = fin.read()) != -1) {
//				/* 값을 정수로 읽어온다. */
//				System.out.println(value);
//				
//				/* 문자로 출력하고 싶은 경우 형변환하면 된다. */
//				System.out.println((char) value);
//			}
			
			/* 한글 값을 입력하는 경우 한글이 깨져서 나온다. 
			 * 한글은 한 글자에 3byte이기 때문에 3byte 데이터를 1byte씩 읽어오면 글자가 깨지게 된다. 
			 * */
			
			/* 1byte씩 읽어와야 하는 경우도 존재하긴 하지만 대부분의 경우 비효율적이다. 
			 * byte배열을 이용해서 한 번에 데이터를 읽어오는 방법도 제공한다.
			 * */
			
			/* File 클래스의 length()로 파일의 길이를 알 수 있다. */
			System.out.println("파일의 길이 : " + new File("src/com/greedy/section02/stream/testInputStream.txt").length());
			
			/* 파일의 길이 만큼의 byte 배열을 만든다. */
			int fileSize = (int) new File("src/com/greedy/section02/stream/testInputStream.txt").length();
			byte[] bar = new byte[fileSize];
			
			/* read() 메소드의 인자로 생성한 byte 배열을 넣어주면
			 * 파일의 내용을 읽어서 byte 배열에 기록해준다. */
			fin.read(bar);
			
			/* 해당 스트림은 파일의 끝까지 다 읽어들인 스트림이기 때문에
			 * 위에 1바이트씩 읽어온 while 반복문을 주석해야 정상적으로 동작한다.
			 * */
			for(int i = 0; i < bar.length; i++) {
				System.out.print((char) bar[i]);
			}
			
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			/* fin 인스턴스가 null이 아닌 경우 자원 반납을 해야 한다. */
			if(fin != null) {
				try {
					/* 자원 해제를 하는 경우에도 IOException을 핸들링 해야 한다. 
					 * 이미 자원이 반납된 경우 발생하는 Exception이다.
					 * */

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

FileReader

- FileInputStream과 사용하는 방법이 거의 동일하다.
- 단, byte단위가 아닌 character단위로 읽어들이는 부분이 차이점이다.
- 따라서 2바이트던 2바이트던 글자 단위로 읽어오기 때문에 한글을 정상적으로 읽어올 수 있다.

		FileReader fr = null;
		
		try {
			/* 파일이 존재하지 않는 경우 파일을 찾지 못한다는 예외가 발생하므로
			 * 파일을 추가해서 정상적으로 스트림이 생성되게 한다.
			 * */
			fr = new FileReader("src/com/greedy/section02/stream/testReader.txt");
			
			/* 파일 내용을 읽어오는 것도 동일하다. 
			 * 한글 값을 입력해도 하나의 글자 단위로 읽어온다. */
//			int value;
//			while((value = fr.read()) != -1) {
//				System.out.print((char) value);
//			}
			
			/* byte 배열로 읽어오면 한글은 깨지게 되므로
			 * char 배열로 내용을 읽어오는 기능을 제공하고 있다. 
			 * */
			char[] carr = new char[(int) new File("src/com/greedy/section02/stream/testReader.txt").length()];
			
			fr.read(carr);
			
			for(int i = 0; i < carr.length; i++) {
				System.out.print(carr[i]);
			}
			
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			if(fr != null) {
				try {
					fr.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}

FileOutputStream

- 프로그램의 데이터를 파일로 내보내기 위한 용도의 스트림이다.

- 1바이트 단위로 처리한다.

- FileNotFoundException을 핸들링해야 하는 것은 동일하지만 실행해도 에외가 발생하지 않고 인스턴스가 잘 생성 된다.

  =>입력스트림과의 차이점
- OutputStream의 경우 대상 파일이 존재하지 않으면 파일을 자동으로 생성해준다.
- FileNotFoundException이 핸들링 하는 부분은 경로 => (지정된 경로를 찾을 수 없습니다)

- 두 번째 인자로 true를 전달하면 이어쓰기가 된다.

- 전달하지 않으면 (false) 덮어쓰기가 된다.

		FileOutputStream fout = null;
		
		try {
			fout = new FileOutputStream("src/com/greedy/section02/stream/testOutputStream.txt"/*, true*/);
			
			/* write() 메소드는 IOException을 핸들링 해야 한다. */
			fout.write(97);
			
			/* byte 배열을 이용해서 한 번에 기록할 수도 있다. 
			 * 10 : 개행문자 (엔터)
			 * */
			byte[] bar = new byte[] {98, 99, 100, 101, 102, 10, 103, 104, 105};
			fout.write(bar); 	//개행처리해서 2행까지 나옴
			
			/* bar의 1번 인덱스부터 3의 길이만큼 파일에 내보내기 */
			fout.write(bar, 1, 3);
			
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			if(fout != null) {
				try {
					fout.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}

FileWriter

- 프로그램의 데이터를 파일로 내보내기 위한 용도의 스트림이다.

- 1글자 단위로 데이터를 처리한다.

- FileNotFoundException을 핸들링해야하지만 실행해도 예외는 발생하지 않는다.
- Writer의 경우 대상 파일이 존재하지 않으면 파일을 자동으로 생성해준다.
- 경로가 존재하지 않는 경우에는 예외가 발생한다. 
- 두 번째 인자로 true를 전달하면 이어쓰기가 된다.
- 인자를 전달하지 않으면 (또는 false) 덮어쓰기가 된다.

- 문자 단위 출력도 내부 버퍼를 사용하므로 쌓여있는 데이터를 flush()로 내보내줘야 최종적으로 파일에 출력되는 모습을 확인할 수 있다.

- 또는 close()로 자원을 반납하면 반납 전에 flush()가 호출되므로 파일에 출력되는 모습을 확인할 수 있다. 

		FileWriter fw = null;
		
		try {

			fw = new FileWriter("src/com/greedy/section02/stream/testWriter.txt"/*, true*/);
			
			/* write() 메소드도 IOException을 핸들링 해야 한다. */
			fw.write(97);
		
			//fw.flush();
			
			/* 문자 기반 스트림은 직접 char 자료형으로 내보내기도 가능하다 */
			fw.write('A');
			
			/* 혹은 char 배열도 가능하며 */
			fw.write(new char[] {'a', 'p', 'p', 'l', 'e'});
			
			/* 문자열도 가능하다 */
			fw.write("우리나라 대한민국");
			
		} catch (IOException e) {
			e.printStackTrace();
		}	finally {
			if(fw != null) {
				try {
					fw.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
		
	}