18_데이터 입출력

Tasker_Jang·2024년 4월 1일
0

18_데이터 입출력

1. 데이터 입출력이란?

주로 데이터는 사용자가 입력하는 형태로 키보드를 통해 입력되거나, 파일에서 읽혀질 수 있습니다. 이 데이터는 프로그램에 의해 처리되어 결과가 모니터로 출력되거나, 파일에 저장되어 나중에 사용될 수 있습니다. 또한 다른 프로그램으로 전송되어 다른 용도로 활용될 수도 있습니다.

이러한 데이터 입출력 과정은 컴퓨터 시스템에서 매우 중요하며, 다양한 응용 프로그램에서 사용됩니다. 데이터의 효율적인 입출력은 시스템의 성능과 사용자 경험에 매우 중요한 영향을 미칩니다.

2. 스트림이란?

스트림은 데이터가 단방향으로 흐르는 것을 의미합니다. 일반적으로 스트림은 입력 스트림과 출력 스트림으로 나뉘며, 입력 스트림은 데이터가 시스템으로 들어오는 방향으로 흐르고, 출력 스트림은 시스템에서 나가는 방향으로 데이터가 흐릅니다.

3. 두가지 유형의 스트림

바이트 스트림(Byte Streams): 바이트 스트림은 데이터를 바이트 단위로 처리하는 스트림입니다. 주로 이미지, 오디오, 비디오 등 바이너리 데이터를 다룰 때 사용됩니다. 파일, 네트워크 연결 등과 같은 입력 소스에서 바이트를 읽거나 출력하는 데 사용됩니다. 예를 들어, InputStream 및 OutputStream 클래스와 그 하위 클래스들이 바이트 스트림을 다루는 데 사용됩니다.

문자 스트림(Character Streams): 문자 스트림은 문자 단위로 데이터를 처리하는 스트림입니다. 텍스트 파일이나 문자열과 같은 문자 데이터를 다룰 때 사용됩니다. 문자 스트림은 유니코드 문자를 처리할 수 있으며, 바이트 스트림과 달리 문자 인코딩에 대해 더 많은 관리를 제공합니다. 주로 Reader 및 Writer 클래스와 그 하위 클래스들이 문자 스트림을 다루는 데 사용됩니다.

바이트 스트림과 문자 스트림은 서로 다른 유형의 데이터를 처리하는 데 사용되며, 적절한 스트림을 선택하여 입출력 작업을 수행해야 합니다. 예를 들어, 텍스트 파일을 읽거나 쓸 때는 문자 스트림을 사용하여 텍스트 인코딩을 올바르게 처리할 수 있습니다.

4. 바이트 입출력 스트림의 최상위 클래스

바이트 입출력 스트림의 최상위 클래스는 InputStream과 OutputStream입니다. 이들 클래스는 Java I/O API의 기본 구성 요소이며, 모든 바이트 기반 입출력 클래스의 기반 클래스입니다.

InputStream: InputStream 클래스는 바이트 단위로 데이터를 읽는 추상 클래스입니다. 이 클래스는 바이트 배열이나 단일 바이트를 읽을 수 있는 다양한 메서드를 제공합니다. 이 클래스를 상속받은 하위 클래스들은 파일, 네트워크 연결, 메모리 버퍼 등 다양한 소스로부터 데이터를 읽을 수 있습니다.

OutputStream: OutputStream 클래스는 바이트 단위로 데이터를 쓰는 추상 클래스입니다. 이 클래스는 바이트 배열이나 단일 바이트를 쓸 수 있는 다양한 메서드를 제공합니다. 이 클래스를 상속받은 하위 클래스들은 파일, 네트워크 연결, 메모리 버퍼 등 다양한 대상으로 데이터를 쓸 수 있습니다.

이 두 클래스는 Java의 입출력 시스템에서 기본적인 역할을 수행하며, 모든 바이트 기반 입출력 클래스는 이들 클래스를 상속하고 확장하여 사용됩니다.

5. 문자 입출력 스트림의 최상위 클래스

문자 입출력 스트림의 최상위 클래스는 Reader와 Writer입니다. 이 두 클래스는 Java의 입출력 API에서 문자 데이터를 처리하는 데 사용됩니다.

Reader: Reader 클래스는 문자 단위로 데이터를 읽는 추상 클래스입니다. 이 클래스는 문자 배열이나 단일 문자를 읽을 수 있는 다양한 메서드를 제공합니다. 파일, 네트워크 연결, 문자열 등 다양한 소스로부터 문자 데이터를 읽을 수 있습니다.

Writer: Writer 클래스는 문자 단위로 데이터를 쓰는 추상 클래스입니다. 이 클래스는 문자 배열이나 단일 문자를 쓸 수 있는 다양한 메서드를 제공합니다. 파일, 네트워크 연결, 문자열 등 다양한 대상으로 문자 데이터를 쓸 수 있습니다.

이 두 클래스는 Java의 입출력 시스템에서 문자 데이터를 처리하는 데 사용되며, 다양한 하위 클래스를 통해 특정한 입력 및 출력 요구사항을 충족시킬 수 있습니다.

6. 바이트 입출력 예시


import java.io.*;

public class ByteStreamExample {
    public static void main(String[] args) {
        // 파일 경로
        String filePath = "example.txt";

        try {
            // 파일에 데이터 쓰기
            OutputStream outputStream = new FileOutputStream(filePath);
            String data = "Hello, World!";
            byte[] byteArray = data.getBytes();
            outputStream.write(byteArray);
            outputStream.close();

            // 파일에서 데이터 읽기
            InputStream inputStream = new FileInputStream(filePath);
            int byteData;
            while ((byteData = inputStream.read()) != -1) {
                // 읽은 바이트 데이터 출력
                System.out.print((char) byteData);
            }
            inputStream.close();
        } catch (IOException e) {
            System.out.println("파일 처리 중 오류 발생: " + e.getMessage());
        }
    }
}

7. 문자 입출력 예시


import java.io.*;

public class CharacterStreamExample {
    public static void main(String[] args) {
        // 파일 경로
        String filePath = "example.txt";

        try {
            // 파일에 문자열 쓰기
            Writer writer = new FileWriter(filePath);
            String data = "Hello, World!";
            writer.write(data);
            writer.close();

            // 파일에서 문자열 읽기
            Reader reader = new FileReader(filePath);
            int charData;
            while ((charData = reader.read()) != -1) {
                // 읽은 문자 데이터 출력
                System.out.print((char) charData);
            }
            reader.close();
        } catch (IOException e) {
            System.out.println("파일 처리 중 오류 발생: " + e.getMessage());
        }
    }
}

8. 보조 스트림

보조 스트림(Decorator Stream)은 다른 스트림에 연결되어 여러 가지 편리한 기능을 제공해주는 스트림입니다. 보조 스트림은 일반적으로 기본 스트림에 대한 기능을 향상시키거나 추가 기능을 제공하기 위해 사용됩니다.

여러 개의 보조 스트림을 연결하여 스트림 체인(Stream Chain)을 구성할 수 있습니다. 이렇게 연결된 보조 스트림은 데이터를 전달하면서 각 보조 스트림에서 제공하는 기능을 차례대로 적용합니다. 이는 기능의 조합과 재사용성을 증가시켜 주며, 코드를 간결하게 유지할 수 있도록 합니다.

예를 들어, BufferedReader와 BufferedWriter는 각각 입력 스트림과 출력 스트림에 버퍼링 기능을 제공해주는 보조 스트림입니다. 이를 이용하면 입출력 속도를 향상시키고, 읽고 쓰는 과정에서 발생할 수 있는 부하를 줄일 수 있습니다.

스트림 체인을 구성할 때에는 각 보조 스트림이 기본 스트림에 연결될 수 있도록 차례대로 연결해주어야 합니다. 이를 통해 데이터가 스트림 체인을 따라 흐를 수 있게 됩니다.

9. 문자 스트림 변환

바이트 스트림과 문자 스트림은 각각 바이트와 문자를 처리하기 위한 스트림 유형입니다. 문자를 바이트 단위로 다룰 때는 문자의 인코딩과 관련된 문제가 발생할 수 있습니다. 예를 들어, 문자를 UTF-8로 인코딩할 때는 한 문자당 1바이트에서 4바이트까지 다양한 바이트 수가 필요합니다.

바이트 스트림으로 문자를 처리할 때는 이러한 인코딩 및 디코딩 과정을 명시적으로 처리해주어야 하며, 이는 복잡성과 오류 가능성을 증가시킬 수 있습니다. 따라서 문자 데이터를 다룰 때는 문자 스트림을 사용하는 것이 좋습니다. 문자 스트림은 문자를 직접 다루므로 인코딩 및 디코딩 과정을 신경쓸 필요가 없습니다.

예를 들어, FileReader 및 FileWriter 클래스는 파일에서 문자를 읽고 쓰는 데 사용되는 문자 스트림의 예입니다. 이들 클래스를 사용하면 파일에서 문자 데이터를 쉽게 읽고 쓸 수 있으며, 문자 인코딩에 대한 걱정 없이 편리하게 작업할 수 있습니다. 따라서 바이트 스트림에서 입출력할 데이터가 문자인 경우, 문자 스트림으로 변환해서 사용하는 것이 좋습니다.

10. 메모리 버퍼


import java.io.*;

public class BufferedInputStreamExample {
    public static void main(String[] args) {
        String filePath = "example.txt";

        try {
            InputStream inputStream = new FileInputStream(filePath);
            BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);

            int byteData;
            while ((byteData = bufferedInputStream.read()) != -1) {
                System.out.print((char) byteData);
            }

            bufferedInputStream.close();
        } catch (IOException e) {
            System.out.println("파일 처리 중 오류 발생: " + e.getMessage());
        }
    }
}

11. 데이터를 읽고 쓸 때의 순서

import java.io.*;

public class DataStreamExample {
    public static void main(String[] args) {
        String filePath = "data.dat";

        // 데이터 출력
        try (DataOutputStream dos = new DataOutputStream(new FileOutputStream(filePath))) {
            dos.writeInt(42);
            dos.writeDouble(3.14);
            dos.writeBoolean(true);
        } catch (IOException e) {
            System.out.println("파일 처리 중 오류 발생: " + e.getMessage());
        }

        // 데이터 읽기
        try (DataInputStream dis = new DataInputStream(new FileInputStream(filePath))) {
            int intValue = dis.readInt();
            double doubleValue = dis.readDouble();
            boolean booleanValue = dis.readBoolean();

            System.out.println("int 값: " + intValue);
            System.out.println("double 값: " + doubleValue);
            System.out.println("boolean 값: " + booleanValue);
        } catch (IOException e) {
            System.out.println("파일 처리 중 오류 발생: " + e.getMessage());
        }
    }
}

12. print 보조 스트림

PrintStream과 PrintWriter은 프린터와 유사한 방식으로 데이터를 출력하는 기능을 제공하는 스트림입니다. 이들은 각각 바이트 스트림과 문자 스트림에 대한 보조 스트림으로 제공되며, 텍스트 데이터를 쉽게 출력하는 데 사용됩니다.

PrintStream은 바이트 기반의 출력 스트림에 대한 기능을 제공하며, PrintWriter는 문자 기반의 출력 스트림에 대한 기능을 제공합니다.

PrintStream 및 PrintWriter 클래스는 다양한 데이터 유형을 출력할 수 있는 메소드들을 제공하며, 자동으로 문자 인코딩을 처리하고 줄 바꿈 및 서식 지정 기능을 제공하여 프로그래머가 간편하게 출력을 다룰 수 있도록 돕습니다.

따라서 프로그램에서 텍스트를 출력할 때, 특히 표준 출력(콘솔 출력)이나 파일 출력 등을 다룰 때에는 PrintStream 또는 PrintWriter를 활용하여 출력하는 것이 유용합니다.

13. 직렬화와 역직렬화

자바에서는 객체를 파일이나 네트워크로 출력하기 위해 직렬화(Serialization)라는 개념을 사용합니다. 직렬화는 객체를 일렬로 늘어선 바이트(byte)로 변환하는 과정을 의미합니다. 이렇게 변환된 바이트 데이터는 파일에 쓰거나 네트워크를 통해 전송될 수 있습니다. 반대로, 직렬화된 바이트 데이터를 객체의 필드 값으로 다시 변환하는 과정을 역직렬화(Deserialization)라고 합니다.


import java.io.*;

public class SerializationExample {
    public static void main(String[] args) {
        // 객체를 직렬화하여 파일에 저장
        try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("person.ser"))) {
            Person person = new Person("Alice", 30);
            oos.writeObject(person);
            System.out.println("객체를 직렬화하여 파일에 저장했습니다.");
        } catch (IOException e) {
            e.printStackTrace();
        }

        // 파일에서 객체를 역직렬화하여 복원
        try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("person.ser"))) {
            Person person = (Person) ois.readObject();
            System.out.println("파일에서 객체를 역직렬화하여 복원했습니다.");
            System.out.println("복원된 객체: " + person);
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

14. Serializable 인터페이스

Serializable 인터페이스는 아무런 메서드나 필드를 가지지 않는 빈 인터페이스입니다. 이 인터페이스는 자바 직렬화 메커니즘에게 해당 클래스의 객체가 직렬화될 수 있다는 것을 알리는 역할을 합니다.

Serializable 인터페이스를 구현한 클래스의 객체는 자바 직렬화 시스템에 의해 바이트 스트림으로 변환되어 저장되거나 네트워크를 통해 전송될 수 있습니다. 이 인터페이스를 구현하지 않은 클래스의 객체는 직렬화될 수 없으며, 직렬화 시도 시 java.io.NotSerializableException이 발생합니다.

Serializable 인터페이스는 마커(marker) 인터페이스(marker interface)로 분류되며, 아무런 메서드나 필드를 가지지 않으며 단순히 해당 클래스의 특정 속성을 나타냅니다. 자바 직렬화 메커니즘은 해당 인터페이스를 구현한 클래스의 객체를 직렬화할 수 있다는 것을 확인하기 위해 이 인터페이스의 존재 여부를 검사합니다.

15. serialVersionUID

직렬화된 객체를 역직렬화할 때, 직렬화할 때 사용된 클래스와 역직렬화할 때 사용되는 클래스는 기본적으로 동일해야 합니다. 그러나 클래스 내용이 서로 다르더라도 직렬화된 필드를 공통으로 포함하고 있다면, 역직렬화할 수 있는 방법이 있습니다. 그 방법은 직렬화할 때와 같은 serialVersionUID 값을 사용하는 것입니다.


import java.io.*;

public class SerializationExample {
    public static void main(String[] args) {
        // 직렬화할 객체 생성
        Person person = new Person("Alice", 30);

        // 직렬화하여 파일에 저장
        try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("person.ser"))) {
            oos.writeObject(person);
            System.out.println("객체를 직렬화하여 파일에 저장했습니다.");
        } catch (IOException e) {
            e.printStackTrace();
        }

        // 파일에서 객체를 역직렬화하여 복원
        try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("person.ser"))) {
            Person restoredPerson = (Person) ois.readObject();
            System.out.println("파일에서 객체를 역직렬화하여 복원했습니다.");
            System.out.println("복원된 객체: " + restoredPerson);
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

// 직렬화 가능한 클래스
class Person implements Serializable {
    private static final long serialVersionUID = 123456789L; // 직렬화 버전 UID
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{name='" + name + "', age=" + age + "}";
    }
}

16. Files 클래스

java.nio.file.Files 클래스는 이전의 java.io.File 클래스를 개선한 클래스로, 좀 더 많은 기능을 제공합니다. Files 클래스는 파일 및 디렉터리를 다루는 데 사용되며, 다음과 같은 기능을 포함하고 있습니다:

  1. 파일 및 디렉터리 생성: createFile(), createDirectory(), createDirectories() 메서드를 사용하여 파일이나 디렉터리를 생성할 수 있습니다.

  2. 파일 복사, 이동, 삭제: copy(), move(), delete() 메서드를 사용하여 파일을 복사, 이동, 삭제할 수 있습니다.

  3. 파일 읽기 및 쓰기: readAllBytes(), write() 메서드를 사용하여 파일을 읽고 쓸 수 있습니다.

  4. 파일 속성 읽기 및 설정: isDirectory(), isRegularFile(), isReadable(), isWritable() 등의 메서드를 사용하여 파일 속성을 읽고 설정할 수 있습니다.

  5. 파일 크기, 생성 시간, 수정 시간 등의 메타데이터 조회: size(), getLastModifiedTime(), getOwner() 등의 메서드를 사용하여 파일의 메타데이터를 조회할 수 있습니다.

  6. 파일의 존재 여부 확인: exists() 메서드를 사용하여 파일이나 디렉터리의 존재 여부를 확인할 수 있습니다.

  7. 파일 간의 차이 비교: mismatch() 메서드를 사용하여 두 파일 간의 차이를 비교할 수 있습니다.

  8. 파일의 접근 권한 관리: setPosixFilePermissions(), getPosixFilePermissions() 메서드를 사용하여 POSIX 권한을 관리할 수 있습니다.

Files 클래스는 File 클래스보다 유연하고 효율적이며, 네이티브 파일 시스템과 상호작용하는 데 더 적합합니다. 또한 NIO(Non-blocking I/O) 패키지의 일부이므로 비동기 파일 및 디렉터리 작업도 지원합니다. 이러한 이유로 Files 클래스를 사용하여 파일 및 디렉터리 작업을 수행하는 것이 권장됩니다.

profile
터널을 지나고 있을 뿐, 길은 여전히 열려 있다.

0개의 댓글