포스트

Day38 실습프로젝트(File I/O)

Stack 리팩토링 - menuPath

1. 기존 스택의 문제점

    ```java //기존 menuGroup에서 Stack이 사용되는 소스코드 public MenuGroup(String title) { super(title); this.menuPath = new Stack<>(); } public void setParent(MenuGroup parent) { this.parent = parent; this.menuPath = parent.menuPath; } private String getMenuPathTitle(Stack menuPath) { StringBuilder strBuilder = new StringBuilder(); for (String s : menuPath) { if (!strBuilder.isEmpty()) { strBuilder.append("/"); } strBuilder.append(s); } return strBuilder.toString(); } ```
  • 기존 소스코드에서 Stack은 menuGroup이 인스턴스 될 때마다 새로운 Stack을 생성한다.
  • 부모의 menuGroup이 있다면 인스턴스된 Stack은 Garbage가 된다.
  • menuPath를 호출하는 메서드는 Stack의 구조로 꺼내는 것이 아니라 List타입으로 탐색을 한다. </ul>

    2. 스택 구조 변경

    1. 현재 위치의 menuTitle을 push한다.
    2. 부모가 있다면 부모의 menuPath로 이동한다.
    3. root의 부모인 null를 만날때 까지 1~2를 반복한다. ![image](https://github.com/user-attachments/assets/26272a60-dbc7-4c51-8476-a1dc86d8296c) ```java public MenuGroup(String title) { super(title); } public void setParent(MenuGroup parent) { this.parent = parent; } private String getMenuPath() { Stack menuPath = new Stack<>(); MenuGroup menuGroup = this; while (menuGroup != null) { menuPath.push(menuGroup.title); menuGroup = menuGroup.parent; } StringBuilder strBuilder = new StringBuilder(); while (!menuPath.empty()) { if (!strBuilder.isEmpty()){ strBuilder.append("/"); } strBuilder.append(menuPath.pop()); } return strBuilder.toString(); } ``` </ol>

      파일 입출력 FILE I/O

      1. 바이너리 데이터 입출력

      • 바이너리 타입의 입력은 텍스트 타입 보다 메모리를 적게사용하고, 속도가 빠르다.
      • 타입별로 인코딩하는 방식이 다르다.
        • "abc" --> UTF-8 문자코드 바이트
        • 20 --> 2의 보수
        • 3.14 --> IEEE-754 규칙
        • true --> 2의 보수
      • 설정 방법이 복잡하다.
      • PDF, PPT, DOC, GIF, JPEG, MP3, AVI 등이 있다.

      2. 데이터 입출력 적용

      1) 데이터 입출력 흐름

        ![image](https://github.com/user-attachments/assets/be6ce308-8c67-4675-af86-250592781bd7)

      2) byte[] 만들기

      • 객체를 byete[] 배열에 1바이트씩 담아야한다.
      • ByteArrayOutputStream 클래스를 이용하여 바이트배열 담을 저장소를 만든다.
      • 이후 toByteArray()메서드로 통해 바이트 배열을 리턴 할 수 있다. ```java public byte[] getBytes() throws IOException { try (ByteArrayOutputStream out = new ByteArrayOutputStream()) { //객체의 정보들을 byte로 담는다. return out.toByteArray(); } } ```

      3) write(int) 메소드

      • 객체를 byete[] 배열에 1바이트씩 담을때 write 메서드를 사용한다.
      • write(int) int 타입을 받지만 1바이트만 읽고 저장할 수 있다.
      • 비트 연산자를 통해 바이트를 1바이트까지 이동 해야한다. ```java ByteArrayOutputStream out = new ByteArrayOutputStream() out.write(no >> 24); out.write(no >> 16); out.write(no >> 8); out.write(no); ```

      4) 데이터 구조

      • 데이터의 구조에 따라 바이트의 할당규칙을 정해야 load과정에서 해당규칙을 통해 값을 읽을 수 있다. ![image](https://github.com/user-attachments/assets/e11fcf04-17e0-4b12-8dae-4e084d615720) ```java //유저정보를 바이트배열로 변환 public byte[] getBytes() throws IOException { try (ByteArrayOutputStream out = new ByteArrayOutputStream()) { //회원번호 넣기 out.write(no >> 24); out.write(no >> 16); out.write(no >> 8); out.write(no); //이름 넣기 byte[] bytes = name.getBytes(StandardCharsets.UTF_8); out.write(bytes.length >> 8); out.write(bytes.length); out.write(bytes); //이메일 넣기 bytes = email.getBytes(StandardCharsets.UTF_8); out.write(bytes.length >> 8); out.write(bytes.length); out.write(bytes); //password bytes = password.getBytes(StandardCharsets.UTF_8); out.write(bytes.length >> 8); out.write(bytes.length); out.write(bytes); //tel bytes = tel.getBytes(StandardCharsets.UTF_8); out.write(bytes.length >> 8); out.write(bytes.length); out.write(bytes); return out.toByteArray(); } } ```

      5) 파일로 출력

      • 데이터의 구조에 따라 바이트배열들을 병합하고 객체의 전체 개수를 구한다 .
      • FileOutputStream에 바이트의 배열들 개수를 넣고 저장한 바이트 배열들을 넣는다. ![image](https://github.com/user-attachments/assets/2f897cc4-b7b7-4242-bd02-10ec8e4501df) ```java //바이트배열로 변환된 유저정보를 파일로 출력 private void saveUser() { try (FileOutputStream out = new FileOutputStream("user.data")) { int userLength = userList.size(); out.write(userLength >> 8); out.write(userLength); for (User user : userList) { byte[] bytes = user.getBytes(); out.write(bytes.length >> 8); out.write(bytes.length); out.write(bytes); } } catch (IOException e) { System.out.println("회원 정보 저장 중 오류 발생" + e.getMessage()); } } ```

      6) 파일로 입력

      • 입력은 출력과정의 역순으로 진행한다. ![image](https://github.com/user-attachments/assets/4cbd2d51-9a64-4f10-a28f-bceea9c786c7) ```java //파일을 테이터 배열로 전환 private void loadUser() { try (FileInputStream in = new FileInputStream("user.data")) { int userLength = in.read() << 8 | in.read(); int maxUserNum = 0; for (int i = 0; i < userLength; i++) { int len = (in.read() << 8) | in.read(); byte[] bytes = new byte[len]; in.read(bytes); User user = User.valueOf(bytes); userList.add(user); maxUserNum = Math.max(maxUserNum, user.getNo()); } User.initSeqNo(maxUserNum); } catch (IOException e) { System.out.println("회원 정보 로딩 중 오류 발생" + e.getMessage()); } } // 데이터 배열을 유저 객체로 전환 public static User valueOf(byte[] bytes) throws IOException { try (ByteArrayInputStream in = new ByteArrayInputStream(bytes)) { User user = new User(); user.setNo(in.read() << 24 | in.read() << 16 | in.read() << 8 | in.read()); byte[] buffer = new byte[10000]; int len = in.read() << 8 | in.read(); in.read(buffer, 0, len); user.setName(new String(buffer, 0, len, StandardCharsets.UTF_8)); len = in.read() << 8 | in.read(); in.read(buffer, 0, len); user.setEmail(new String(buffer, 0, len, StandardCharsets.UTF_8)); len = in.read() << 8 | in.read(); in.read(buffer, 0, len); user.setPassword(new String(buffer, 0, len, StandardCharsets.UTF_8)); len = in.read() << 8 | in.read(); in.read(buffer, 0, len); user.setTel(new String(buffer, 0, len, StandardCharsets.UTF_8)); return user; } } ```
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.