I/O 활용

황상익·2024년 11월 14일

Inflearn JAVA

목록 보기
59/61

메모리

package chap54.io.member;

import java.io.Serializable;

//Serializable 직렬화 마커 (있어야 직렬화 가능)
public class Member implements Serializable {
    private String id;
    private String name;
    private Integer age;

    public Member() {
    }

    public Member(String id, String name, Integer age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Member{" +
                "id='" + id + '\'' +
                ", name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
package chap54.io.member;

import java.util.List;

public interface MemberRepository {
    void add(Member member);

    List<Member> findAll();
}
package chap54.io.member;


import java.util.ArrayList;
import java.util.List;

public class MemoryMemberRepository implements MemberRepository {

    private final List<Member> memberList = new ArrayList<>();

    @Override
    public void add(Member member) {
        memberList.add(member);
    }

    @Override
    public List<Member> findAll() {
        return memberList;
    }
}
package chap54.io.member;

import chap54.io.member.impl.DataMemberRepository;
import chap54.io.member.impl.FileMemberRepository;
import chap54.io.member.impl.ObjectMemberRepository;

import java.util.List;
import java.util.Scanner;

public class MemberConsoleMain {
    //private static final MemberRepository repository = new MemoryMemberRepository();
    //private static final FileMemberRepository repository = new FileMemberRepository();
    //private static final DataMemberRepository repository = new DataMemberRepository();
    private static final ObjectMemberRepository repository = new ObjectMemberRepository();


    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        while (true) {
            System.out.println("1. 회원 등록 | 2. 회원 목록 조회 | 3. 종료");
            System.out.print("선택 : ");
            int choice = sc.nextInt();
            sc.nextLine();

            switch (choice) {
                case 1:
                    registerMember(sc);
                    break;

                case 2:
                    displayMembers();
                    break;

                case 3:
                    System.out.println("프로그램을 종료합니다.");
                    return;

                case 4:
                    System.out.println("다시 입력하세요");
            }
        }
    }

    private static void registerMember(Scanner scanner) {
        System.out.print("ID 입력 : ");
        String id = scanner.nextLine();

        System.out.print("Name 입력 : ");
        String name = scanner.nextLine();

        System.out.print("Age 입력 : ");
        int age = scanner.nextInt();

        Member member = new Member(id, name, age);
        repository.add(member);
        System.out.println("회원이 성공적으로 등록");
    }

    private static void displayMembers() {
        List<Member> members = repository.findAll();
        for (Member member : members) {
            System.out.printf("[ID : %s, Name : %s, Age : %d]\n]", member.getId(), member.getName(), member.getAge());
        }
    }
}

데이터를 메모리에 보관하기 때문에 자바 종료시 정보 사라진다.

파일에 보관

package chap54.io.member.impl;

import chap54.io.member.Member;
import chap54.io.member.MemberRepository;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;

public class FileMemberRepository implements MemberRepository {

    private static final String FILE_PATH = "temp/members-txt.dat";
    private static final String DELIMITER = ",";

    @Override
    public void add(Member member) {
        //계속 추가 해야하기 때문에 append true (항상 인코딩 정보 들어가야 함)
        try(BufferedWriter bw = new BufferedWriter(new FileWriter(FILE_PATH, StandardCharsets.UTF_8, true))) {
            bw.write(member.getId() + DELIMITER + member.getName() + DELIMITER + member.getAge());
            bw.newLine(); //enter
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public List<Member> findAll() {
        List<Member> members = new ArrayList<>();
        try(BufferedReader br = new BufferedReader(new FileReader(FILE_PATH, StandardCharsets.UTF_8))) {
            String line;
            while ((line = br.readLine()) != null) {
                String[] memberData = line.split(DELIMITER); //, 단위로 자름
                //회원 데이터를 만듬
                members.add(new Member(memberData[0], memberData[1], Integer.valueOf(memberData[2])));
            }

            return members;
            //회원 목록조회를 바로 할 경우, 회원 저장 안된 것이기 때문에 -> Exception을 잡음
        } catch (FileNotFoundException e) {
            return new ArrayList<>(); // 빈 값으로 반환
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

빈 컬렉션 반환
new ArrayList() 보다는 List.of 사용

문제점
모든 타입을 문자로 저장하는 문제
구분자를 사용하는 문제

DataStream

자바의 데이터 타입을 그대로 사용 가능, 자바 타입을 그대로 사용하면서 파일에 데이터를 저장, 불러올 수 있고 구분자 사용 X

package chap54.io.member.impl;

import chap54.io.member.Member;
import chap54.io.member.MemberRepository;

import java.io.*;
import java.util.ArrayList;
import java.util.List;

public class DataMemberRepository implements MemberRepository {
    private static final String FILE_PATH = "temp/members-txt.dat";


    @Override
    public void add(Member member) {
        try {
            DataOutputStream dos = new DataOutputStream(new FileOutputStream(FILE_PATH, true));
            dos.writeUTF(member.getId());
            dos.writeUTF(member.getName());
            dos.writeInt(member.getAge());
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public List<Member> findAll() {
        List<Member> members = new ArrayList<>();
        try {
            DataInputStream dis = new DataInputStream(new FileInputStream(FILE_PATH));

            //더 앍어올 파일이 있는지
            while (dis.available() > 0) {
                //저장한 순서대로 조회
                //2byte를 숫자로 쓰고, 뒤에 값들을 넣는다.
                //읽어드릴때 숫자를 우선 읽고 그 뒤에 값들을 읽음
                members.add(new Member(dis.readUTF(), dis.readUTF(), dis.readInt()));
            }
            return members;
        } catch (FileNotFoundException e) {
            return new ArrayList<>();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

회원을 저장
회원을 저장할때는 회원 필드의 타입에 맞는 메서드를 호출

DataStream 원리
writeUTF은 UTF-8 형식으로 문자를 저장, 2byte를 추가로 사용, 앞 글자의 길이를 저장

3id1(2byte(문자 길이) + 3byte(실제 문자 데이터))

한계
자바의 int와 같이 4byte를 사용해서 저장, -> writeInt를 사용하면 4byte를 사용해서 저장
회원의 필드 하나하나를 다 조회해서 각 타입에 맞도록 따로따로 저장

ObjectStream

메모리에 보관되어 있는 회원 인스턴스를 파일에 편리하게 저장.

객체 직렬화

자바 객체 직렬화(Serialization)는 메모리에 있는 객체 인스턴스를 바이트 스트림으로 변환하여 파일에 저장하거나 네트워크를 통해 전송할 수 있도록 하는 기능

public interface Serializable {
}

아무런 기능 없음, 단지 직렬화를 사용한다는 마커

//Serializable 직렬화 마커 (있어야 직렬화 가능)
public class Member implements Serializable {
    private String id;
    private String name;
    private Integer age;

    public Member() {
    }
package chap54.io.member.impl;

import chap54.io.member.Member;
import chap54.io.member.MemberRepository;

import java.io.*;
import java.util.ArrayList;
import java.util.List;

public class ObjectMemberRepository implements MemberRepository {
    private static final String FILE_PATH = "temp/members-txt.dat";


    //객체를 byte화 하면 network에 전송하는 것 또한 가능 하다.
    @Override
    public void add(Member member) {
        //회원을 전체를 한번에 저장
        List<Member> members = findAll();
        members.add(member);

        try {
            //ObjectOutputStream 객체를 직렬화 해주는 기능
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(FILE_PATH));
            oos.writeObject(members);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public List<Member> findAll() {
        try {
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream(FILE_PATH));
            Object findObject = ois.readObject();
            //회원 객체를 하나씩 저장하면 하나씩 밖에 안되지만, 통체로 파일로 만들어서 읽어온다.
            return (List<Member>) findObject;
        } catch (FileNotFoundException e) {
            //return List.of() //수정할 수 없는 빈 컬렉션을 반환
            return new ArrayList<>();
        } catch (IOException | ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    }
}

직렬화
객체 인스턴스를 직려화 해서 byte로 변경
회원 객체 하나가 아니라 회원 목록 전체를 파일 저장, members 컬렉션을 직렬화 해야 함

역직렬화
ObjectInputStream을 사용하면 byte를 역직렬화 해서 객체 인스턴스로 만들어 사용

XML, JSON, 데이터베이스

객체 직렬화의 한계

  • 버전 관리의 어려움
    클래스 구조가 변경되면 이전에 직렬화된 객체와의 호환성 문제
    serialVersionUID 관리가 복잡

  • 플랫폼 종속성
    자바 직렬화는 자바 플랫폼에 종속적이어서 다른 언어나 시스템과의 상호 운용성이 떨어진다.

  • 성능 이슈
    직렬화/역직렬화 과정이 상대적으로 느리고 리소스를 많이 사용

  • 유연성 부족
    직렬화된 형식을 커스터마이즈하기 어렵다.

  • 크기 효율성
    직렬화된 데이터의 크기가 상대적으로 크다.

대안 1. XML

복잡성과 무거움이라는 문제가 있었다. 태그를 포함한 XML 문서의 크기가
커서 네트워크 전송 비용도 증가

대안 2. Json

웹 API와 RESTful 서비스가 대중화되면서 JSON은 표준 데이터 교환 포맷으로 자리 잡았다.

데이터 베이스

어떤 형식이든 데이터를 저장할 때, 파일에 데이터를 직접 저장하는 방식은 몇 가지 큰 한계
1. 데이터의 무결성을 보장하기 어렵다. 여러 사용자가 동시에 파일을 수정하거나 접근하려고 할 때, 데이터의
충돌이나 손상 가능성이 높음

  1. 데이터 검색과 관리의 비효율성이다. 파일에 저장된 데이터는 특정 형식 없이 단순히 저장될 수 있기 때문
    에, 필요한 데이터를 빠르게 찾는 데 많은 시간이 소요

  2. 보안 문제이다. 파일 기반 시스템에서는 민감한 데이터를 안전하게 보호하기 위한 접근 제어와 암호화 등이
    충분히 구현되지 않을 수 있다

  3. 대규모 데이터의 효율적인 백업과 복구가 필요

-> 데이터베이스로 문제점 해결

profile
개발자를 향해 가는 중입니다~! 항상 겸손

0개의 댓글