Recording/멋쟁이사자처럼 BE 13기

[멋쟁이사자처럼 부트캠프 TIL 회고] BE 13기_17일차_''Java IO"

LEFT 2024. 12. 24. 17:45

🦁멋쟁이사자처럼 백엔드 부트캠프 13기 🦁
TIL 회고 - [17]일차

🚀17차에는 입력과 출력을 담당하는 Java IO에 대해서 배웠다.

BufferedReader와 Scanner는 자주 사용해봤지만 BufferedReader사용 시 왜 예외처리를 해주어야하는지,

사용 이후에는 close()를 통해 닫아주어야하는 이유 등에 대해 생각해보지 못했다.

또 다양한 Stream객체가 있다는 것, 각자 다른 일들을 수행한다는 것 등 이번 회고를 통해 몰랐던 부분들을 정리할 수 있게되었다.


Java IO

  • 자바에서 입출력 담당
  • 텍스트문서 읽기, 인터넷에서 데이터 다운로드, 네트워크를 통한 데이터 전송 등

  • Input과 Output으로 입력과 출력을 담당하는데 이들은 “데이터가 이동할 통로”를 가짐
    이 통로는 “데이터의 흐름” = “Stream 스트림 (데이터의 흐름을 추상화)"

  • 스트림의 데이터타입은 같은 형태로 추상화되어있기때문에 String, int, 동영상, 이미지 등 어떤 것이 들어와도
    데이터타입에 상관없이 스트림으로 받아와 처리

  • 입력담당하는 스트림은 InputStream : 프로그램으로 데이터가 들어옴
  • 출력담당하는 스트림은 OutputStream : 프로그램을 기점으로 밖으로 흘러나감
  • Java IO는 입력을 위한 통로, 출력을 위한 통로를 “따로” 관리 (=같이 데이터가 흐르지 않는다.)

Java IO의 설계철학

  • OCP(Open-Closed Principle) : 클래는 확장에는 열려 있어야 하지만 변경에는 닫혀 있어야 한다.

  • 유연성 : 다양한 유형의 데이터 소스와 대상을 지원할 수 있도록 설계
    이는 프로그래머가 동일한 인터페이스를 사용하여 파일, 네트워크 연결, 메모리 내 데이터 등 데이터 읽고 쓰기 가능

  • 확장성 : 자바 IO는 쉽게 확장 가능, 새로운 유형의 데이터 소스, 사용자 정의 입출력을 가능하게함

  • 재사용가능성 : 일관된 입출력 관련 클래스 설계로 코드 재사용 가능
    (ex. 데코레이터 패턴에서는 목적지/근원지를 가지는 주인공 클래스와 “기능을 가진 클래스”인 장식 클래스가 있다.) 이 장식 클래스만 갈아끼우는 방식으로 재사용이 가능
  • 주인공 클래스 : 데이터를 어디에 쓸지, 어디로부터 데이터를 읽어올 것인지의 “목적지/근원지”를 가짐
  • 장식 클래스 : 주인공 클래스를 꾸며줄, 편리하게 해줄 기능을 가짐

Java IO와 Decorator 패턴

  • Java.io는 데코레이터 패턴으로 만들어져있음.
    ex. 파일에서 데이터를 읽어오는 스트림(Stream)에 기능을 더하는 데코레이터(Decorator)를 사용

  • 한 객체를 여러 개의 데코레이터로 감쌀 수 있다.
    데코레이터 사용으로 서브클래스를 만드는 것보다 유연하게 기능을 확장

Java IO의 4가지 추상클래스

  • 자바 IO API의 클래스들 중 InputStream, OutputStream, Reader, Writer 등이 붙은 클래스들이 추상클래스
  • 자바 초기 Byte단위의 데이터를 다루는 InputStream, OutputStream만 존재
    ➡️ 초기에는 1바이트만 데이터를 처리하면 됐기때문에 이러한 클래스들이 사용됨
  • 하지만 한국어/중국어/일본어 등의 다양한 외국어를 처리하기 위해서는
    Byte단위로 처리하는 InputStream, OutputStream에서는 Character단위로 묶어서 사용자에게 보여줄때 문자가 깨지게됨
    ➡️이를 해결하기위해 등장한 추상클래스 Reader(읽기), Writer(쓰기)

  • 이 추상클래스들은 Byte단위의 입출력을 다루는 InputStream, OutputStream 클래스와 달리
    Character 단위의 입출력을 다루는 Reader, Writer 클래스

Java IO API - Classes


BufferedInputStream API

BufferedInputStream API - Constructors

  • "주인공 클래스""장식 클래스"를 구분짓는 쉬운 방법은 해당 클래스의 생성자 부분을 확인하면 된다.
  • 예를 들어 BufferedInputStream은 Buffer라는 “기능”을 갖는다.
    → 한 줄씩 입출력할때 “한 줄의 데이터”를 갖고 있을 공간을 Buffer가 제공해줌
    → read(), readLine() 등의 메소드도 제공
  • ⭐따라서 Buffered가 붙어있는 클래스들은 “장식클래스"

ByteArrayInputStream API

  • 4가지의 추상클래스를 생성자로 받고 있지 않다.
  • 혼자 쓰일 수 있는 "주인공 클래스"인 것

CharArrayInputStream API

  • 4가지의 추상클래스를 생성자로 받고 있지 않으므로 "주인공 클래스"

DataInputStream API

  • InputStream타입의 in을 매개변수로 받으므로 “장식클래스"
  • 이는 반드시 IO객체가 들어와서 사용되어야한다는 것

FileInputStream API 

  • 생성자로 File이라는 객체를 가지는 “주인공 클래스"

❓주인공 클래스가 하는 일 

  • 어디에서 읽어 들일 것인가, 어디에다 쓸 것인가를 결정
  • FileInputStream – 파일로”부터” 읽어들인다.
  • ByteArrayOutputStream – “바이트배열에” 저장한다.
  • 주인공클래스는 간단한 메소드만 가지고 있다.

❓장식 클래스가 하는 일

  • 주인공 클래스가 사용할 유용한 기능을 제공한다.
  • DataInputStream은 readDouble(), readFloat(), readBoolean(), readInt() 등의 메소드를 제공하고 있다.
  • 이처럼 장식 클래스는 다양한 메소드를 가짐

InputStream 관련 클래스

  • FileInputStream : 파일 시스템에 저장된 데이터를 읽음
  • ByteArrayInputStream : 메모리에 저장된 바이트 배열로부터 데이터를 읽음
  • BufferedInputStream : 버퍼링을 사용하여 입력의 효율을 높임
    → 데이터를 일정량 모아두었다가 한번에 처리 (여러번 연산보다 효율적)
  • DataInputStream : 기본 데이터 타입(int, long, float 등)“바이트 스트림”으로부터 직접 읽을 수 있게함

OutputStream 관련 클래스

  • FileOutputStream : 파일 시스템에 “바이트 단위로”로 데이터를 씀
  • ByteArrayOutputStream : 바이트 배열에 데이터를 씀
  • BufferedOutputStream : 버퍼링을 사용하여 출력 효율을 높임
  • DataOutputStream : 기본 데이터 타입을 “바이트 스트림”에 쉽게 쓸 수 있게함

▶️실습 - BufferedReader

BufferedReader의 매개변수

  • BufferedReader의 매개변수로 Reader타입이 들어와야한다고 표시
  • 이를 해결하기 위해 InputStreamReader를 사용해야함

Reader API - Constructors

  • 이 클래스는 Reader이지만 생성자에가보면 InputStream을 받고 있음
  • InputStreamReader : C타입 변환기, 젠더 등의 역할을 하는 클래스

InputStream → InputStreamReader → Reader

  • 이러한 구조에서 가운데 다리 역할, 즉 젠더의 역할을 하는 것

InputStreamReader의 매개변수

  • 젠더 역할의 InputStreamReader를 넣어주면
    이번에는 이 InputStreamReader가 InputStream타입을 매개변수로 가져야한다고 표시
  • 데이터를 입력을 위해 Scanner클래스에서도 사용했던 System.in을 사용

System.in

  • System.in : 키보드와 연결된 System 클래스의 InputSteam 타입의 static final 상수를 의미

System class API

  • in의 설명을 자세히 보면 “표준” 입력 스트림이고, 이미 열려있으며, 입력데이터를 “제공할 준비가 되어있다”고 한다.
  • 따라서 System클래스의 in 필드를 직접 객체로 참조하여 넣을 수 있다. → System.in
// 1번방법. 한 줄 입력받기
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

// 2번방법. 한 줄 입력받기
InputStream is = System.in;
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
  • 두 방법 모두 같은 기능을 수행
  • 2번 방법처럼 각 클래스들을 선언하고 매개변수로 넣어주었던 부분을
  • 1번 방법처럼 간편하게 사용할 수 있는 것

❓BufferedReader의 예외처리

➡️readLine() 메소드를 사용할때 Exception처리를 해주어야한다는 에러가 발생하는데 throws로 Exception을 넘긴다.
InputStream==null 일때를 대비해 미리 예외를 처리하는 것이다.

public static void main(String[] args) throws Exception{
    BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    String str = br.readLine();
}
  • 이렇게 throws로 Exception을 JVM에게 예외를 넘긴다.

▶️실습 - 파일로부터 입력받기

  • BufferedReader를 통해 파일로부터 “한줄씩 입력받기”를 구현 → 키보드가 근원지
  • FileInputStream를 통해 “파일로부터”를 구현

  • FileInputStream생성자는 IO가 아닌 File 등으로 입력받고 있기때문에 “주인공 클래스"
  • 따라서 자체적으로 생성이 가능
FileInputStream fis = new FileInputStream("src/day12/IOExam01.java");
  • 이처럼 BufferedReader에서는 new InputStreamReader를 통해 꾸며주어서 동작을 했다면
  • FileInputStream은 자체적으로 생성자를 가지고, 파일 경로명을 받고 있는 것을 확인가능
// 1번방법. 파일로부터 한 줄 입력받기
FileInputStream fis = new FileInputStream("src/day12.io/IOExam01.java");
InputStreamReader isr = new InputStreamReader(fis);
BufferedReader br2 = new BufferedReader(isr);

// 2번방법. 파일로부터 한 줄 입력받기
FileInputStream fis2= new FileInputStream("src/day12.io/IOExam01.java");
BufferedReader br3 = new BufferedReader(new InputStreamReader(fis));
  • 두 방법 모두 같은 기능을 수행
  • ➡️ BufferedReader는 Reader만 받아들일 수 있으므로
    FileInputStream이 직접 들어가지 못함 따라서 InputStream을 Reader로 바꿔주는 “커넥트 = 젠더”가 필요
  •  InputStreamReader 클래스가 "젠더 역할"을 함

▶️실습 - Hello.txt 파일을 읽어오기

 // HelloIO.txt 읽어오기
        FileInputStream fis3= new FileInputStream("src/HelloIO.txt");
        BufferedReader br4 = new BufferedReader(new InputStreamReader(fis3));
        System.out.println("데이터를 읽어왔습니다 - " + br4.readLine()); // 1줄 읽어옴
        System.out.println("데이터를 읽어왔습니다 - " + br4.readLine()); // 1줄 읽어옴

▶️실습 - 키보드로부터 5줄 입력받아서 콘솔에 출력

public static void main(String[] args) throws Exception{
    BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    String[] strArr = new String[5];

    for(int i = 0; i < strArr.length; i++){
        System.out.print("5명 중 한 학생의 이름을 입력해주세요 : ");
        strArr[i] = br.readLine();
    }

    System.out.println(Arrays.toString(strArr));
}

▶️실습 - 키보드로부터 입력받은 데이터를 “파일에 출력"

  • FileWriter만 사용
  • 👀파일을 열면 반드시 닫아주어야한다. ➡️close()
  • 닫아주지 않으면 파일에는 아무것도 써지지 않는다.
  • 운영체제에서는 버퍼를 데이터로 가득 채워야 데이터를 전달하러간다.
    데이터를 하나 받고 바로 전달하고 하나 받고 바로 전달하는 것은 비효율적이기때문이다.
  • 따라서 데이터가 가득 찰때까지 운영체제는 기다리는데 close()를 해주지 않고
    데이터도 덜 채워진 상태이면 데이터를 전달해주지 않기때문에 남아있는 공간에 상관없이
  • 운영체제에게 버퍼에 데이터를 전달해달라는 명령의 동작을하는 것이 close()
public static void main(String[] args) throws Exception{
    FileWriter fw = new FileWriter("src/sample/FileWriterTest.txt");
    BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

    System.out.println("파일에 쓸 내용을 입력하세요.");
    for(int i = 0; i < 5; i++){
        System.out.print("[" + (i+1) + "줄] : ");
        fw.write(br.readLine() + "\\n");
    }

    br.close();
    fw.close();
}

  • FileWriter클래스만 사용하여 실습해보았다.

PrintWriter

  • FileWriter클래스와 달리 동일한 출력을 하지만 편리한 기능을 제공하는 클래스이다.
  • 처음에는 "장식 클래스"로 등장했지만 자주 쓰이다보니 "주인공 클래스"로도 사용할 수 있도록 되어있다.

public static void main(String[] args) throws Exception{
    FileWriter fw = new FileWriter("src/sample/FileWriterTest.txt");
    PrintWriter pw = new PrintWriter(fw);

    BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

    System.out.println("파일에 쓸 내용을 입력하세요.");
    for(int i = 0; i < 5; i++){
        System.out.print("[" + (i+1) + "줄] : ");
        pw.println(br.readLine());
    }

    br.close();
    fw.close();
    pw.close();
}

  • FileWriter 실습 코드에서는 데이터를 입력할때마다 “\n” 줄바꿈을 명시적으로 지정해주었었는데
  • PrintWriter는 출력의 기능을 잘 구현하고 있어 println()메소드로 인해 자동으로 줄바꿈이 되는 것을 확인 가능

▶️실습 - close() ➡️ try-with-resources 사용

public static void main(String[] args) {
    try(
        FileWriter fw = new FileWriter("src/sample/FileWriterTest.txt");
        PrintWriter pw = new PrintWriter(fw);
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    ){
        System.out.println("파일에 쓸 내용을 입력하세요.");
        for(int i = 0; i < 5; i++){
            System.out.print("[" + (i+1) + "줄] : ");
            pw.println(br.readLine());
        }    
    }catch(Exception e){
        e.printStackTrace();
    }
}
  • 자동 리소스 닫기 기능을 이용하여 throws Exception을 넘겨주지 않고
  • try() 블록 안에 닫을 객체들을 담아두고, { 실행할 문장 }과 catch로 Exception을 작성하면
  • close()로 닫아주지 않아도 try-with-resources로 대체가능

▶️실습 - 파일로부터 입력받아서 파일에서 출력하기

public static void main(String[] args) throws Exception {
    try(
            FileInputStream fis = new FileInputStream("src/sample/FileInputStream.txt");
            FileOutputStream fos = new FileOutputStream("src/sample/FileOutputStream.txt");
        ){
        int readData = 0;
        while((readData = fis.read()) != -1){ // EOF : End Of File 파일이 끝나면 -1값을 리턴하게됨.
            fos.write(readData);
        }

        if(readData == -1){
            System.out.println("모든 데이터를 옮겼습니다!");
        }

    }catch(Exception e){
        throw new RuntimeException(e);
    }
}

  • FileInputStream으로 만들어진 파일로부터 데이터를 읽음
  • 모든 데이터가 옮겨졌으면, readData는 -1으로 갱신되어있기때문에 조건문을 통해 출력을 하게된다.
  • read() : int형을 반환하기때문에 (값이 없으면 -1) readData 변수를 만들어
    그 변수를 통해 FileInputStream으로부터 읽어온 값이 있으면
  • FileOutputStream의 write() 메소드로 그 값을 쓴다.
    readData 변수는 “각 문자의 코드값”이 쓰여진다. (ASCII코드값)
  • char타입으로 형변환해보면 “문자”들이 쓰여있는것을 확인할 수도 있다.

각 문자들을 출력해본 “예시”

  • 값을 잘 읽어들여서 FileOutputStream.txt 에 써진 것을 확인 가능

❓FileInputStream 예외처리 해주는 이유

FileInputStream API

  • API를 보면 throws FileNotFoundException처럼 예외를 넘기고 있다.
  • 따라서 예외처리를 구현 → try-with-resources 로 구현가능

버퍼 Buffer

byte[] buffer = new byte[1024];
  • 파일의 크기만큼 버퍼를 생성해도 운영체제가 담을 수 있는 버퍼의 공간은 한정되어있다.
    따라서 (512, 1024, … 등)의 크기를 지정해야함

❓버퍼를 사용하는 이유

➡️하드디스크에 자주 접근하는 것을 방지하여 속도를 향상시킴

❓512, 1024 등의 숫자를 사용하는 이유

➡️컴퓨터는 계산할때 2진수를 사용하는데 1바이트는 8비트로 10진수로 변환하면 "256"이 된다.
이렇게 컴퓨터는 2진수로 계산하는 것이 빠르기때문에 모든 숫자를 2진수로 관리한다.

➡️ 1KB = 1024바이트이다. 1KB(킬로바이트)를 1000바이트가 아닌 2의 10제곱인 1024바이트로 사용하기때문에
컴퓨터가 1024바이트의 공간을 사용할 수 있음에도 1000바이트만 선언하게된다면
나머지 24바이트의 공간이 낭비되므로 1024를 사용하여 속도를 조금 더 빠르게 개선하는 것

▶️ 실습 - 버퍼 사용

public static void main(String[] args) throws Exception {
    try(
           FileInputStream fis = new FileInputStream("src/sample/fis_Buffer.txt");
           FileOutputStream fos = new FileOutputStream("src/sample/fos_Buffer.txt");
        ){
        byte[] buffer = new byte[1024];
        int readData = 0;
        int bufferIndex = 0;
        while((readData = fis.read(buffer)) != -1){ // EOF : End Of File 파일이 끝나면 -1값을 리턴하게됨.
            fos.write(buffer, 0, readData); // 버퍼를 쓸때 0번부터 시작해서 readData 까지 읽는다.
            bufferIndex++;
        }
        
        System.out.println("[buffer] 데이터를 읽었습니다. : 버퍼의 크기 [" + bufferIndex + "]");

        if(readData == -1){
            System.out.println("모든 데이터를 옮겼습니다!");
        }

    }catch(Exception e){
        throw new RuntimeException(e);
    }
}
  • 문자를 한 글자씩 옮기는 것보다 버퍼로 옮겨서 하는것이 “속도 개선”에 도움

  • fos.write(buffer, off, len) : FileOutputStream의 write()를 사용하여
    byte[]배열의 버퍼와 시작시점, 데이터의 길이를 매개변수로 넣어준다.
  • readData는 "-1"이 아닌 이상 데이터들을 받아와서 그 길이만큼 버퍼 배열에 쓰게 된다.

  • fis_Buffer.txt → fos_Buffer.txt 로 잘 옮겨진 것을 확인할 수 있다.
FileInputStream fis = new FileInputStream("src/day12/io/IOExam01.java");
FileOutputStream fos = new FileOutputStream("src/sample/fos_Exam01Buffer.txt");
  • 버퍼의 크기가 잘 출력되는지 확인하기 위해 FileInputStream의 경로를 IOExam01.java 파일로 바꾼다.

  • 이처럼 java파일의 코드들이 txt파일로 잘 옮겨진 것을 확인할 수 있다.

문자 기반 스트림 Character

  • 문자(char) 단위 처리 : 어플리케이션 주로 사용
  • 인코딩 호환성 : 다양한 문자 인코딩 지원
  • 텍스트 중심 : 텍스트 파일, 문자열 데이터 처리에 적합 → HTML, XML, JSON파일 처리 등
  • Reader, Writer클래스

Reader 클래스

  • FileReader : 파일로부터 텍스트 데이터 읽기
  • StringReader : 문자열로부터 텍스트 읽기
  • BufferedReader: 버퍼링을 통해 효율적인 읽기 지원
  • InputStreamReader : byte스트림을 문자 스트림으로 변환하는 “젠더 / 다리” 역할

Writer 클래스

  • FileWirter : 파일에 텍스트 데이터 쓰기
  • StringWriter : 문자열에 텍스트 데이터 쓰기
  • BufferedWriter : 버퍼링을 통해 효율적인 쓰기 지원
  • OutputStreamWriter : 문자 스트림을 byte스트림으로 변환하는 “젠더 / 다리” 역할

특수 IO

  • System.in : 표준 입력 스트림, 콘솔로 사용자 입력받기, InputStream형태로 제공
  • System.out : 표준 출력 스트림, 콘솔에 데이터 출력, PrintStream클래스의 인스턴스
  • System.err : 표준 오류 출력 스트림, 콘솔에 오류/경고 출력, PrintStream클래스의 인스턴스
  • System클래스에서 모두 표준 입출력 및 오류 출력을 다루는 정적멤버들 → 콘솔 기반 입출력 작업에 사용

보조 스트림 Decorative Streams

  • 기본 스트림에 추가적인 기능을 제공
  • 데이터를 필터링하거나 버퍼링, 문자와 바이트간 변환, 객체 직렬화 등 다양한 기능 수행

Buffered Streams (버퍼링 스트림)

  • BufferedReader, BufferedWriter : 내부적으로 버퍼를 사용해 입출력 효율을 높임
  • BufferedInputStream, BufferedOutputStream

Data Streams (데이터 스트림) - DataInputStream, DataOutputStream

Object Streams (객체 스트림)

  • ObjectInputStream, ObjectOutputStream
  • 객체 직렬화역직렬화를 위해 사용
  • 객체 직렬화/역직렬화 : 객체 → 바이트 형태 변환 혹은 바이트 → 객체 형태로 복원 가능

Print Streams (프린트 스트림) - PrintWriter, PrintStream

Character Streams to Byte Streams

  • 문자 스트림과 바이트 스트림 변환
  • InputStreamReader와 OutputStreamWriter 바이트 스트림과 문자 스트림 간의 변환을 위한 “젠더” 역할

▶️실습 - DataOutputStream (쓰기)

  • 컴퓨터가 이해하고, 사용자가 이해할 수 없는 문자로 쓰여진다.
public static void main(String[] args) {
    // 기본 데이터 타입으로 파일에 쓰기
    // 사용자를 위한 목적이 아닌 컴퓨터를 위한 목적으로 쓰여짐 (=사용자가 이해할 수 없는 문자로 쓰여짐)
    try(
    DataOutputStream dos = new DataOutputStream(new FileOutputStream("src/sample/DataExam.txt"));
    ){
        // 기본 데이터 타입을 쓰기 위한 샘플 데이터
        dos.writeBoolean(true);
        dos.writeByte(Byte.MAX_VALUE);
        dos.writeChar('a');
        dos.writeDouble(3.82);

        System.out.println("데이터가 쓰여졌습니다!");
    }catch(Exception e){
        throw new RuntimeException(e);
    }
}

실행결과 - 컴퓨터문자

▶️실습 - DataInputStream (읽기)

public static void main(String[] args) {
    // 기본 데이터 타입으로 파일을 읽기
    // 사용자를 위한 목적이 아닌 컴퓨터를 위한 목적으로 읽어봄 (=사용자가 이해할 수 없는 문자)
    try(
            DataInputStream dis = new DataInputStream(new FileInputStream("src/sample/DataExam.txt"));
    ){
        // 기본 데이터 타입을 읽기 위한 샘플 데이터
        if(dis.readBoolean()){
            System.out.println("true입니다.");
        }
        // 꺼낼때는 DataOutputStream에서 저장한 순서대로 꺼내야함
        byte b = dis.readByte();
        char c = dis.readChar();
        double d = dis.readDouble();
        System.out.println("byte : " + b + ", char : " + c + ", double : " + d);

        System.out.println("데이터를 모두 읽어왔습니다!");
    }catch(Exception e){
        throw new RuntimeException(e);
    }
}

  • 꺼낼때는 DataOutputStream에서 썼던 타입의 순서대로 읽어들여야한다.

▶️실습 - BufferedReader와 PrintWriter 사용

public static void main(String[] args) {
    try(
    BufferedReader reader = new BufferedReader(new FileReader("src/sample/input.txt"));
    PrintWriter writer = new PrintWriter(new FileWriter("src/sample/output.txt"))
    ){
        String dataLine;
        while( (dataLine = reader.readLine()) != null){
            writer.println(dataLine);
        }
    }catch(IOException e){
        e.printStackTrace();
    }
}
  • BufferedReader를 사용해서 한 줄을 입력받게 하고, 그 매개변수로 FileReader 인스턴스를 만든다.
  • PrintWriter로 출력을 편리하게 하고, 그 매개변수로 FileWriter를 받는다.
  • try-with-resources를 통해 자동 리소스 닫기를 수행
  • String dataLine : 입력받을 데이터가 없으면 null로 반환
    ↔ 이는 FileInputStream 실습에서 readData를 통해 ASCII코드를 입력받을때
    -1이 데이터가 없는 것을 의미했던 것과는 조금 다르다.

File

  • File클래스는 File을 추상화한 객체
public static void main(String[] args) {
    File file = new File("FileTest.txt");
    System.out.println(file.exists()); // 파일이 존재하는지 여부 출력
    try {
        if (!file.exists()) {
            file.createNewFile();
            System.out.println("새로운 파일이 생성되었습니다." + file.getAbsolutePath());
        }
        // File객체를 통해서 파일 정보를 확인할 수 있음
        System.out.println("파일 이름 : " + file.getName());
        System.out.println("파일 크기 : " + file.length() + "bytes");
        System.out.println("읽기 가능 : " + file.canRead());
        System.out.println("쓰기 가능 : " + file.canWrite());
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}
  • 파일에 관련하여 유용한 메소드를 제공

첫번째 실행 결과
두번째 실행결과

  • 두번째 실행에서는 파일이 존재하는지 여부를 통해 true를 반환

File 디렉토리 관련 메소드

System.out.println(System.getProperty("user.dir"));

  • 현재 있는 디렉토리를 출력한다.
File dir = new File(".");
System.out.println(dir.isDirectory());

String[] files = dir.list();
for(String f : files){
	System.out.println(f);
}
  • “.” : 현재 디렉토리를 의미한다.
  • 새로운 파일을 현재 디렉토리 경로로 만들어내고,
  • isDirectory() : 현재있는 곳이 디렉토리인지 여부를 boolean으로 타입
  • 👀이를 활용해 하위 디렉토리들의 파일 정보를 출력하는 것도 만들 수 있다.

  • dir.list() : 이 디렉토리의 파일들을 String타입의 배열에 넣고 출력확인

❓이처럼 경로를 객체로 추상화 시켜서 사용하는 이유

➡️Java IO에서 인스턴스 생성 시 src 경로를 직접 쓰다보면 오타로 인한 오류가 발생할 수 있는데
이처럼 파일을 객체로 추상화시켜서 그 객체를 인자로 넣어주면 그러한 경우를 방지할 수 있음


객체직렬화

  • 물체를 분해해서 원소들로 직렬화 (줄 세우기)하는 것을 의미
  • 객체를 파일에 저장
  • serialization, serializable ➡️ implements를 해주어야하는 경우가 있다.
  • Person객체를 만들어서 활용
  • ObjectInputStream의 readObject()메소드를 활용
  • ObjectOutputStream의 writeObject()메소드를 사용
// 객체 선언
Person person = new Person("Isak", 29);

// 객체를 파일에 저장 - 객체 직렬화
try(
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("src/sample/person.txt"));
){
    out.writeObject(person);
}catch (Exception e){
    throw new RuntimeException(e);
}
System.out.println("객체 -> 파일 [직렬화] 완료!");
  • 객체를 전송하고자할때는 객체 직렬화가 이루어져야함
  • 따라서 객체 직렬화를 위해서는 Person객체가 implements Serializable 해야함
    (=객체직렬화를 할 것이라고 선언 = “마크업 인터페이스”라고 함)
  • 그렇지 않은 경우 오류가 발생한다. → java.io.NotSerializableException

마크업 인터페이스를 수행한 후에 다시 실행해보면

implements Serializable 후 실행결과


객체 역직렬화

  • 객체직렬화를 통해 객체를 파일로 직렬화해서 보냈다면
  • 다시 객체역직렬화를 하여 파일로부터 객체를 가져올 수도 있다.
// 객체를 파일로부터 읽어오기 - 객체 역직렬화
Person readPerson = null;
try(
ObjectInputStream in = new ObjectInputStream(new FileInputStream("src/sample/person.txt")); // 확장자는 지정해주지 않아도된다.
){
    readPerson = (Person)in.readObject(); // Person타입으로 형변환해주어야함 (Object로 저장되어있었으므로)
}catch(Exception e){
    throw new RuntimeException(e);
}

System.out.println("파일 -> 객체 [역직렬화 결과] : " + readPerson);


URL 클래스

  • 웹에서 입력받아서 사용하고자할때 활용
  • URL이라는 객체로 추상화된 것을 활용 → java.net.URL 패키지
URL url = new URL("<https://www.youtube.com/>");

  • openStream() : 웹페이지의 정보를 InputStream타입으로 얻어올 수 있다.
  • throws Exception으로 예외처리를 넘긴다.
public static void main(String[] args) throws Exception {
    URL url = new URL("<https://www.youtube.com/>");

    // 한 줄을 읽기 위해 BufferedReader
    // 웹에서 InputStream을 읽기위한 url.openStream()
    BufferedReader br = new BufferedReader(new InputStreamReader(url.openStream()));

    String webline = null;
    while( (webline = br.readLine()) != null){
        System.out.println(webline);
    }
}

  • 해당 페이지의 HTML 정보를 텍스트로 입력받아 가져온다.
  • 👀이를 통해 크롤링을 구현할 수도 있을 것이다.

💡수업을 들으면서 빠르게 이해가 가지 않았던 부분이나 다시 알고 싶었던 부분은 실습코드와 IntelliJ의 실행결과를 캡처해놓고 회고를 진행하면서 이해하고 있다.

내가 이해한 흐름대로 회고를 작성하다보니 글이 길어져도 시간은 빠르게 지나가는 것 같다!

2시간으로 그 날의 모든 내용을 완벽히 습득하긴 어렵지만 나만의 스타일로 정리하다보니 이해에 큰 도움이 되는 것 같다.

Java IO파트도 이전에 개인적으로 강의를 보며 공부할때는 이해 하지 못한 부분이 꽤 많았는데 오늘 회고를 통해 Java IO 내용을 체득할 수 있었다. 🚀