개발자입니다
[비트캠프] 59일차(13주차1일) - Java(컬렉션 API, 파일 입출력), myapp-26 본문
[비트캠프] 59일차(13주차1일) - Java(컬렉션 API, 파일 입출력), myapp-26
끈기JK 2023. 1. 30. 10:52
Collection API
List 관련 클래스 계층도(hierarchy)
List 관련 클래스 계층도는 위와 같다.
《interface》Iterable 에 iterator(), forEach() 표준 정의하였다.
- 이를 상속받은 《interface》Collection 에 add(), contains(), remove(), size(), toArray() 표준 정의하였다.
- 이를 상속받은 《interface》List에 add(int), set(int), remove(int), get(int) 표준 정의하였다.
- 이를 구현한 《abstract》abstractList 를 상속받은 vector 를 상속받은 Stack 에 push(), pop(), peek() 를 구현하였다.
- 이를 상속받은 《interface》Queue에 peek(), poll(), offer() 표준 정의하였다.
- 이를 상속받은 《interface》List에 add(int), set(int), remove(int), get(int) 표준 정의하였다.
향상된 for문 : 우측에 올 수 있는 건 배열 또는 iterable 구현체이다.
static void print(MyLinkedList<String> list) {
for (String e : list) {
System.out.println(e);
}
System.out.println("------------------------");
}
아래와 같이 구현하면 향상된 for 를 사용할 수 있다.
public class MyLinkedList<E> implements Cloneable, Iterable<E> {
@Override
public Iterator<E> iterator() {
return new Iterator<E>() {
int cursor;
@Override
public boolean hasNext() {
return cursor >= 0 && cursor < MyLinkedList.this.size();
}
@Override
public E next() {
return MyLinkedList.this.get(cursor++);
}
};
}
Set 관련 클래스 계층도(hierarchy)
《interface》Set 은 중복 불가! = "집합" 이다.
Map 관련 클래스 계층도(hierarchy)
《interface》Map 에 put(k, v), get(k), keySet(), values(), entrySet() 표준 정의하였다. 《abstract》AbstractMap 가 구현하고 HashMap 이 상속하였다.
entrySet() 은 Set으로 원소는 Map.Entry 이다. Map.Entry 는 key, value 를 갖는다.
File I/O API
FileOutputStream / FileInputStream
객체를 byte[ ] 로 변환해 FileOutputStream 을 사용해 파일로 만든다.
파일을 FileInputStream 을 사용해 byte[ ] 로 만들어 객체로 변환한다.
myapp
26. 바이너리 데이터 입출력
### 26. 파일 API를 사용하여 데이터를 바이너리 형식으로 입출력하기: FileInputStream/FileOutputStream
- 입출력 스트림 API를 사용하여 데이터를 파일로 저장하고 읽는 방법
- 바이너리 형식으로 데이터를 입출력하는 방법
Board 객체를 byte[ ] 로 변환하고 파일로 만든다. 파일을 byte[ ] 로 만들고 Board 객체로 변환한다.
Board 객체들을 byte[ ], byte[ ], ... 로 변환한다. 객체별 배열 크기를 2byte 로 저장하고 byte[ ] 를 저장한다. 뒤이어 배열 크기를 2byte 로 저장하고 byte[ ] 를 저장한다. 이를 Board 객체로 만들어 List에 add() 한다.
List 에서 get() 하여 Board 객체를 만들고 이를 byte[ ] 로 변환한다. 이를 byte[ ], byte[ ], ... 로 변환하고 Board 객체로 만든다.
저장과 로드 각각 따로 코드 확인한다.
1. 파일 저장 : 게시판 나가려고 0 입력시 boardDao.save() 호출한다.
2. 파일 로드 : 게시판 들어가려고 BoardHandler 객체의 service() 실행할때 boardDao.load() 호출한다.
public class BoardHandler {
/* 수정 */
public void service() {
boardDao.load("board.data"); // 추가
while (true) {
System.out.printf("[%s]\n", this.title);
System.out.println("1. 등록");
System.out.println("2. 목록");
System.out.println("3. 조회");
System.out.println("4. 변경");
System.out.println("5. 삭제");
System.out.println("6. 검색");
System.out.println("0. 이전");
int menuNo = Prompt.inputInt(String.format("%s> ", this.title));
switch (menuNo) {
case 0:
boardDao.save("board.data"); // 추가
return;
case 1: this.inputBoard(); break;
case 2: this.printBoards(); break;
case 3: this.printBoard(); break;
case 4: this.modifyBoard(); break;
case 5: this.deleteBoard(); break;
case 6: this.searchBoard(); break;
default:
System.out.println("잘못된 메뉴 번호 입니다.");
}
}
}
}
public class BoardDao {
/* 아래 코드 추가 */
public void save(String filename) {
try (
// 1) 바이너리 데이터(바이트배열)를 출력할 도구를 준비한다.
FileOutputStream out = new FileOutputStream(filename)) {
// 2) 게시글 개수를 저장 : 4byte
out.write(BinaryEncoder.write(list.size()));
// 3) 게시글 출력
// 목록에서 Board 객체를 꺼내 바이트 배열로 만든 다음 출력한다.
for (Board b : list) {
out.write(BinaryEncoder.write(b.getNo()));
out.write(BinaryEncoder.write(b.getTitle()));
out.write(BinaryEncoder.write(b.getContent()));
out.write(BinaryEncoder.write(b.getPassword()));
out.write(BinaryEncoder.write(b.getViewCount()));
out.write(BinaryEncoder.write(b.getCreatedDate()));
}
} catch (Exception e) {
e.printStackTrace();
}
}
public void load(String filename) {
if (list.size() > 0) { // 중복 로딩 방지!
return;
}
try (
// 1) 바이너리 데이터를 읽을 도구 준비
FileInputStream in = new FileInputStream(filename)) {
// 2) 저장된 게시글 개수를 읽는다: 4byte
int size = BinaryDecoder.readInt(in);
// 3) 게시글 개수 만큼 반복해서 데이터를 읽어 Board 객체에 저장한다.
for (int i = 0; i < size; i++) {
// 4) 바이너리 데이터를 저장한 순서대로 읽어서 Board 객체에 담는다.
Board b = new Board();
b.setNo(BinaryDecoder.readInt(in));
b.setTitle(BinaryDecoder.readString(in));
b.setContent(BinaryDecoder.readString(in));
b.setPassword(BinaryDecoder.readString(in));
b.setViewCount(BinaryDecoder.readInt(in));
b.setCreatedDate(BinaryDecoder.readString(in));
// 5) Board 객체를 목록에 추가한다.
list.add(b);
}
if (list.size() > 0) {
lastNo = list.get(list.size() - 1).getNo();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
public class TeacherDao {
/* 아래 코드 추가 */
public void save(String filename) {
try (FileOutputStream out = new FileOutputStream(filename)) {
out.write(BinaryEncoder.write(list.size()));
for (Teacher t : list) {
out.write(BinaryEncoder.write(t.getNo()));
out.write(BinaryEncoder.write(t.getName()));
out.write(BinaryEncoder.write(t.getTel()));
out.write(BinaryEncoder.write(t.getCreatedDate()));
out.write(BinaryEncoder.write(t.getEmail()));
out.write(BinaryEncoder.write(t.getDegree()));
out.write(BinaryEncoder.write(t.getSchool()));
out.write(BinaryEncoder.write(t.getMajor()));
out.write(BinaryEncoder.write(t.getWage()));
}
} catch (Exception e) {
e.printStackTrace();
}
}
public void load(String filename) {
if (list.size() > 0) {
return;
}
try (FileInputStream in = new FileInputStream(filename)) {
int size = BinaryDecoder.readInt(in);
for (int i = 0; i < size; i++) {
Teacher t = new Teacher();
t.setNo(BinaryDecoder.readInt(in));
t.setName(BinaryDecoder.readString(in));
t.setTel(BinaryDecoder.readString(in));
t.setCreatedDate(BinaryDecoder.readString(in));
t.setEmail(BinaryDecoder.readString(in));
t.setDegree(BinaryDecoder.readInt(in));
t.setSchool(BinaryDecoder.readString(in));
t.setMajor(BinaryDecoder.readString(in));
t.setWage(BinaryDecoder.readInt(in));
list.add(t);
}
if (list.size() > 0) {
lastNo = list.get(list.size() - 1).getNo();
}
} catch (FileNotFoundException e) {
System.out.println("데이터 파일이 존재하지 않습니다!");
} catch (Exception e) {
e.printStackTrace();
}
}
}
public class StudentDao {
/* 아래 코드 추가 */
public void save(String filename) {
try (FileOutputStream out = new FileOutputStream(filename)) {
out.write(BinaryEncoder.write(list.size()));
for (Student s : list) {
out.write(BinaryEncoder.write(s.getNo()));
out.write(BinaryEncoder.write(s.getName()));
out.write(BinaryEncoder.write(s.getTel()));
out.write(BinaryEncoder.write(s.getCreatedDate()));
out.write(BinaryEncoder.write(s.getPostNo()));
out.write(BinaryEncoder.write(s.getBasicAddress()));
out.write(BinaryEncoder.write(s.getDetailAddress()));
out.write(BinaryEncoder.write(s.isWorking()));
out.write(BinaryEncoder.write(s.getGender()));
out.write(BinaryEncoder.write(s.getLevel()));
}
} catch (Exception e) {
e.printStackTrace();
}
}
public void load(String filename) {
if (list.size() > 0) {
return;
}
try (FileInputStream in = new FileInputStream(filename)) {
int size = BinaryDecoder.readInt(in);
for (int i = 0; i < size; i++) {
Student s = new Student();
s.setNo(BinaryDecoder.readInt(in));
s.setName(BinaryDecoder.readString(in));
s.setTel(BinaryDecoder.readString(in));
s.setCreatedDate(BinaryDecoder.readString(in));
s.setPostNo(BinaryDecoder.readString(in));
s.setBasicAddress(BinaryDecoder.readString(in));
s.setDetailAddress(BinaryDecoder.readString(in));
s.setWorking(BinaryDecoder.readBoolean(in));
s.setGender(BinaryDecoder.readChar(in));
s.setLevel(BinaryDecoder.readByte(in));
list.add(s);
}
if (list.size() > 0) {
lastNo = list.get(list.size() - 1).getNo();
}
} catch (FileNotFoundException e) {
System.out.println("데이터 파일이 존재하지 않습니다!");
} catch (Exception e) {
e.printStackTrace();
}
}
}
util 패키지에 Binary Encoder, Decoder 생성한다.
package bitcamp.util;
public class BinaryEncoder {
public static byte[] write(byte value) {
return new byte[] {value};
}
public static byte[] write(char value) {
return new byte[] {
(byte) (value >> 8),
(byte) (value)};
}
public static byte[] write(boolean value) {
return new byte[] {(byte) (value ? 1 : 0)};
}
public static byte[] write(int value) {
byte[] bytes = new byte[4];
bytes[0] = (byte) (value >> 24);
bytes[1] = (byte) (value >> 16);
bytes[2] = (byte) (value >> 8);
bytes[3] = (byte) value;
return bytes;
}
public static byte[] write(String value) {
// [2byte: 문자열의 바이트 배열 길이][n바이트: 문자열의 바이트 배열]
byte[] strBytes = value.getBytes();
byte[] bytes = new byte[strBytes.length + 2];
System.arraycopy(strBytes, 0, bytes, 2, strBytes.length);
bytes[0] = (byte) (strBytes.length >> 8);
bytes[1] = (byte) (strBytes.length);
return bytes;
}
public static void main(String[] args) {
// int value = 0xabcdef31;
// byte[] bytes = ByteArrayGenerator.write(value);
byte[] bytes = BinaryEncoder.write("ABC가각간");
for (int i = 0; i < bytes.length; i++) {
System.out.printf("%02x", bytes[i]);
}
}
}
package bitcamp.util;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
public class BinaryDecoder {
public static byte readByte(InputStream in) throws Exception {
return (byte) in.read();
}
public static char readChar(InputStream in) throws Exception {
int value = in.read() << 8;
value |= in.read();
return (char) value;
}
public static boolean readBoolean(InputStream in) throws Exception {
return in.read() == 1 ? true : false;
}
public static int readInt(InputStream in) throws Exception {
int value = 0;
value = in.read() << 24;
value |= in.read() << 16;
value |= in.read() << 8;
value |= in.read();
return value;
}
public static String readString(InputStream in) throws Exception {
// [2byte: 문자열의 바이트 배열 길이][n바이트: 문자열의 바이트 배열]
// 1) 2바이트를 읽어 문자열의 배열 개수를 알아낸다.
int length = in.read() << 8;
length |= in.read();
// 2) 문자열의 배열을 읽어 들일 빈 배열을 준비한다.
byte[] bytes = new byte[length];
// 3) 문자열의 배열을 읽어 빈 배열에 담는다.
in.read(bytes, 0, length);
// 4) 배열에 들어 있는 문자 코드를 가지고 String 객체를 생성한다.
String str = new String(bytes);
return str;
}
public static void main(String[] args) throws Exception {
// 데이터가 저장된 바이트 배열 준비
byte[] bytes = new byte[] {0x00, 0x05, (byte)0x41, (byte)0x42, (byte)0xea, (byte)0xb0, (byte)0x80};
// 바이트 배열에서 데이터를 읽는 도구 준비
ByteArrayInputStream in = new ByteArrayInputStream(bytes);
// 바이트 배열을 읽어서 String을 리턴 받는다.
String str = BinaryDecoder.readString(in);
// 잘읽었는지 출력해 본다.
System.out.println(str);
in.close();
}
}
String → byte[]
"AB가각".getBytes() 로 8byte 배열을 얻는다. length 를 구하고 byte[ ] bytes = new byte[8 + 2] 로 새 배열을 만든 다음, length 정보를 맨 앞 2byte에 저장한다.
System.arraycopy(strBytes, 0 , bytes, 2, 8); 로 배열 정보 생성한다.
byte[ ] → String
byte[ ] 를 String 으로 바꾼다. readString() 을 사용한다. 먼저 cursor 가 위처럼 있다고 가정한다. 2byte를 읽어 4byte인 int에 저장한다. 얻어낸 길이 5byte 정보를 토대로 다음 5byte를 읽어 byte[ ] 에 저장한다. 이를 new String( ) 괄호에 넣는다.
Board ↔ 파일
BoardDao 에서 Board 객체를 BinaryEncoder를 사용해 byte[ ] 로 만든다. FileOutputStream 의 write() 사용해 파일로 만든다.
파일을 FileInputStream 사용해 byte[ ] 로 만든다. byte[ ] 를 BinaryDecoder 사용해 Board 객체로 만들어 BoardDao 에 리턴한다.
조언
*SI에서 1억 연봉 뚫으려면 그 당시 트렌드인 신기술을 다뤄야 한다. 하지만 기술 초기에는 연봉 높지만 시간이 지나면 사람들이 많이 진입하므로 연봉이 다시 돌아오게 된다.
*전문가가 되려면 결국 수학을 잘해야한다. 또는 한 기술의 전문가가 되어야한다. 아니면 대량의 데이터를 처리한 경험이 있어야한다.
*객체 지향은 어느 클래스와 어떤 관계를 맺고 있느냐를 파악하는 것이 중요하다.
과제
저녁 학습
- com/eomcs/io/ex01~02
파일 입출력 완성
- BoardDao 참고하여 StudentDao, TeacherDao 완성하라