본문 바로가기
뒷북 정리 (국비 교육)/java

[Java] step17. InputOutput

by 규글 2022. 4. 25.

step17. InputOutput

 Input과 output를 합쳐 IO라고 한다. 어떤 값을 memory(메모리)에 읽어들이는 것을 input(입력), memory에 읽어들인 것을 바깥으로 빼내는 것을 output(출력)이라고 한다. 합쳐서 입출력이라고 하는 것이다. 키보드나 마우스, 마이크, 터치 스크린, 파일, 네트워크 등으로 부터 메모리에 입력하고, 콘솔 창이나 또 다른 파일 혹은 또 다른 네트워크로 출력할 수 있다. Memory(메모리)는 variable(변수)을 예로 들 수 있다. 키보드를 통해 입력한 값을 변수에 저장하고, 그 저장된 값을 콘솔 창에 출력하는 것이다.

 Data(데이터)의 실체는 이진수이다. 이진수 여덟 자리가 byte가 되고, 이는 2의 8승, 총 256가지를 표현할 수 있다. 키보드의 각 key에는 그 각각에 할당된 값이 있고, 그 값이 input 되는 것이다. 이를 byte 알갱이가 input되는 것이라 표현했다. 메모장을 예로 들면 이렇게 입력한 byte 알갱이들을 해석한 것이 되는 것이다. 파일도 마찬가지이다. 폴더에 만들어진 text file, audio file, video file 등등은 결국 이런 이진수들의 모임, byte 알갱이들의 모임이다. File에서 모든 알갱이를 읽어들여서 다른 file로 내보내는 것이 복사의 개념이고, network에서 모든 알갱이를 읽어들여서 다른 file로 내보내는 것이 다운로드의 개념이다. 앞으로 이런 작업들을 code를 통해 할 것이다.

 

- 01

import java.io.IOException;
import java.io.InputStream;

public class MainClass01 {
	public static void main(String[] args) {
		// 키보드와 연결된 InputStrem type 의 참조값을 kbd 라는 지역변수에 담기 
		// InputStream 객체는 1 byte 단위 처리 스트림 이다.
		// 영문자 대소문자, 숫자, 특수문자만 처리할수 있다. 
		// 한글 처리 불가 
		InputStream kbd=System.in;
		System.out.println("입력:");
		try {
			// 입력한 키의 코드값 읽어오기 
			int code=kbd.read();
			System.out.println("code:"+code);
			// 코드값에 대응되는 문자 얻어내기
			char ch=(char)code;
			System.out.println("char:"+ch);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

 InputStream 객체는 data 를 1 byte 단위로만 읽어들일 수 있다. 데이터 알갱이를 1 byte씩만 읽을 수 있는 것이다. 입력한 code 값을 읽어오는 것이 InputStream 객체의 read( ) method이다. 이 method는 exception이 발생해서 try~catch로 묶어주어야 한다. 이 객체를 사용하면 영문 대소문자, 숫자, 특수 문자는 처리할 수 있느나, 한글은 처리할 수 없다. 한글은 왜 처리할 수 없을까? 다음의 예시로 가보자.

 

- 02

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

public class MainClass02 {
	public static void main(String[] args) {
		// 1 byte 처리 스트림
		InputStream is=System.in;
		// 2 byte 처리 스트림
		InputStreamReader isr=new InputStreamReader(is);
		try {
			System.out.println("입력:");
			// 한글의 코드 값도 읽어들일수 있다. (2 byte 처리)
			int code=isr.read();
			System.out.println("code:"+code);
			char ch=(char)code;
			System.out.println("char:"+ch);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

 이번엔 InputStreamReader 객체를 만들어보았다. 이 객체는 InputStream과 달리 2 byte 단위 처리를 할 수 있는 객체이다. 한글의 경우 왼쪽 console 창 결과 이미지처럼 code가 12593인 것이 'ㄱ'에 대응한다. 그런데 InputStream 객체는 1 byte 단위의 처리를 할 수 있는데, 그 종류는 256 가지 밖에 되지 않는다. 때문에 한글 처리를 할 수 없는 것이다. 대신 InputStreamReader 객체는 256 x 256 가지나 된다.

 

- 03

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

public class MainClass03 {
	public static void main(String[] args) {
		// InputStream is=System.in;
		// InputStreamReader isr=new InputStreamReader(is);
		// 한 번에 여러 글자를 읽어들여서 String type 으로 리턴해주는 기능을 가지고 있는 객체
		BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
		try {
			System.out.println("입력:");
			// 입력한 문자열 한 줄 얻어내기
			String line=br.readLine();
			// 출력하기
			System.out.println("line:"+line);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

 BufferedReader 객체는 입력한 문자열을 한 번에 읽어들여서 String type으로 return해주는 기능을 가지고 있다. 그 기능은 readLine( ) method를 이용해서 가능해진다.

 

- 04

import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;

public class MainClass04 {
	public static void main(String[] args) {
		// 콘솔창에 출력할수 있는 PrintStream 객체의 참조값
		PrintStream ps=System.out;
		// 학습을 위해서 PrintStream  객체를 부모type OutputStream 으로 받아보기
		// OutputStream 도 1 byte 처리 스트림이다.
		OutputStream os=ps;
		try {
			os.write(97); // 출력하기
			os.write(98);
			os.write(99);
			os.flush(); // 출력된 내용을 방출하기 
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}
}

 앞선 1~3의 예시와 반대로 출력하는 객체를 알아본다. 이름 상으로 정확히 반대인 OutputStream 객체를 이용한다. 이 OutputStream 객체도 InputStream과 마찬가지로 1 byte 단위를 처리한다. InputStream의 read( ) method와 딱 반대로 write( ) method를 이용한다.

 보이는 PrintStream 객체는 OutputStream을 부모 type으로 가지는 객체이다.

 

- 05

import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintStream;

public class MainClass05 {
	public static void main(String[] args) {
		PrintStream ps=System.out;
		OutputStream os=ps;
		// 2 byte 처리 스트림
		OutputStreamWriter osw=new OutputStreamWriter(os);
		try {
			osw.write("하나");
			osw.write("두울");
			osw.write("세엣");
			osw.flush();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

 InputStreamReader 객체와 반대인 OutputStreamWriter 객체를 사용한 2 byte 단위 처리 예제이다.

 

- 06

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintStream;

public class MainClass06 {
	public static void main(String[] args) {
		PrintStream ps=System.out;
		
		OutputStream os=ps;
		OutputStreamWriter osw=new OutputStreamWriter(os);
		BufferedWriter bw=new BufferedWriter(osw);	
		try {
			bw.write("하나");
			bw.newLine(); // 개행 기호 출력
			bw.write("\t두울");
			bw.newLine();
			bw.write("세엣");
			bw.flush();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}		
	}
}

 BufferedReader 객체와 반대인 BufferedWriter 객체이다. 따로 BufferedReader 객체의 readLine( ) method와 반대일 것 없이 write( ) method를 사용했다. 등장하는 newLine( ) method는 개행 기호를 출력해준다. 사실 개행 기호는 '\r\n' 이다. 이 개행 기호를 write( ) method 안에 작성해도 그 그대로를 출력하는 것이 아니라 개행으로 해석해준다. 참고로 '\t' 는 tab이다. 이렇게 write( ) 로 출력을 준비해놓고 flush( ) method를 사용하면 출력된 내용을 한 번에 방출해준다.

 

- 07

import java.io.File;

public class MainClass07 {
	public static void main(String[] args) {
		File myFile=new File("c:/");
		// 폴더 혹은 파일의 목록을 String 배열로 얻어내기 
		String[] names=myFile.list();
		// 목록 문자열을 콘솔에 출력하기
		for(String tmp:names) {
			System.out.println(tmp);
		}
	}
}

 이번에는 글자가 아니라 파일에 대한 예시이다. File 객체를 만들면서 경로를 전달해주고 list( ) method를 사용하면 해당 경로에 있는 폴더와 파일의 목록을 얻을 수 있다.

 

- 08

import java.io.File;

public class MainClass08 {
	public static void main(String[] args) {
		File myFile=new File("c:/");
		// 파일객체 목록(File[]) 을 얻어내기 
		File[] files=myFile.listFiles();
		// 반복문 돌면서 하나씩 참조해서 작업해 보기
		for(File tmp:files) {
			// 만일 해당 파일 객체가 디렉토리이면 
			if(tmp.isDirectory()) {
				System.out.println("[ "+tmp.getName()+" ]");
			}else {// 파일이면
				System.out.println(tmp.getName());
			}
		}
	}
}

 File 객체를 만들어서 사용할 수 있는 method는 굉장히 많다. 그냥 list( ) 가 아닌 listFiles( ) method를 사용하면 파일이나 폴더의 목록을 File[ ] type으로 얻어야하는데, File 객체의 각 항목이 폴더인지 아닌지를 구분할 수 있는 isDirectory( )를 예시로 택했다.

 

- 09

import java.io.File;
import java.io.IOException;

public class MainClass09 {
	public static void main(String[] args) {
		// 이미 만들어 졌거나 혹은 만들 예정인 파일을 access 할수 있는 File 객체 
		File f1=new File("c:/acorn202104/myFolder/gura.txt");
		try {
			// 새로운 파일 만들기 
			f1.createNewFile();
			System.out.println("gura.txt 파일을 만들었습니다.");
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

 java로 파일을 만드는 것이 가능하다. File 객체를 만들면서 경로를 전달할 때 원하는 파일까지 작성하고, createNewFile( ) method를 이용하면 파일을 만들 수 있다.

 

- 10

import java.io.File;

public class MainClass10 {
	public static void main(String[] args) {
		File f1=new File("c:/acorn202104/myFolder/folder1");
		// 디렉토리 만들기 
		f1.mkdir();
		System.out.println("디렉토리를 만들었습니다.");
		for(int i=0; i<10; i++) {
			File tmp=new File("c:/acorn202104/myFolder/new_folder"+i);
			tmp.mkdir();
		}
		System.out.println("디렉토리 10개를 만들었습니다.");
	}
}

 폴더도 마찬가지이다. File 객체를 만들면서 해당 폴더의 이름까지 작성하고, mkdir( ) method를 사용하면 폴더를 만들 수 있다. 이미 만들어졌거나 같은 이름으로 된 폴더가 이미 존재한다면, 이 method는 false를 return 한다.

 폴더의 경우 용량이 크기로 잡히지는 않지만 hard disk 상에서 사실상의 용량을 잡아먹기는 한다. 이름도 그렇고 해당 폴더의 위치 등등의 폴더를 관리하기 위한 정도의 용량을 잡아먹는다고 한다.

 

- 11

import java.io.File;

public class MainClass11 {
	public static void main(String[] args) {
		// c:/acorn2020/myFolder 에 있는 모든 내용(파일 혹은 폴더) 삭제하기
		
		// c:/acorn2020/myFolder 에 access 할수 있는 파일 객체
		File f1=new File("c:/acorn202104/myFolder");
		// File[] 객체 얻어내기
		File[] files=f1.listFiles();
		// 반복문 돌면서 모두 삭제
		for(File tmp:files) {
			tmp.delete();
		}
		System.out.println("c:/acorn2020/myFolder 안에 있는 내용을 모두 삭제했음");
	}
}

 File 객체를 만들면서 전달한 경로 내에 있는 모든 것들을 delete( ) method로 삭제할 수 있다. C 자체를 삭제하지 않도록 주의한다.

 

- 12

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

public class MainClass12 {
	public static void main(String[] args) {
		// 문자열을 저장할 파일을 만들기위한 File 객체
		File memoFile=new File("c:/acorn202104/myFolder/memo.txt");
		try {
			// 실제로 파일이 존재하는지 여부 
			boolean isExist=memoFile.exists();
			if(!isExist) {// 존재하지 않으면 
				// 파일을 실제로 만든다.
				memoFile.createNewFile();
			}
			// 파일에 문자열을 출력할 객체
			FileWriter fw = new FileWriter(memoFile);
			fw.write("김구라\r\n");
			fw.write("\t해골\r\n");
			fw.write("원숭이");
			fw.flush();
			fw.close();
			
			System.out.println("파일에 문자열을 저장 했습니다.");
		}catch(IOException ie) {
			ie.printStackTrace();
		}
	}
}

 실제로 파일이 존재하는지 아닌지를 먼저 체크하고자 한다면 exists( ) method를 사용한다. 물론 해당 파일이 이미 존재한다면 존재하는 그대로의 객체이기 때문에 덮어쓰기와 같은 동작을 하게 된다.

 File 객체에는 문자열을 출력하는 기능이 없다. 그래서 파일을 만들고 그 파일에 문자열을 출력하고 싶으면 FileWriter 객체를 사용해야 한다. 이 FileWriter 객체를 만드는 방법은 5종류가 있는데, 객체를 만들면서 File 객체를 전달해주고 write( )와 flush( ) method를 사용해서 내용을 작성할 수 있다. 원하는 내용을 출력했다면 마지막에는 close( ) method로 닫아주어야 한다고 했다. 물론 이게 없다고 내용이 없는 상태로 파일이 생성되지 않는다거나 하는 것은 아니다. 이 method를 수행하면 해당 파일에 대한 편집을 더 이상 할 수 없는 상태가 되는, 일종의 마침 표시라고 생각하면 되겠다.

 

- 13

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

public class MainClass13 {
	public static void main(String[] args) {
		File memoFile=new File("c:/acorn202104/myFolder/memo.txt");
		try {
			if(!memoFile.exists()) {
				System.out.println("파일이 존재 하지 않습니다.");
				return; // 메소드 끝내기 
			}
			// 파일에서 문자열을 읽어들일 객체
			FileReader fr = new FileReader(memoFile);
			/*
			 *  memo.txt file에 몇 개의 문자가 있는지는 알 수 없으나
			 *  아래 3줄의 코드를 memo.txt file에 있는 모든 문자를
			 *  읽어올 때까지 반복수행하고 싶음.
			 */
			while(true) {
				int code = fr.read();
				// 만약 다 읽었다면 반복문을 탈출
				if(code == -1) {
					break;
				}
				// 아니라면 해당 문자 얻어내기
				char ch = (char)code;
				// 해당 문자 출력하기
				System.out.print(ch);
			}
		}catch(IOException ie) {
			ie.printStackTrace();
		}
	}
}

 FileReader 객체는 파일의 text를 읽을 수 있는 객체이다. 객체의 read( ) method는 한 글자를 읽어서 정수로 반환한다. 이 method는 text를 다 읽어서 더 이상 읽을 수 있는 text가 없다면 -1 을 return 한다. 이를 이용해서 while문으로 전체 text 반복해서 읽어내고 반복문을 탈출할 수 있다. 이를 글자로 다시 반환하려면 char type으로 casting 해주면 된다.

 그리고 console 창에 값을 출력하는 System.out.print("xxx"); 는 이전까지 사용했던 것과는 뭔가 차이가 있다. 생김새가 print( ) 와 println( )으로 다르다. 'ln'을 붙이면 개행기호도 함께 출력하나, 이를 뺀 method는 text file 그대로 출력한다.

 

- 14

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;

public class MainClass14 {
	public static void main(String[] args) {
		File memoFile=new File("c:/acorn202104/myFolder/memo.txt");
		try {
			if(!memoFile.exists()) {
				System.out.println("파일이 존재 하지 않습니다.");
				return; // 메소드 끝내기 
			}
			// 파일에서 문자열을 읽어들일 객체
			FileReader fr=new FileReader(memoFile);
			BufferedReader br=new BufferedReader(fr);
			
			// 반복문 돌면서
			while(true) {
				String line = br.readLine();
					if(line==null) {
						break;
					}
				System.out.println(line);
			}
		}catch(IOException ie) {
			ie.printStackTrace();
		}
	}
}

 이전 예시와 다르게 이번 예시는 BufferedReader 객체를 사용해서 text 한 글자씩이 아니라 한 줄씩 뽑는 것이다. 이렇게 text 한 줄씩 뽑지만 개행 기호는 없다. 그래서 반복문 내에서 println( ) method를 사용하면 알아서 개행된다.

 

- 15

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;

public class MainClass15 {
	public static void main(String[] args) {
		File memoFile=new File("c:/acorn202104/myFolder/memo.txt");
		// 필요한 객체의 참조값을 담을 지역 변수를 미리 만든다.
		FileReader fr=null;
		BufferedReader br=null;
		try {
			if(!memoFile.exists()) {
				System.out.println("파일이 존재 하지 않습니다.");
				return; // 메소드 끝내기 
			}
			// 파일에서 문자열을 읽어들일 객체의 참조값을 미리 만들어둔 지역 변수에 담는다.
			fr=new FileReader(memoFile);
			br=new BufferedReader(fr);
			while(true) {
				// 반복문 돌면서 문자열을 줄단위로(개행기호 기준) 읽어낸다.
				String line=br.readLine();
				if(line==null) {// 더이상 읽을 문자열이 없으면
					break;// 반복문 탈출
				}
				// 읽은 문자열 출력하기 
				System.out.println(line);
			}
		}catch(IOException ie) {
			ie.printStackTrace();
		}finally { // 예외가 발생하던 안하던 반드시 실행이 보장되는 블럭 
			// 마무리 작업을 한다 (보통 열었던 스트림 객체를 닫는 작업을 한다)
			try {
				if(fr!=null) fr.close();
				if(fr!=null) br.close();
			} catch (Exception e) {}
		}
	}
}

 이전 예시와 같은 기능이지만 조금 더 꼼꼼하게 코딩한 예시이다. 보통은 열었던 Stream 객체를 닫아주는 작업을 하기 때문에 모든 작업을 마쳤을 때 닫아주는 작업을 finally 에서 해준다. 그리고 이렇게 finally 안에서 객체를 참조하기 위해서는 객체의 참조값을 field를 만들어서 담아줘야 한다. 물론 finally 뿐만 아니라 작업을 수행할 try 안에서도 마찬가지이다.

 그런데 객체의 참조값 자리가 null 이 될 수도 있다. 언제 null이 될까? 코딩할 때 잘못되어서 null이 되는 경우가 있을 수 있다고 한다. 예를 들어 file 자체가 들어가지 않는다거나 하는 등의 일이 발생할 수 있다고 한다.

 

- 16

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

public class MainClass16 {
	public static void main(String[] args) {
		// 필요한 참조값을 담을 지역 변수를 미리 만든다.
		FileInputStream fis=null;
		FileOutputStream fos=null;
		
		try {
			// 파일에서 byte 를 읽어낼 객체 
			fis=new FileInputStream("c:/acorn202104/myFolder/1.jpg");
			// 읽어낸 byte 를 출력할 객체 
			fos=new FileOutputStream("c:/acorn202104/myFolder/copied.jpg");
			// 반복문 돌면서 읽어내기
			while(true) {
				// 1 byte 씩 읽어들인다.
				int data=fis.read();
				System.out.println(data);
				if(data==-1) {// 더 이상 읽을 데이터가 없으면
					break;// 반복문 탈출
				}
				// 읽은 1 byte  출력
				fos.write(data);
				fos.flush();
			}
			System.out.println("파일을 성공적으로 복사 했습니다.");
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}finally {
			try {
				if(fis!=null)fis.close();
				if(fos!=null)fos.close();
			}catch(IOException ie) {}
		}
	}
}

 이번 예시는 파일 자체를 복사하는 예시이다. 처음의 Input/OutputStream과 마찬가지로 File과 관련된 FileInput/OutputStream 객체가 존재한다. 이 객체는 file의 data를 1 byte 단위로 객체의 내용을 읽고, 내보낸다. 그런데 만약에 파일의 크기가 굉장히 크다고 생각해보자. 쌀포대가 있는데, 쌀을 한 톨씩 보내는 것이다. 굉장히 이상하지 않은가? 이럴 경우는 바가지로 쌀을 보낼 수 있으면 좋겠다. 다음이 바로 그런 예시이다.

 

- 17

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

public class MainClass17 {
	public static void main(String[] args) {
		// 필요한 참조값을 담을 지역 변수를 미리 만든다.
		FileInputStream fis=null;
		FileOutputStream fos=null;
		
		try {
			// 파일에서 byte 를 읽어낼 객체 
			fis=new FileInputStream("c:/acorn202104/myFolder/1.jpg");
			// 읽어낸 byte 를 출력할 객체 
			fos=new FileOutputStream("c:/acorn202104/myFolder/copied2.jpg");
			// byte 데이터를 읽어낼 방 1024 개짜리 배열객체 생성
			byte[] buffer=new byte[1024];
			// 반복문 돌면서 읽어내기
			while(true) {
				// byte[] 객체를 전달해서 읽어내고 리턴되는 데이터는 읽은 byte 의 개수가 리턴된다.
				int readedByte=fis.read(buffer);
				System.out.println(readedByte);
				if(readedByte==-1) {// 더 이상 읽을 byte 가 없다면 
					break;// 반복문 탈출
				}
				// byte[] 에 있는 데이터를 0번 방으로부터 읽은 만큼(readedByte) 출력하기
				fos.write(buffer, 0, readedByte);
				fos.flush(); // file이 큰거면 출력한 만큼 방출해야함
			}
			System.out.println("파일을 성공적으로 복사 했습니다.");
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}finally {
			try {
				if(fis!=null)fis.close();
				if(fos!=null)fos.close();
			}catch(IOException ie) {}
		}
	}
}

 특정 크기의 바가지로 데이터를 옮기다가 남은 내용이 바가지의 크기보다 작아지는 경우가 생길 수도 있는데, 그런 경우는 그냥 그 남은 크기를 가지고 데이터를 옮긴다.

'뒷북 정리 (국비 교육) > java' 카테고리의 다른 글

[Java] step19. JDBC  (0) 2022.05.03
[Java] step18. Socket  (0) 2022.04.28
[Java] step16. Thread  (0) 2022.04.25
[Java] step15. Swing  (0) 2022.04.25
[Java] step14. Exception  (0) 2022.04.24

댓글