C18

0

이것이 자바다

목록 보기
16/18
post-thumbnail

입출력 스트림

데이터는 키보드를 통해 입력될 수도 있고, 파일 또는 프로그램으로부터 입력될 수도 있다. 반대로 데이터는 모니터로 출력될 수도 있고, 파일에 저장되거나 다른 프로그램으로 전송될 수 있다. 이것을 총칭해서 데이터 입출력이라고 한다.

자바는 입력 스트림과 출력 스트림을 통해 데이터를 입출력 한다. 스트림은 단방향으로 데이터가 흐르는 것을 말하는데, 다음 그림과 같이 데이터는 출발지에서 나와 도착지로 흘러 들어간다.

어떤 데이터를 입출력하느냐에 따라서 두가지로 나뉜다.

바이트 스트림 : 그림, 문자, 멀티미디어 등 모든 종류의 데이터를 입출력할 때 사용
문자 스트림 : 문자만 입출력할 때 사용

자바는 데이터 입출력과 관련된 라이브러리를 java.io 패키지에서 제공하고 있다. java.io 패키지는 바이트 스트림과 문자 스트림을 아래와 같이 이름으로 구분해서 제공한다.

바이트 입출력 스트림의 최상위 클래스는 InputStream과 OutputStream이다. 이 클래스를 상속받는 자식 클래스에는 접미사로 InputStream , OutputStream이 붙는다. 예를 들어 이미지와 같은 바이너리 파일의 입출력 스트림 클래스는 FileInputStream, FileOutputStream이다.

바이트 출력 스트림

OutputStream은 바이트 출력 스트림의 최상위 클래스로 추상 클래스이다. 모든 바이트 출력 스트림 클래스는 이 OutputStream 클래스를 상속받아서 만들어진다.

아래는 OutputStream 클래스의 주요 메소드

1 바이트 출력

write (int b) 메소드는 매개값 int(4byte)에서 끝 1byte만 출력한다. 매개변수가 int 타입이므로 4byte 모두를 내보내는 것은 아니다.

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;

public class WriteExample {
    public static void main(String[] args) {
        try {
            OutputStream os = new FileOutputStream("C:/Temp/test1.db");

            byte a = 10;
            byte b = 20;
            byte c = 30;

            os.write(a);
            os.write(b);
            os.write(c);

            os.flush();
            os.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

바이트 배열 출력

1바이트만 출력하는 경우는 드물고 대게 바이트 배열을 통째로 출력하는 경우가 가장 많다. write(byte[ ] b ) 메소드는 매개값으로 주어진 배열의 모든 바이트를 출력한다.

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;

public class WriteExample {
    public static void main(String[] args) {
        try {
            OutputStream os = new FileOutputStream("C:/Temp/test2.db");

            byte[] array = {10, 20, 30};

            os.write(array); // 일부 출력시 os.write(array, 1, 3);
            os.flush();
            os.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

바이트 입력 스트림

InputStream은 바이트 입력 스트림의 최상위 클래스로, 추상 클래스이다. 모든 바이트 입력 스트림은 InputStream 클래스를 상속받아 만들어진다.

1 바이트 읽기

read() 메소드는 입력 스트림으로부터 1byte를 읽고 int(4byte) 타입으로 리턴한다. 따라서 리턴된 4byte 중 끝 1byte에만 데이터가 들어 있다.

더 이상 입력 스트림으로부터 바이트를 읽을 수 없다면 read()는 -1을 리턴.

	InputStream is = ...;
    while(true) {
    	int data == is.read(); //1 바이트를 읽고 리턴
    	if (data == -1) break; //-1을 리턴했을 경우 while문 종료
	}
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;

public class ReadExample {
    public static void main(String[] args) {
        try {
            InputStream is = new FileInputStream("C:/Temp/test1.db");

            while (true) {
                int data = is.read();
                if (data == -1) break;
                System.out.println(data);
            }

            is.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

바이트 배열로 읽기

read(byte[ ] b) 메소드는 입력 스트림으로부터 주어진 배열의 길이만큼 바이트를 읽고 배열에 저장한 다음 읽은 바이트 수를 리턴한다. 많은 양의 바이트를 읽을 때 read(byte[ ] b) 메소드를 사용하는 것이 좋으며, 입력 스트림으로부터 100개의 바이트가 들어온다면 read(byte[] b)메소드는 한 번 읽을 때 배열 길이만큼 읽기 때문에 읽는 횟수가 현저히 줄어든다.

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

public class ReadExample {
    public static void main(String[] args) {
        try {
            InputStream is = new FileInputStream("C:/Temp/test2.db");

            byte[] data = new byte[100];

            while (true) {
                int num = is.read(data);
                if (num == -1) break;
                ;

                for (int i = 0; i < num; i++) {
                    System.out.println(data[i]);
                }
            }
            is.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

파일 복사 예제. FileInputStream에서 읽은 바이트를 바로 FileOutputStream으로 출력하는 것이다. JPEG 그림 파일을 하나 준비해 파일 이름을 test.jpg로 변환 후 C:/Temp 폴더에 저장한다.

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;

public class CopyExample {
    public static void main(String[] args) throws Exception {
        String originalFileName = "C:/Temp/test.jpg";
        String targetFileName = "C:/Temp/test2.jpg";

        //입출력 스트림 생성
        InputStream is = new FileInputStream(originalFileName);
        OutputStream os = new FileOutputStream(targetFileName);

        byte[] data = new byte[1024]; //읽은 바이트를 저장할 때 배열 생성
        while (true) {
            int num = is.read(data); //최대 1024 바이트를 읽고 배열에 저장, 읽은 바이트는 리턴
            if (num == -1) break;
            os.write(data, 0, num);
        } os.flush();
        os.close();
        is.close();

        System.out.println("복사 완료");

    }
}

Java9 이후 transferTo() 메소드 사용가능.

byte[] data = new byte[1024]; 
        while (true) {
            int num = is.read(data); 
            if (num == -1) break;
            os.write(data, 0, num);
        } //7줄이
        
is.transferTo(os);//한줄로 대체 가능   

문자 입출력 스트림

바이트 입출력 스트림인 InputStream , OutputStream에 대응하는 문자 입출력 스트림으로 Reader, Writer가 있다. 입출력되는 단위가 문자인 것을 제외하고는 바이트 입출력 스트림과 사용 방법은 동일.

문자 출력

Writer는 문자 출력 스트림의 최상위 클래스로, 추상 클래스이다. 따라서 모든 문자 출력 스트림 클래스는 상속 받아 만들어진다.

Writer는 OutputStream과 사용 방법은 동일, 출력 단위가 다름(char). 문자열을 출력하는 write(String str) 메소드를 추가로 제공한다.

문자 읽기

Reader는 문자 입력 스트림의 최상위 클래스로 추상클래스이다. 모든 문자 입력 스트림 클래스는 Reader클래스를 상속받는다.

보조 스트림

보조 스트림이란 다른 스트림과 연결되어 여러 가지 편리한 기능을 제공해주는 스트림을 말한다. 보조 스트림은 자체적으로 입출력을 수행할 수 없기 때문에 입출력 소스로부터 직접 생성된 입출력 스트림에 연결해서 사용해야 한다.

입출력 스트림에 보조 스트림을 연결하려면 보조 스트림을 생성할 때 생성자 매개값으로 입출력 스트림을 제공하면 된다.

	InputStream is = new FileInputStream(" ... ");
    InputStreamReader reader = new InputStreamReader( is );
    // 보조 스트림을 연결하는 코드
    //보조 스트림은 또 다른 보조 스트림과 연결하여 스트림 체인으로 구성가능
    BufferedReader br = new BufferedReader( reader );

아래는 자주 사용되는 보조 스트림

보조스트림 사진

문자 변환 스트림

바이트 스트림에서 입출력할 때 데이터가 문자라면 문자 스트림으로 변환해서 사용하는 것이 좋다. 그 이유는 문자로 바로 입출력하는 편리함이 있고, 문자셋의 종류를 지정할 수 있기 때문이다.

InputStream을 Reader로 변환

InputStreamReader 보조 스트림을 연결

	InputStream is = new FileInputStream("C:/Temp/test.txt");
    Reader reader = new InputStreamReader(is);

OutputStream을 Writer로 변환

똑같이 OutputStreamWriter 보조 스트림을 연결

	OutputStream os = new FileOutputStream("C:/Temp/test.txt");
    Writer writer = new OutputStreamWriter(os);

성능 향상 스트림

CPU와 메모리가 아무리 뛰어나도 하드 디스크의 입출력이 늦어지면 프로그램의 실행 성능은 하드 디스크의 처리 속도에 맞춰진다. 네트워크로 데이터를 전송할 때도 느린 네트워크 환경이라면 컴퓨터 사양이 좋아도 속도가 느려질 수 밖에 없다.

프로그램 입출력 소스와 직접 작업하지 않고 중간에 메모리 버퍼와 작업함으로써 실행 성능을 향상시킬 수 있다.

출력 스트림의 경우 직접 하드 디스크에 데이터를 보내지 않고 메모리 버퍼에 데이터를 보냄으로써 출력 속도를 향상시킬 수 있다. 버퍼는 데이터가 쌓이기를 기다렸다가 꽉 차게 되면 데이터를 한꺼번에 하드 디스크로 보냄으로써 출력 횟수를 줄여준다.

입력 스트림에서도 버퍼를 사용하면 읽기 성능이 좋아진다. 하드 디크스크로부터 직접 읽는 것 보다는 메모리 버퍼로부터 읽는 것이 빠르다.

아래는 메모리 버퍼를 제공하여 실행 성능 향상 시키는 보조 스트림이다.

	BufferedInputStream bis = new BufferedInputStream(바이트 입력 스트림);
    BufferedOutputStream bos = new BufferedOutputStream(바이트 출력 스트림);
    
    BufferedReader br = new BufferedReader(문자 입력 스트림);
    BufferedWriter bw = new BufferedWriter(문자 출력 스트림);

문자 입력 스트림 Reader에 BufferedReader 를 연결하면 성능 향상뿐 아니라 행 단위로 문자열을 읽는 매우 편리한 readLine() 메소드를 제공한다.

	BufferedReader br = new BufferedReader(new FileReader( "..." ));
    while(true) {
    	String str = br.readLine(); // 파일에서 한 행씩 읽음
        if(str == null) break; // 더 이상 읽을 행이 없을 경우 while문 종료
    }

기본 타입 스트림

바이트 스트림에 DataInputStream과 DataOutputStream 보조 스트림을 연결하면 기본 타입인 boolean, char, short, int, long, float, double 값을 입출력할 수 있다.

	DataInputStream dis = new DataInputStream(바이트 입력 스트림);
    DataOutputStream dos = new DataOutputStream(바이트 출력 스트림);

아래는 DataInputStream DataOutputStream이 제공하는 메소드
메소드 사용시 주의사항이 있는데, 데이터 타입의 크긱가 모두 다르므로 DataOutputStream으로 출력한 데이터를 다시 DataInputStream으로 읽어 올 때에는 출력한 순서와 동일한 순서로 읽어야 한다는 것이다. 예를 들어출력할 때의 순서가 int -> Boolean -> double이라면 읽을 때도 그대로여야 한다.

프린트 스트림

PrintStream과 printWriter는 프린터와 유사하게 출력하는 print(), println(), printf() 메소드를 가지고 있는 보조 스트림이다. 지금까지 콘솔에 출력하기 위해 System.out.println()을 사용하였는데 out이 PrintStream 타입이기 때문이다.

//연결 구조
	PrintStream ps = new PrintStream(바이트 출력 스트림);
    PrintWriter pw = new PrintWriter(문자 출력 스트림);

객체 스트림

자바는 메모리에 생성된 객체를 파일 또는 네트워크로 출력할 수 있다. 객체를 출력하려면 필드값을 일렬로 늘어선 바이트로 변경해야 하는데, 이것을 직렬화라고 한다. 반대로 직렬화된 바이트를 객체의 필드값으로 복원하는 것을 역직렬화라고 한다.

ObjectInputStream과 ObjectOutputStream은 객체를 입출력할 수 있는 보조 스트림이다. ObjectOutputStream은 바이트 출력 스트림과 연결되어 객체를 직렬화하고, ObjectInputStream은 바이트 입력 스트림과 연결되어 객체로 복원하는 역직렬화를 한다.

// ObjectInputStream과 ObjectOutputStream 보조 스트림을 연결하는 코드이다.
	ObjectInputStream ois = new ObjectInputStream(바이트 입력 스트림);
    ObjectOutputStream oos = new ObjectOutputStream(바이트 출력 스트림);
    
// ObjectOutputStream으로 객체를 직렬화하기 위해서는 writeObject() 메소드 사용
	oos.writeObject(객체);

//반대로 ObjectInputStream의 readObject() 메소드는 읽은 바이트를 역직렬화해서 객체로 생성한다.
//readObject() 메소드의 리턴 타입은 Object이므로 구체적 타입으로 강제 타입 변환해야 한다.
	객체타입 변수 = (객체타입) ois.readObject();

Serializable 인터페이스

자바는 Serializable 인터페이스를 구현한 클래스만 직렬화 할 수 있도록 제한한다. Serializable 인터페이스는 멤버가 없는 빈 인터페이스지만, 객체를 직렬화 할 수 있다고 표시하는 역할을 한다.

객체가 직렬화할 때 인스턴스 필드값은 직렬화 대상이지만 정적 필드값과 transient로 선언된 필드값은 직렬화에서 제외되므로 출력되지 않는다.

serialVersionUID 필드

직렬화할 때 사용된 클래스와 역직렬화할 때 사용된 클래스는 기본적으로 동일한 클래스여야 한다. 만약 클래스의 이름이 같더라도 클래스의 내용이 다르면 역직렬화에 실패한다.

public class Member implements Serializable {
	int field1;
    int field2;
    }

public class Member implements Serializable
	int field1;
    int field2;
    int field3;
    }

이런 식으로 클래스 내용이 다르더라도 직렬화된 필드를 공통으로 포함하고 있다면 역직렬화할 수 있는 방법이 있다. 두 클래스가 동일한 serialVersionUID 상수값을 가지고 있으면 된다.

public class Member implements Serializable {
	static final long serialVersionUID = 1;
	int field1;
    int field2;
    }

public class Member implements Serializable
	static final long serialVersionUID = 1;
    int field1;
    int field2;
    int field3;
    }

File과 Files 클래스

java.io 패키지와 java.nio.file 패키지는 파일과 디텍토리 정보를 가지고 있는 File과 Files 클래스를 제공한다. Files는 File을 개선한 클래스로 기능이 보다 다양하다.

File 클래스

	File file = new File("경로");
    //File 클래스로부터 File 객체를 생성하는 방법

경로 구분자는 운영체제마다 조금씩 다른데, 윈도우는 \ , / 맥, 리눅스는 /를 사용

경로에 실제 파일이나 디텍토리가 없더라도 예외가 발생하지 않으므로 실제 있는지 확인할 경우에는 exists() 메소드를 호출해보면 된다.

	boolean isExist = file.exists();

exists() false 경우

exists() true 경우

Files 클래스

Files 클래스는 정적 메소드로 구성되어 있기 때문에 Flie 클래스처럼 객체로 만들 필요가없으며 Files의 정적 메소드는 운영체제의 파일 시스템에게 파일 작업을 수행하도록 위임한다. 아래는 Files클래스가 가지고 있는 정적 메소드이 메소드들은 매개값으로 Path 객체를 받는다. Path 객체는 파일이나 디렉토리 찾기 위한 경로 정보를 가지고 있는데 , 정적 메소드인 get() 메소드도 얻을 수 있다.

	Path path = Paths.get(String first, String ... more )

get() 메소드의 매개값은 파일 경로인데 전체 경로를 한번에 지정, 상위 디렉토리와 하위 디렉토리를 나열 지정해도 상관없다.

확인문제

1. 1번

2. 1번

3. 3번

4. 1번

5. 3번

6. 3번

7.

new FileReader(filePath); 
new BufferedReader(fr);

rowData = br.readLine(); 
if(rowData == null) {
	break; 
}
System.out.println(++rowNumber + ": " + rowData);

8. 4번

9. 2번

10.

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.Scanner;

public class Example {
    public static void main(String[] args) {
        try {
            Scanner scanner = new Scanner(System.in);
//경로 입력 받기
            System.out.print("원본 파일 경로: ");
            String originalFilePath = scanner.nextLine();
            System.out.print("복사 파일 경로: ");
            String targetFilePath = scanner.nextLine();
//원본 파일 존재 여부 확인
            File originalFile = new File(originalFilePath);
            if (!originalFile.exists()) {
                System.out.println("원본 파일이 존재하지 않습니다.");
                System.exit(0);
            }
//복사 파일 경로상에 없는 모든 디렉토리 생성
            File targetFile = new File(targetFilePath);
            File parentFile = targetFile.getParentFile();
            if (!parentFile.exists()) {
                parentFile.mkdirs();
            }
//입출력 스트림 얻기
            BufferedInputStream bis = new BufferedInputStream(
                    new FileInputStream(originalFilePath));
            BufferedOutputStream bos = new BufferedOutputStream(
                    new FileOutputStream(targetFilePath));
//파일 데이터를 읽고 출력하기 byte[] data = new byte[1024]; int num = -1;
            while (true) {
                num = bis.read(data);
                if (num ==-1)break;
                bos.write(data, 0, num);
            }
            System.out.println("복사가 성공되었습니다.");
//입출력 스트림 닫기 bis.close(); bos.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

아니 나만 답지 없으면 못푸나?

0개의 댓글

관련 채용 정보