I/O 활용
#Java/adv2
회원 데이터를 관리하는 콘솔 프로그램 예제(Create, Read)
- Member 클래스 정의 및 Repository 인터페이스 정의
- ArrayList에 저장하는 방법
- 문제: 자바 프로그램 종료 시 모든 회원 정보가 사라진다
- 파일에 저장해서 데이터를 영구 보존해보자.
- 한 줄 단위로 회원 데이터를 파일에 저장(ID,Name,Age)
- BufferedReader, BufferedWriter 보조 스트림을 이용.
- try-with-resource로 스트림 선언 및 해제
- 코드 블록이 끝나면 자동으로
close()
가 호출
- 코드 블록이 끝나면 자동으로
try (BufferedWriter bw = new BufferedWriter(new FileWriter(FILE_PATH, UTF_8, true)))
bw.write(member.getId() + DELIMITER + member.getName() + DELIMITER + member.getAge());
bw.newLine();
(회원 구분)
try (BufferedReader br = new BufferedReader(new FileReader(FILE_PATH, UTF_8)))
while ((line = br.readLine()) != null)
: 한 줄씩(회원 한 명씩) 읽기String[] memberData = line.split(DELIMITER);
members.add(new Member(memberData[0], memberData[1], Integer.valueOf(memberData[2])));
- FileNotFoundException의 경우 IOException의 자식이다. findAll()에서 파일이 존재하지 않는 경우, 예외를 잡아서 빈 ArrayList를 반환해준다.
- 해당 컬렉션에 데이터를 추가하지 않는 경우에는 List.of()를 사용하는게 좋다.
- (BW로 쓰기를 할 때는 컬렉션을 거치지 않고 파일에 작성한다 List.of()로 불변 컬렉션 받는게 더 좋음)
- 문제점: 각 필드를 구분하기 위해 구분자를 사용하여 저장할 때 구분자를 넣어주고, 조회할 때 구분자를 사용해서 구분해야 함.
DataOutputStream
,DataInputStream
을 사용하면 자바의 데이터 타입을 그대로 사용할 수 있다.- 구분자를 사용하지 않아도 된다
try (DataOutputStream dos = new DataOutputStream(new FileOutputStream(FILE_PATH, true)))
dos.writeUTF(member.getName());
,dos.writeInt(member.getAge());
, …
try (DataInputStream dis = new DataInputStream(new FileInputStream(FILE_PATH)))
while (dis.available() > 0)
members.add(new Member(dis.readUTF(), dis.readUTF(), dis.readInt()));
- FileNotFoundException의 경우 예외 처리는 이전과 동일
- /DataStream 원리: 구분자나 개행 없이 데이터를 저장하고 조회할 수 있는 이유?
- id와 name은 모두 문자열이라서, id1name1 이렇게 저장되는데, 어떻게 id와 name을 딱 끊어서 구분할까?
writeUTF()
은 UTF-8 형식으로 문자를 저장하는데, 저장할 때 2byte를 추가로 사용해서 앞에 글자의 길이를 저장해둔다. (3id1 5바이트(2 + 3), 5name1 7바이트(2 + 5), 20(정수, 나이, 4바이트))readUTF()
로 읽어들일 때 먼저 앞의 2byte로 글자의 길이를 확인하고 해당 길이 만큼 글자를 읽어들인다.
DataStream
덕분에 자바의 타입도 그대로 사용하고, 구분자도 제거할 수 있었다.- 모든 데이터를 문자로 저장할 때 보다 저장 용량도 더 최적화 할 수 있다.
- 문제점: 더 편리해지긴 했는데, 여전히 회원의 필드 하나하나를 다 조회해서 각 타입에 맞도록 따로따로 저장하고 조회해야 한다.
/회원 관리 예제4 - ObjectStream :회원 객체 통채로 편하게 저장할 수 있는 방법
- 회원 인스턴스도 생각해보면 메모리 어딘가에 보관되어 있다 메모리의 객체를 읽어서 저장해보자.
ObjectStream
을 사용하면 이렇게 메모리에 보관되어 있는 회원 인스턴스를 파일에 편리하게 저장할 수 있다.- /객체 직렬화
- 메모리에 있는 객체 인스턴스를 바이트 스트림으로 변환하여 파일에 저장하거나 네트워크를 통해 전송할 수 있도록 하는 기능
- 객체의 상태를 유지하여 나중에 역직렬화(Deserialization)를 통해 원래의 객체로 복원할 수 있다.
- 객체 직렬화를 사용하려면 직렬화하려는 클래스는 반드시
Serializable
인터페이스를 구현해야 한다.- 아무런 기능이 없는 인터페이스다. 직렬화 가능한 클래스라는 표시가 목적이다.
- 메서드 없이 단지 표시가 목적인 인터페이스를 마커 인터페이스라 한다.
- 구현하지 않으면
NotSerializableException
예외가 발생한다.
- 객체 직렬화와 관련된 키워드(참고 사항)
serialVersionUID
: 객체 직렬화 버전을 관리한다.transient
키워드:transient
가 붙은 필드는 직렬화하지 않고 무시한다.
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(FILE_PATH)))
List<Member> members = findAll();
members.add(member)
oos.writeObject(members);
- 컬렉션 + 컬렉션에 포함된 member를 통째로 write한다.
- 메모리의 객체를 읽어서 완전히 새로 쓴다. 대상 스트림 만들 때 append 이런 거 붙이면 안 된다.
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(FILE_PATH)))
Object findObject = ois.readObject();
return (List<Member>) findObject;
add()
메서드에서 findAll()에서 반환된 컬렉션에 member를 추가하므로, 불변 컬렉션을 반환하는 List.of()를 사용하면 안 된다.
- /직렬화
ObjectOutputStream
을 사용하면 객체 인스턴스를 직렬화하여 byte로 변환할 수 있다.- 회원 목록 전체를 파일에 저장해야 하므로,
members
컬렉션을 직렬화 해야한다 oos.writeObject(members)
를 호출하면members
컬렉션과 그 안에 포함된Member
를 모두 직렬화하여 byte로 변경한다. 그리고oos
와 연결되어 있는FileOutputStream
에 결과를 출력한다.ArrayList
,Member
같은 클래스 정보도 함께 저장된다
- /역직렬화
ObjectInputStream
을 사용하면 byte를 역직렬화하여 객체 인스턴스로 만들 수 있다.- 반환타입이
Object
이므로 캐스팅해서 사용해야 한다.
- 현재는 객체 직렬화를 거의 사용하지 않는다.
- /객체 직렬화의 한계
- 버전관리: 클래스 구조가 변경되면 이전 직렬화된 객체와 호환성 문제가 발생, UID 관리가 복잡함
- 플랫폼 종속성: 자바에 종속적이어서, 다른 언어나 시스템과 상호 운영성 떨어짐
- 성능 이슈: 직렬화/역직렬화 과정이 상대적으로 느리고 리소스를 많이 사용한다.
- 유연성 부족: 직렬화된 형식 커스터마이징이 어려움
- 크기 효율성: 데이터 크기가 상대적으로 크다
- 객체 직렬화 대안1: XML
- 플랫폼 종속성 문제를 해결하기 위해 등장
- 복잡성과 무거움이라는 문제가 있었다. (데이터에 비해 태그 껍데기가 많음)
- 객체 직렬화의 대안2 - JSON
- JSON은 가볍고 간결
- 자바스크립트와의 자연스러운 호환성
- 웹 API와 RESTful 서비스가 대중화되면서 사실상 JSON은 표준 데이터 교환 포맷이 됨
- 거의 모든 곳에서 호환이 가능하고, 사람이 읽고 쓰기 쉬운 텍스트 기반 포맷이어서 디버깅과 개발이 쉬움
- 단점: 텍스트 기반 포맷이라 용량이 큼
- 객체 직렬화의 대안3 - Protobuf, Avro - 더 적은 용량, 더 빠른 성능
- 매우 작은 용량으로 더 빠른 속도가 필요하다면 Protobuf, Avro 같은 대안 기술이 있다.
- 언어별로 존재한다. 협의가 편한곳에서 사용한다. 이 정도까지 최적화가 필요한 경우는 정말 드물다.
- 호환성은 떨어지지만 byte 기반에, 용량과 성능 최적화가 되어 있으므로 매우 빠르다.
- byte 기반이라 사람이 읽기는 어려움
- /데이터베이스
- 파일에 데이터를 저장하는 방식: 한계점
- 데이터의 무결성을 보장하기 어렵다.
- 데이터 검색과 관리의 비효율성이다.
- 보안 문제, 대교모 데이터의 효율적인 백업과 복구
- 이러한 문제점들을 하나하나 해결하면서 발전한 서버 프로그램이 바로 데이터베이스이다.
- 실무에서는 이미지, 영상처럼 큰 데이터를 제외하면 대부분의 데이터를 파일에 저장하지 않고 데이터베이스에 저장한다.
- 파일에 데이터를 저장하는 방식: 한계점
'Java > IO, Network' 카테고리의 다른 글
[Java] 51. 네트워크 - 프로그램(2) (0) | 2025.07.01 |
---|---|
[Java] 50. 네트워크 - 프로그램(1) (0) | 2025.07.01 |
[Java] 49. File, Files (0) | 2025.07.01 |
[Java] 47. 자바 IO 기본(2) (0) | 2025.07.01 |
[Java] 46. 자바 IO 기본(1) (0) | 2025.07.01 |