슈퍼코딩/주특기(JAVA)

2024.05.15(수) 슈퍼코딩 신입연수원 7주차 Day 3 후기 - I/O Stream, client-server, 직렬화/역직렬화

곰돌이볼 2024. 5. 15. 13:34
  • 강의
    • 57강(stream 사용하여 컬렉션 우아하게 사용하기) ~ 59강(Server-Client 소개와 직렬화/역직렬화)

Java I/O Stream


  • 자바 입출력 스트림으로, stream API(collection의 stream)과 다른 개념
  • 입력 스트림(Input stream) : 자바 프로그램에서 자료를 읽을 때 사용하는 스트림
  • 출력 스트림(Output stream) : 자바 프로그램에서 파일을 저장할 때 사용하는 스트림
  • 단위
    • 바이트 스트림 : Byte(=1bits) → 자바 초기설정
    • 문자 스트림 : Char(=2Byte)
    • 1bit는 너무 단위가 적어서 더 큰 단위를 사용
  • I/O Stream
    • Byte Stream(8bits)
      • Input Byte Stream : ...InputStream
      • Output Byte Stream : ...OutputStream
    • Char Stream(16bits)
      • Input Char Stream : ...Reader
      • Output Char Stream : ...Writer
  • System
    • in, out, err → 정적변수

 

사용자 입력(Console)

  • 사용자 입력받기(Console)
    • System.in
    • Scanner
더보기
public class Main {
    public static void main(String[] args) {
        // System.in - 문자 입력
        /*
        입력값 a = d
        출력값 a = 100
        출력값 a = d
         */
        int a = 0;
        System.out.print("입력값 a = ");
        try {
            a = System.in.read(); // 입력
        } catch (IOException e) {
            e.printStackTrace();
        }
        System.out.println("출력값 a = " + a);
        System.out.println("출력값 a = " + (char)a);

        // System.in - 문자열 입력
        /*
        입력값 str = hello world!
        출력값 str = hello world!
         */
        int str = 0;
        System.out.print("입력값 str = ");
        try {
            StringBuilder sb = new StringBuilder();
            while ((str = System.in.read()) != '\n') {
                sb.append((char)str); // 입력
            }
            System.out.println("출력값 str = " + sb.toString());
        } catch (IOException e) {
            e.printStackTrace();
        }

        // Scanner
        /*
        입력값 str2 = hellow world~~~~
        출력값 str2 = hellow world~~~~
         */
        Scanner scanner = new Scanner(System.in);
        System.out.print("입력값 str2 = ");
        String str2 = scanner.nextLine(); // 입력
        System.out.println("출력값 str2 = " + str2);
    }
}

 

File I/O

  • File 입출력 - 1Byte
    • FileInputStream : 파일 읽기(1Byte)
    • FileOutputStream : 파일 출력(1Byte)
    • Ascii는 1Byte이고, UTF(한글 지원)은 2Byte
public class Main {
    public static void main(String[] args) {
        // File read
        try (FileInputStream file = new FileInputStream("src/resource/read.txt")) {
            int i;
            while ((i = file.read()) != -1) { // 파일 읽기
                System.out.print((char) i); // 출력
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        
        // File write
        String content = "Hello World2222";
        
        // true값을 넣어서 파일이 있으면 파일 내용 뒤에 이어붙이기 함
        try(FileOutputStream file = new FileOutputStream("src/resource/write.txt", trur)) {
            file.write(content.getBytes()); // 파일 저장
            System.out.println("File write complete");
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

 

 

  • File 입출력 - 2Byte
    • Ascii는 1Byte이고, UTF(한글 지원)은 2Byte
    • FileReader : 파일 읽기(2Byte)
    • FileWriter : 파일 출력 (2Byte)
public class Main {
    public static void main(String[] args) {
        // File read
        try (FileReader file = new FileReader("src/resource/read.txt")) {
            int i;
            while ((i = file.read()) != -1) { // 파일 읽기
                System.out.print((char) i); // 출력
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

        // File write
        String content = "반갑습니다";

        try(FileWriter file = new FileWriter("src/resource/write2.txt")) {
            file.write(content); // 파일 저장
            System.out.println("File write complete");
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

 

Image I/O

  • 이미지 입출력
public class Main {
    public static void main(String[] args) {
        // image path
        String path = "src/resource/oak.png";

        try {
            // image read
            File file = new File(path);
            BufferedImage image = ImageIO.read(file);

            // 이미지 회전
            BufferedImage rotatedImage = rotateImage(image, 90);

            // 변환된 이미지 저장
            String outputPath = "src/resource/oak_rotate.png";
            ImageIO.write(rotatedImage, "png", new File(outputPath));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static BufferedImage rotateImage(BufferedImage image, double angle) {
        double radians = Math.toRadians(angle);
        double sin = Math.abs(Math.sin(radians));
        double cos = Math.abs(Math.cos(radians));

        int width = image.getWidth();
        int height = image.getHeight();

        int newWidth = (int) Math.floor(width * cos + height * sin);
        int newHeight = (int) Math.floor(height * cos + width * sin);

        BufferedImage rotatedImage = new BufferedImage(newWidth, newHeight, BufferedImage.TYPE_INT_ARGB);
        Graphics2D graphics = rotatedImage.createGraphics();

        graphics.translate((newWidth - width) / 2, (newHeight - height) / 2);
        graphics.rotate(radians, width / 2, height / 2);
        graphics.drawRenderedImage(image, null);
        graphics.dispose();

        return rotatedImage;
    }
}

 

 

보조 스트림

  • 목적
    • 기본 기반 stream 문자열 지원
    • 기존 기반 stream 성능 개선
    • 기존 메서드를 간편하게 사용할 수 있도록 개선
  • 종류
    • I/O Stream : 사용자 입출력(console) 개선, 문자열 적용
      • InputStreamReader
        public class Main {
            public static void main(String[] args) {
                // I/O stream
        
                // 적용 전
                int str = 0;
                System.out.print("입력값 str = ");
                try {
                    StringBuilder sb = new StringBuilder();
                    while ((str = System.in.read()) != '\n') {
                        sb.append((char) str); // 입력
                    }
                    System.out.println("InputStreamReader 적용전 : " + sb.toString());
                } catch (IOException e) {
                    e.printStackTrace();
                }
        
                // 적용 후
                int str2 = 0;
                System.out.print("입력값 str2 = ");
                try(InputStreamReader isr = new InputStreamReader(System.in)) {
                    StringBuilder sb = new StringBuilder();
                    while ((str2 = isr.read()) != '\n') {
                        sb.append((char) str2); // 입력
                    }
                    System.out.println("InputStreamReader 적용후 : " + sb.toString());
                } catch (IOException e) {
                    e.printStackTrace();
                }
        
                /*
                입력값 str = 안녕
                InputStreamReader 적용전 : 안녕
                입력값 str2 = 안녕
                InputStreamReader 적용후 : 안녕
                 */
            }
        }
    • Buffered I/O : file 입출력 개선
      • BufferedReader
      • BufferedWriter
        public class Main {
            public static void main(String[] args) {
                /*
                적용 전 : 9.0ms
                적용 후 : 1.0ms
                 */
                // Buffered I/O - 적용 전
                try (FileReader fr = new FileReader("src/resources/read_long.txt");
                     FileWriter fw = new FileWriter("src/resources/write_long.txt")
                ) {
                    double start = System.currentTimeMillis();
                    int data;
                    while ((data = fr.read()) != -1) {
                        fw.write((char) data);
                    }
                    double end = System.currentTimeMillis();
                    System.out.println("적용 전 : " + (end - start) + "ms");
                } catch (FileNotFoundException e) {
                    e.printStackTrace();
                } catch (IOException e) {
                    e.printStackTrace();
                }
        
                // Buffered I/O - 적용 후
                try (BufferedReader br = new BufferedReader(new FileReader("src/resources/read_long.txt"));
                     BufferedWriter bw = new BufferedWriter(new FileWriter("src/resources/write_long.txt"))
                ) {
                    double start = System.currentTimeMillis();
                    int data;
                    while ((data = br.read()) != -1) {
                        bw.write((char) data);
                    }
                    double end = System.currentTimeMillis();
                    System.out.println("적용 후 : " + (end - start) + "ms");
                } catch (FileNotFoundException e) {
                    e.printStackTrace();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    • PrintWriter : file I/O 성능 개선
      • 익숙한 함수인 println(), printf() 매서드 등을 사용할 수 있음
      • PrintWriter pw = new PrintWriter(new FileWriter(filePath));
        pw.println("hello");

I/O stream 정리


  • 기본
    • System.in.read()
    • new FileInputStream(파일경로/파일명)
    • new FileOutputStream(파일경로/파일명)
    • new FileReader(파일경로/파일명)
    • new FileWriter(파일경로/파일명)
  • 보조
    • new Scanner(System.in)
    • new InputStreamReader(System.in)
    • new BufferedReader(new FileReader(파일경로/파일명))
    • new BufferedWriter(new FileWriter(파일경로/파일명))
    • new PrintWriter(new FileWriter(파일경로/파일명));

Server-Client


  • 네트워크를 통한 정보 전달 시대 → Server-Client 구조
  • Client : 데이터를 요청하는 사이드(requset)
  • Server : 데이터 요청에 대한 응답하는 사이드(response)
  • 구조
    • client : server = 1 : N
  • 소켓(socket)
    • 네트워크 통로 게이트
    • server 소켓 오픈 → 오픈된 server 소켓으로 client 소켓 오픈
  • server-client socket과 스트림
    • respone
      • client : output stream
      • server : input stream
    • requset
      • clinet : input stream
      • server : output stream
  • socket 프로그래밍 흐름
  Server   Client  
socket() 소켓 생성   소켓 생성 socket()
bind() IP, Port 주소 할당      
listen() 연결대기 ← ← ← ← ← ← ← ← 연결 요청 connet()
accept() 수락      
write() 송신 → → → → → → → → 수신 read()
read() 수신         송신 write()
cloase() 소켓 소멸   소켓 소멸 close()

 

  • 실습코드 - Server.java
public class Server {
    public static void main(String[] args) {
        try (ServerSocket serverSocket = new ServerSocket(5000); // 서버 소켓 생성
        ) {
            System.out.println("서버가 시작되었습니다.");

            while (true) {
                try (Socket clientSocket = serverSocket.accept()) {  // 클라이언트 접속 대기
                    System.out.println("클라이언트가 접속하였습니다.");

                    // 클라이언트로부터 데이터를 받기 위한 InputStream 생성
                    InputStream clientInputStream = clientSocket.getInputStream();
                    BufferedReader clientBufferedReader = new BufferedReader(new InputStreamReader(clientInputStream));

                    // 클라이언트로 데이터를 보내기 위한 OutputStream 생성
                    OutputStream serverOutputStream = clientSocket.getOutputStream();
                    PrintWriter printWriter = new PrintWriter(serverOutputStream, true);

                    // inputLine
                    String inputLine;

                    // 클라이언트로부터 데이터를 읽고 화면에 출력
                    System.out.println("클라이언트로부터 온 요청 >> ");
                    while ((inputLine = clientBufferedReader.readLine()) != null) {
                        System.out.println(inputLine);

                        // 클라이언트에게 응답을 보냄
                        printWriter.println("서버로부터 응답이 왔습니다.");
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
  • 실습코드 - Client.java
public class Client {
    public static void main(String[] args) {
        try(Socket socket = new Socket("localhost", 5000)) { // 서버 연결

            // 서버로 데이터를 보내기 위한 outputStream 생성
            OutputStream outputStream = socket.getOutputStream();
            PrintWriter clientPrintWriter = new PrintWriter(outputStream, true);

            // 서버로부터 데이터를 받기 위한 inputStream 생성
            InputStream inputStream = socket.getInputStream();
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));

            // 서버에 메세지 전송
            clientPrintWriter.println("안녕하세요. 저는 클라이언트 요청입니다.");

            // 서버로부터 받은 응답 출력
            String response = bufferedReader.readLine();
            System.out.println("서버로부터 받은 응답 >>\n " + response);

            System.out.println("Client is closed");
        } catch (UnknownHostException e) {
            e.printStackTrace();
        }
        catch (IOException e) {
            e.printStackTrace();
        }
    }
}
  • 실습결과
server client
서버가 시작되었습니다.
클라이언트가 접속하였습니다.
클라이언트로부터 온 요청 >> 
안녕하세요. 저는 클라이언트 요청입니다.
서버로부터 받은 응답 >>
 서버로부터 응답이 왔습니다.
Client is closed

 


직렬화/역직렬화


  • 직렬화 : java 객체 → Byte 코드
  • 역직렬화 : Byte 코드 → java 객체
  • 구현
    • Serializable 상속
    • Object I/O Stream
    • transient : 직렬화 대상 제외
    • SerialVersionUID : 직렬화 고유 번호 관리
  • 구현
public class SerializeExample {
    public static void main(String[] args) {
        /*
        Student 직렬화 : �� sr practice.day20240515.Student���u�7�! I ageL namet Ljava/lang/String;xp   t 	홍길동
        Student 역직렬화 : 홍길동, 20
         */

        // 직렬화
        byte[] serializedData = null;
        Student student = new Student("홍길동", 20);
        try (ByteArrayOutputStream bao = new ByteArrayOutputStream();
             ObjectOutputStream oos = new ObjectOutputStream(bao);
        ) {
            oos.writeObject(student);
            oos.flush(); // 적용

            serializedData = bao.toByteArray();
            System.out.println("Student 직렬화 : " + new String(serializedData));
        } catch (IOException e) {
            e.printStackTrace();
        }

        // 역직렬화
        try (ByteArrayInputStream bio = new ByteArrayInputStream(serializedData);
             ObjectInputStream ois = new ObjectInputStream(bio);
        ) {
            Student student2 = (Student) ois.readObject();
            System.out.println("Student 역직렬화 : " + student2.getName() + ", " + student2.getAge());
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    }
}

class Student implements Serializable {
    transient String name; // 직렬화 제외
    int age;

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

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

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