8장_입출력

IkSun·2023년 5월 23일

CHAPTER 08 입출력 스트림과 파일 입출력

8.1 자바의 입출력 스트림
8.2 문자 스트림과 파일 입출력
8.3 바이트 스트림과 파일 입출력
8.4 버퍼 입출력과 파일 입출력
8.5 File 클래스
8.6 파일 입출력 응용: 파일 복사


문자 입출력 스트림 : Reader, Writer
바이트 입출력 스트림 : InputStream, OutputStream


스트림

  • 스트림 입출력
    • 버퍼를 가지고 순차적으로 이루어지는 입출력
  • 자바의 입출력 스트림
    • 응용 프로그램과 입출력 장치를 연결하는 소프트웨어 모듈
      • 입력 스트림 : 입력 장치로부터 자바 프로그램으로 데이터를 전달
      • 출력 스트림 : 출력 장치로 데이터 출력

자바의 입출력 스트림 특징

  • 스트림의 양끝에 입출력 장치와 자바 응용프로그램 연결
  • 스트림은 단방향
    • 입력과 출력을 동시에 하는 스트림 없음
  • 입출력 스트림 기본 단위
    • 바이트 스트림의 경우 : 바이트
    • 문자 스트림의 경우 : 문자(자바에서는 문자1개 : 2바이트)
  • 선입선출(FIFO : First In First Out) 구조

자바의 입출력 스트림 종류

  • 바이트 입출력 스트림과 문자 입출력 스트림
    • 바이트 스트림
      • 입츨력되는 데이터를 단순 바이트로 처리
      • 이미지, 영상, 소리, 텍스트 모두 사용 가능
      • 예) 바이너리 파일을 읽는 입력 스트림
        • 바이너리 장점 : 문자 이외의 여러가지가 제공될 수 있다(기억)
    • 문자 스트림
      • 문자만 입출력하는 스트림
      • 문자가 아닌 바이너리 데이터는 스트림에서 처리하지 못함
      • 예) 텍스트 파일을 읽는 입력 스트림
  • JDK는 입출력 스트림을 구현한 다양한 클래스 제공

숫자 31을 정수(2진수)로 저장, 문자 "31"를 바이너리(바이트)단위로 저장. 차이점은?

  • 정수 31 : 11111(2진수), 0x 00 00 00 1f (16진수 : 1f)
  • 문자 "31" : 바이트는 3과 1의 연속된 정보
    • '3' 은 16진수로 0x33, '1'은 16진수 0x31
      • 0x 00 33 00 31 UTF-16(2byte 기준 : 총 4byte)
      • 0x 33 31 UTF-8(1byte 기준 : 총 2byte)

표준 입력 스트림 System.int 에 InputStreamReader 스트림을 연결하는 사례

  • 키보드에서 입력한 정보는 'byte 데이터' 이다.
  • 이것을 문자 정보로 받아서 사용하기 위해, 문자로 변환하는 과정을 거쳐야 한다.
  • 이떄 사용하는 것이 'InputStreamaReader'를 이용하는 것
InputStreamReader rd = new InputStreamReader(System.in)
//키보드에서 문자 읽음. 단 현제 byte 값이므로 정수 변수 c 에 값을 할당.
int c = rd.read()  

[주의]
문자 전용 '유니 코드' 기반의 파일도 바이트 단위로 관리가 되고
'바이너리'기반의 파일도 바이트 단위로 관리가 된다.


문자 스트림

  • 문자 스트림
    • 유니코드(2바이트)로 된 문자를 입출력 하는 스트림
      • 문자로 표현되지 않는 데이터는 다루지 못함 -> 문자만 가능!
      • 문자 스트림은 이미지, 동영상과 같은 바이너리 데이터는 입출력 할 수 없음
  • 문자 스트림을 다루는 클래스
    • Reader/Writer
      • 추상 클래스 문자 스트림을 다루는 모든 클래스의 슈퍼 클래스
        • reader, writer 를 상속받아 사용해야한다.
    • InputStreamReader/OutputStreamReader
      • 바이트 스트림과 문자 스트림을 연결시켜주는 다리 역할
      • 지정된 문자 집합 이용
      • InputStreamReader : 바이트를 읽어 문자로 인코딩
      • OutputStreamReader : 문자를 바이트로 디코딩하여 출력
    • FileReader/FileWriter
      • 텍스트 파일에서 문자 데이터 입출력

FileReader 를 이용한 파일 읽기

  • 파일 전체를 읽어 화면에 출력하는 코드 샘플
FileReader fin = new FileReader("c:\\windows\\system.ini");   
int c;
while((c = fin.read()) != -1) {  
   System.out.print((char)c); 
}
fin.close(); 

예제 8-1 : FileReader 로 텍스트 파일 읽기 - system.ini 파일 읽기

import java.io.*;

public class FileReaderEx {
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		FileReader fin = null;
		try {
            //유니코드 기반의 바이트 정보로 저장되어 있는 문자 파일(*.ini) 내용을 읽어들이기
            //system.ini 파일을 열고 파일과 입력 바이트 스트림 객체 fin 연결
			fin = new FileReader("c:\\windows\\system.ini");   
			int c;
            //파일 끝까지 바이트 단위로 끊어서 c에 읽어 들임
            //파일의 끝을 만나면 read() 는 -1 리턴
			while((c = fin.read()) != -1) {  
				System.out.print((char)c); //바이트 c를 문자로 변환하여 화면에 출력
			}
			fin.close(); //스트림 닫기, 파일도 닫힘, 더이상 스트림으로부터 읽을 수 없음
		} catch (IOException e) {
			System.out.println("입출력오류");
		}
	}
}


문자 집합과 InputStreamReader 로 텍스트 파일 읽기

  • 유니코드 기반의 바이트 정보로 저장되어있는 txt 파일의 내용을 읽어들이는 코드
  • FileReader 클래스는 파일의 내용을 바로 읽어들여서 응용프로그램에 사용했다면
  • InputStreamReader 는 2단계를 거치고 (FileInputSteam, InputStreamReader) 문자집합 정보를 전달한다.
FileInputStream fin = new FileInputStream("c:\\temp\\hangul.txt");
InputStreamReader in = new InputStreamReader(fin, "MS949");

while((c = in.read()) != -1) {
   System.out.print((char)c);
}

예제 8-2 : InputStreamReader로 한글 텍스트 파일 읽기

  • InputStreamReader 를 이용하여 MS949 문자 집합으로 한글 텍스트 파일을 읽고 출력하라.
import java.io.*;

public class FileReaderHangulSuccess {
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		InputStreamReader in = null;
		FileInputStream fin = null;
		try {
        	// 유니토드 기바의 바이트 정보로 저장되어 있는 txt 파일의 내용을 읽어들이기
			fin = new FileInputStream("/Users/seonik/Documents/temp/hangul.txt");  
            
            //예제 8-3 : 한글 텍스트 파일 읽기(문자 집합 지정이 잘못된 경우)
			//in = new InputStreamReader(fin,"US-ASCII"); //오류
			in = new InputStreamReader(fin, "MS949"); //한글 완성형 확장형 문자집합 : MS949
			int c;
			
			System.out.println("인코딩 문자 집합은 " + in.getEncoding());
			while((c = in.read())!= -1) {
				System.out.print((char)c);
			}
			in.close();
			fin.close();
		} catch(IOException e) {
			System.out.println("입출력 오류");		
		}
	}
}
//result
인코딩 문자 집합은 MS949
가나다라마바사아자차카타파하

FilrWriter 사용 예

  • c:\temp\text.txt 파일에 문자 출력 스트림을 생성하는 코드
FileWriter fout = new FileWriter("c:\\temp\\text.txt");
  • 파일 쓰기
    • 문자 단위 쓰기

      FileWriter fout = new FileWriter("c:\\temp\\text.txt");
      fout.write('A'); //문지 'A' 를 파일에 출력 -> text.txt 파일에 'A' 쓴다.
      fout.close(); //스트림을 닫는다. 파일을 저장하고 닫는다.
    • 블록 단위 쓰기

      char [] buf = new char[1024];
      
      //buf[] 배열의 처음부터 배열 크기(1024개 문자)만큼 쓰기
      fout.write(buf, 0, buf.length);

예제 8-4 : 키보드 입력을 파일로 저장하기

  • 키보드에서 입력 받은 데이터를 c:\\temp\\text.txt 파일에 저장하는 코드를 작성하라.
//하나씩이 아니라 한줄 단위를 받아들임
//엔터를 치면 그 이전까지 작성된 문자열을 하나의 문자열로 받는다
import java.io.*;
import java.util.*;

public class FileWriterEx {
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Scanner scanner = new Scanner(System.in);
		FileWriter fout = null;
		try {
			fout = new FileWriter("/Users/seonik/Documents/temp/text.txt");
			while(true) {
				String line = scanner.nextLine();
				if(line.length() == 0) //라인의 length 가 0 이라는 의미는 문장이 없다는 의미
					break; // 파일 입력을 종료하기 위해 break
				fout.write(line, 0, line.length()); //문장 한 줄 파일에 쓰기
				fout.write("\r\n", 0, 2); //"\r\n" : 파일에 쓸때의 줄바꿈
			}
			fout.close();
		} catch (IOException e) {
			System.out.println("입출력 오류");
		}
		scanner.close();
	}
}
//result
// 실행 결과 test.txt 파일이 폴더에 생성되고, 콘솔 입력 내용이 저장됨.
I love java
헬로우 자바 


// 마지막 헬로우 자바를 입력하고 엔터를 누른뒤 다음 라인으로 가고 나서
// 다시 한번 엔터를 누르먄 맨 마지막 라인의 문자열의 길이가 0이 되어서 
// while 문을 빠져나올 수 있다.


바이트 스트림 클래스

  • 바이트 스트림
    • 바이트 단위의 바이너리 값을 읽고 쓰는 스트림
  • 바이트 스트림 클래스 - java.io 패키지에 포함
    • InputStream/OutputStream
      • 추상 클래스
      • 바이트 입출력 스트림을 다루는 모든 클래스의 수퍼 클래스
    • FileInputStream/FileOutputStream
      • 파일로부터 바이트 단위로 읽거나 저장하는 클래스
      • 바이너리 파일의 입출력
    • DataInputStream/DataOutputStream
      • 자바의 기본 데이터 타입의 값(변수)을 바이너리 값 그대로 입출력
      • 문자열도 바이너리 형태로 입출력

FileOutputStream 을 이용한 파일 쓰기

  • 파일 전체를 읽어 화면에 출력하는 코드 샘플
FileOutputStream fout = new FileOutputStream("c:\\temp\\test.out"); //test.out 파일을 열고, 출력 바이트 스트림인 객체 fout 과 연결

byte b[] = {7, 51, 3, 4, -1, 24};

for(int i=0; i<b.length; i++)
   fout.write(b[i]);  //파일에 배열 b[i] 의 정수 값(바이너리)을 그대로 기록
   
fout.close(); //스트림 닫음.
  • test.out 파일의 내부
    • 파일에 있는 각 바이너리 값들은 문자 정보가 아님, 바이너리 값에 대응하는 그래픽 심볼들

예제 8-5 : FileOutputStream 으로 바이너리 파일 쓰기

  • FileOutputStream 을 이용하여 bytep[] 배열 속에 들어있는 바이너리 값을 test.out 파일에 저장하라.
  • 생성된 test.out 파일은 바이너리 파일이 되므로 메모장으로 그 의미를 확인하기 어렵다.
  • 이 파일은 예제 8-6 에서 FileInputStream 을 이용하여 읽어 다시 출력한다.
import java.io.*;

public class FileOutputStreamEx {
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		byte b[] = {7,51, 3, 4, -1, 24};
		try {
        	// test.out 파일에 있는 각 바이너리 값들은 문자 정보가 아님  
            // 생성된 파일은 바이너리 파일이 됨.
			FileOutputStream fout = new FileOutputStream("/Users/seonik/Documents/temp/test.out");
           
			for(int i=0; i<b.length; i++)
				fout.write(b[i]);
			fout.close();
		} catch (IOException e) {
			System.out.println("temp/test.out 에 저장할 수 없었습니다. 경로명을 확인해주세요.");
			return;
		}
		System.out.println("temp/test.out 에 저장하였습니다.");
	}	

}
//result
temp/test.out 에 저장하였습니다.


다음과 같이 꺠지므로 해당 파일을 읽어올 때 인코딩을 Hex viewer 를 선택하여 출력하면 다음과 같이 16진수 바이너리로 저장된 정수 정보가 보인다.

예제 8-6 : FileInputStream 으로 바이너리 파일 읽기

  • 예제 8-5 에서 저장한 파일을 읽어 byte [] 배열 속에 저장하고 화면에 출력하기
import java.io.*;

public class FileInputStreamEx {
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		byte b[] = new byte[6];  //비어 있는 6 byte 배열
		try {
			FileInputStream fin = new FileInputStream("/Users/seonik/Documents/temp/test.out");
			int n=0, c;
			while((c = fin.read())!= -1) {
				b[n] = (byte)c; //바이트 단위로 test.out 의 정보를 읽은 후 b[n] 에 할당
				n++;
			}
			System.out.println("temp/test.out 에서 읽은 배열을 출력합니다.");
			for(int i=0; i<b.length; i++)
				System.out.print(b[i] + " ");
			System.out.println();
			fin.close();
		} catch (IOException e) {
			System.out.println("temp/test.out 에서 읽기 못했습니다. 경로명을 체크해보세요.");
		}
	}	

}
//result
temp/test.out 에서 읽은 배열을 출력합니다.
7 51 3 4 -1 24 

버퍼 입출력 스트림과 버퍼 입출력의 특징

  • 버퍼 스트림
    • 버퍼를 가진 스트림
    • 입출력 데이터를 일시적으로 저장하는 버퍼를 이용하여 입출력 효율 개선
  • 버퍼 입출력의 목적
    • 입출력시 운영체제의 API 호출 횟수를 줄여 입출력 성능 개선
      • 출력시 여러 번 출력되는 데이터를 버퍼에 모아두고 한 번에 장치로 출력
      • 입력시 입력 데이터를 버퍼에 모아두고 한번에 프로그램에게 전달.

버퍼 스트림의 종류

  • 바이트 버퍼 스트림

    • 바이트 단위의 바이너리 데이터를 처리하는 버퍼 스트림
    • BufferedInputStream와 BufferedOutputStream
  • 문자 버퍼 스트림

    • 유니코드의 문자 데이터만 처리하는 버퍼 스트림
    • BufferedReader와 BufferedWriter
  • 정보를 하나씩 읽는건 오래걸리기 때문에 버퍼에 한번 쓰고 버퍼가 다 차면 시스템에 제공

  • 출력도 버퍼에 write 하고 내보내는 것이 속도가 빠르다.

  • 추가적인 기능

    • 버퍼 스트림은 자동 플러싱 기능이 있다.
    • close() 메소드 호출 시 플러싱이 자동 호출

예제 8-7 : 버퍼 스트림을 이용한 출력

import java.io.*;
import java.util.Scanner;

public class BufferedIOEx {
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		FileReader fin = null;
		int c;
		try {
            //문자 기반의 바이트 정보로 저장되어 있는 문자 파일 ini 의 내용을
            // 문자 기반의 FileReader 참조변수 fin 으로 읽은 후
			fin = new FileReader("/Users/seonik/Documents/temp/test2.txt");
            
            //5바이트 크기의 버퍼 설정하고 
            //BufferOutputStream 클래스 참조변수인 out 으로 관리, 
            //System.out 표준 스트림에 출력
			BufferedOutputStream out = new BufferedOutputStream(System.out, 5); 
            //한 글자씩 읽어들인 문자 c 를 out 이라는 버퍼 출력 스트림에 쓰기
			while((c = fin.read()) != -1) { //파일 데이터를 모두 스크린에 출력
				out.write(c);
			}
            //버퍼라 다 찼으면 화면에다가 내보내줌
            
			// 파일 데이터가 모두 출력된 상태 
            // new Scanner(System.in).nextLine(); 주석 처리하면 1234567AB 출력됨
			new Scanner(System.in).nextLine(); //Enter 키 입력
			out.flush(); //버퍼에 남아 있던 문자 출력
            // out.flush(); 주석 처림 해도 1234567AB 가 출력됨 -> 우리가 찾아보기
			fin.close();
			out.close();
				
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

}
//result
12345

//-> 출력 스트림의 버퍼 크기가 5이므로 파일을 읽어 8개의 문자를 출력하였지만,
//5개의 문자만 출력되고, 4개의 문자는 버퍼에 남아 있어 화면에 보이지 않는다.
//(버퍼 용량인 5개가 꽉 차야 출력되기 때문!)

//<--- 프로그램 라인 15번에서 <Enter>키를 입력받으면 --->
// out.flash() 을 실행하여 버퍼에 남아 있는 4개의 문자를 강제로 출력시킴
12345
67AB

File 클래스

  • File 클래스
    • 파일의 경로명을 다루는 클래스
      • java.io.File
      • 파일과 디렉터리 경로명의 추상적 표현
  • 파일 관리 기능
    • 파일 이름 변경, 삭제, 디렉터리 생성, 크기 등 파일 관리
    • File 객체는 파일 읽고 쓰기 기능 없음
  • file 클래스 생성자와 주요 메소드 : 다 외울 필요는 없고 교수님이 말한것만 기억하기

연동한 파일(폴더, 파일..)에 대한 여러 클래스 사용 예

  • 파일 객체 생성
File f = new File("c:\\windows\\system.ini");
  • 파일 경로명
String filename = f.getName();		//"system.ini"
String path = f.getPath();			//"c:\\windows\\system.ini"
String parent = f.getParent();		//"c:\\windows"  
  • 파일인지 디렉토리인지 구분
File f = new File("c:\\windows\\system.ini");
if(f.isFile())
   System.out.prinln(f.getPath() + "는 파일입니다.");
else if(f.isDirectory())
   System.out.prinln(f.getPath() + "는 디렉토리입니다.");
  • 서브 디렉토리의 리스트 얻기
File f = new File("c:\\Temp");
FIle[] subfiles = f.listFiles();

for(int i=0; i<filenames.length; i++){
   System.out.prin(subfiles[i].getName());
   System.out.prinln("\t파일 크기:" + subfiles[i].length());
}

예제 8-8 : File 클래스 활용한 파일 관리

  • File 클래스를 이용하여 파일의 타입을 알아내고, 디렉토리에 있는 파일들을 나열하며,
  • 디렉토리 이름을 변경하는 프로그램을 작성해보자.
import java.io.File;

public class FileEx {
	public static void listDirectory(File dir) {
		System.out.println("-----" + dir.getPath()+ "의 서브 리스트 입니다. -----");
		
		File [] subFiles = dir.listFiles(); //디렉토리에 포함된 파일과 디렉토리 이름의 리스트 얻기
        // -> 폴더에 대한 모든 정보가 subFiles 라는 참조변수로 subFile[0], subFile[1], subFile[2] ... 얘네들 관리
		for(int i=0; i<subFiles.length; i++) { //subFiles 배열의 각 File 에 대해 루프
			File f = subFiles[i];  
			long t = f.lastModified();  //마지막 수정된 시간
			System.out.print(f.getName());
			System.out.print(f.isFile()?"\t파일":"\t디렉터리");
			System.out.print("\t파일 크기:" + f.length());  //파일 크기
			System.out.printf("\t수정한 시간: %tb,%td,%ta,%tT\n",t,t,t,t);  //월, 일, 요일, 시간
		}
	}

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		File f1 = new File("/Users/seonik/Documents/temp");  //원래는 system.in 임
		System.out.println(f1.getPath() + "," + f1.getParent() + "," + f1.getName());
		
		String res = "";  //널값으로 하나 만들어 놓기
		if(f1.isFile())  //파일이면
			res = "파일";  
		else if(f1.isDirectory()) //디렉토리이면
			res = "디렉토리";
		System.out.println(f1.getPath() + "은" + res + "입니다."); 
		
        //아직 디렉토리가 만들어지지 않았고, 디렉토리를 만들 정보만 만든 것
		File f2 = new File("/Users/seonik/Documents/temp/java_sample"); //새로 만들고자 하는 디렉토리
		if(f2.exists()) {  //f2 디렉토리가 존재하는지 검사
			f2.mkdir();  //f2 가 존재하지 않으면, 디렉토리는 여기서 만들어짐
		}
			
		listDirectory(new File("/Users/seonik/Documents/temp")); //리스트 출력
		f2.renameTo(new File("/Users/seonik/Documents/temp/javasample")); //java_sample -> javasample 로 이름 변경
		listDirectory(new File("/Users/seonik/Documents/temp")); //javasample 로 변경한 후 리스트 출력
	}

}



예제 8-9 : 텍스트 파일 복사

  • 문자 스트림 FileReader 와 FileWriter 를 이용하여 파일 복사
import java.io.*;

public class TextCopyEx {
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		File src = new File("/Users/seonik/Downloads/studymemo.txt"); //원본 파일 경로명 
		File dest = new File("/Users/seonik/Documents/temp/system.txt"); //복사 파일 경로명
		
		int c;
		try {
			FileReader fr = new FileReader(src); // 파일 입력 문자 스트림 생성
			FileWriter fw = new FileWriter(dest); //파일 출력 문자 스트림 생성
			while((c = fr.read()) != -1) { //문자 하나 읽고
				fw.write((byte)c);
			}
			fr.close();
			fw.close();
			System.out.println(src.getPath() + "를" + dest.getPath() + "로 복사하였습니다.");
		} catch (IOException e) {
			e.printStackTrace();
			System.out.println("파일 복사 오류");
		}
	}

}
//result
/Users/seonik/Downloads/studymemo.txt를
/Users/seonik/Documents/temp/system.txt로 복사하였습니다.

예제 8-10 : 바이너리 파일 복사

  • 바이트 스트림을 이용하여 바이너리 파일을 복사하는 프로그램 작성.
import java.io.*;

public class BinaryCopyEx {
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		File src = new File("/Users/seonik/Downloads/img1.jpg"); //원본 파일 경로명 
		File dest = new File("/Users/seonik/Documents/temp/copyimg.jpg"); //복사 파일 경로명
		
		int c;
		try {
			FileInputStream fi = new FileInputStream(src); // 파일 입력 바이트 스트림 생성
			FileOutputStream fo = new FileOutputStream(dest); //파일 출력 바이트 스트림 생성
			while((c = fi.read()) != -1) { 
				fo.write((byte)c);
			}
			fi.close();
			fo.close();
			System.out.println(src.getPath() + "를" + dest.getPath() + "로 복사하였습니다.");
		} catch (IOException e) {
			e.printStackTrace();
			System.out.println("파일 복사 오류");
		}
	}
}
//result
/Users/seonik/Downloads/img1.jpg를
/Users/seonik/Documents/temp/copyimg.jpg로 복사하였습니다.

profile
공부한 것 기록용

0개의 댓글