본문 바로가기

카테고리 없음

[ Java ] 직렬화 개념 맛보기, I/O 클래스

Java에서 객체 직렬화(Object Serialization)

객체 직렬화가 필요한 경우는 네트워크로 객체를 전송하고, 수신할 때 그리고 객체들을 데이터베이스에 저장할 때 등이 있다.

프로그램을 실행하는 동안 클래스를 통해 객체가 생성되고(인스턴스) 해당 객체에는 수 많은 정보가 변수들에 저장되어 있을 것이다.

그런데 프로그램을 실행하는 동안 객체가 소멸되기도 하고, 프로그램이 종료되면 사라진다. 즉 인스턴스 자체만으로는 정보를 계속 보관하는 영속성이 없다. 그래서 보관되어야할 정보라면, 파일에 기록하거나 데이터베이스 관리 시스템에 저장 등을 한다. 그리고 프로그램을 다시 시작했을 때 파일에 기록된 내용을 읽어오거나 데이터베이스 관리 시스템에 원하는 정보를 요청해서 응답 받거나 등을 통해 객체에 저장된 정보들의 영속성을 지원하는 것이다. 프로그램이 실행되는 동안 유지하고 있던 객체 내부의 정보와 객체 자신의 정보를 어디엔가 기록해 놓고, 나중에 필요한 시기에 다시 볼 수 있는 방법을 제공하는 것이 객체 직렬화다. 그리고 직렬화는 java.io.Serializable 인터페이스를 구현한 클래스를 통해 생성된 객체에서 가능한 것이다. java.io.Serializable 인터페이스를 구현한 클래스로부터 생성된 객체가 직렬화가 가능하다. 

 

직렬화의 과정은 (객체 상태를 나타내는) 인스턴스 변수의 값을 저장하는 일이다. 

 

보니까 직렬화가 InputStream과 OutputStream이랑 관련이 높은 것 같다. Stream부터 다시 정리해봐야할 것 같음.

 

Stream이란

***stream: 개울, 시내(강과 비교하여 강보다 작은); (액체·기체의)줄기, 흐름, 예를 들어 상처에서 피가 '한 줄기' 흘러내렸다 할 때,

a stream of blood 이런 표현을 쓴다; (사람·차량들로 계속 이어진) 줄 [흐름]; (수많은 일의) 연속[이어짐]; 컴퓨터 분야에서 '스트림 처리하다'의 용어로 사용되고, 데이터 전송을 연속적으로 이어서하다 라는 의미다.

 

stream은 데이터의 흐름(흐르는 통로), 데이터를 전송/수신할 때 연속적으로 연이어서 흐르는 데이터의 양상을 의미하는 것 같다.

- 외부에서? 데이터가 들어오면 입력스트림(사용자가 키보드로 데이터를 입력하면, 입력된 데이터가 프로그램으로 들어오는 경우), 프로그램에서 데이터가 나가면 출력스트림을 이용한다(모니터에 콘솔내용을 출력한다던가 프로그램내에서 입력받은 내용을 파일에 기록하는 경우)? 프로그램을 기준으로 외부로부터 입력받은 데이터를 읽어오고, 프로그램에서 생성된 데이터를 (외부의) 파일에 기록할 때 스트림이 필요하고, 이런 스트림을 사용할 수 있게 스트림 문법을 지원하는 클래스가 있다.

 

스트림 클래스는 byte기반과 문자기반 크게 2가지로 나뉜다.

바이트 기반 스트림은 그림, 문자 등 모든 종류의 데이터를 보내거나 받을 수 있다

→ 1byte단위로 잘게 쪼개서 전송 및 수신한다. 이렇게 잘게 쪼개져 작업이 수행되면 원본의 손상을 방지할 수 있다.

문자 기반 스트림은 오직 문자만 전송하고, 수신하는 것에 특화되어 있다 → 2byte 단위

 

바이트 기반의 입/출력 스트림의 최상위 클래스는 InputStream과 OutputStream이 있고,

문자 기반의 입/출력 스트림의 최상위 클래스는 Reader와 Writer가 있다.

이 추상클래스들을 구현한 클래스들로부터 생성된 객체는 스트림 문법을 사용할 수 있다.

 

- FileOutputStream 실습: 사용자로부터 입력받은 데이터를 파일에 기록하기(프로그램 → 파일)

package etc.api.io.stream;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Scanner;

public class OutputStreamEx {
    public static void main(String[] args) {
        /*
        * 1. 프로그램에서 생성된 객체가 갖는 내용을 파일에 기록하기 위해 사용하는 클래스는 FileOutputStream이다.
        * 2. FileOutputStream 생성자의 매개값으로 파일을 쓸 전체 경로를 지정한다.
        * 3. 입출력과 관련된 클래스가 모여있는 io패키지의 모든 클래스들은 생성자에 throws 키워드가 있기 때문에
        * try ~ catch 블록을 항상 작성해주어야 한다.
        * */

        // 입력받은 내용을 파일에 기록하기 위해
        // 사용자로부터 입력을 받을 수 있게 Scanner객체 생성하기
        Scanner sc = new Scanner(System.in);
        System.out.print("파일명을 입력하세요: ");
        String fileName = sc.next();


        FileOutputStream fos = null;
        try{
            // 기록한 내용을 담은 파일이 어디에 위치해야하는지, 파일위치의 전체경로를 생성자 매개값에 작성하기
            // FileNotFoundException 예외를 처리해 주어야 한다.
            fos = new FileOutputStream("C://workspace//"+fileName+".txt");
            System.out.print("문장을 입력하세요: ");
            sc.nextLine(); // 파일명 작성후 엔터친 값을(\n) 처리
            String text = sc.nextLine();

            // 입력받은 문자를 byte단위로 쪼개야한다.
            byte[] arr = text.getBytes(); // 문자열 데이터를 바이트 데이터로 변환


            // 바이트 단위로 변환된 데이터를 파일에 바이트 단위로 기록한다.
            // write() 메서드가 던지는 예외 IOException을 처리해주어야 한다.
            fos.write(arr);

            System.out.println("파일이 정상적으로 저장되었습니다.");

        }catch(Exception e) { // Exception이 FileNotFoundException과 IOException을 처리해준다.
            e.printStackTrace();

        }finally{
            // finally는 선택사항
            // 예외의 발생 유무에 상관하지 않고, 무조건 실행한다.
            // 스트림을 더 이상 사용하지 않는 경우 시스템 자원을 반납하는 코드를 작성해주어야 한다.
            // --> 메모리 누수를 방지하기 위함

            try{
                // close() 메서드가 던지는 IOException을 예외처리해주어야 한다.
                fos.close();
                sc.close();
            }catch(IOException e) {
                e.printStackTrace();
            }

        }

    }
}

 

- FileInputStream 실습: 파일에 저장된 데이터를 콘솔창에 찍기(프로그램 ← 파일)

***FileInputStream은 추상클래스인 InputStream을 상속, InputStream은 Closeable 인터페이스를 구현, Closeable 인터페이스는 AutoCloseable 인터페이스를 상속하기 때문에 AutoCloseable의 기능을 사용할 수 있는 FileInputStream은 try-with-resources 문법을 사용하여 auto close를 진행할 수 있다 (자바 8버전 이후로 사용이 가능함)

close() 하려는 객체가 AutoCloseable 인터페이스와 구현 관계여야 한다.

package etc.api.io.stream;

import java.io.FileInputStream;
import java.io.IOException;

public class InputStreamEx {
    public static void main(String[] args) {
        /*
        *
        * 1. 파일을 읽어들이는데 사용하는 클래스는 FileInputStream
        * 2. 읽어들일 파일의 전체 경로를 생성자의 매개값으로 전달한다.
        * 3. InputStream 객체는 생성자에 throws가 있기 때문에 예외처리를 진행해야 한다.
        *
        * */

        // FileInputStream fis = null;

        // try-with-resources 문법을 사용하여 auto close를 진행할 수 있다 (자바 8버전 이후로 사용이 가능함)
        // close() 하려는 객체가 AutoCloseable 인터페이스와 구현 관계여야 한다.
        try(FileInputStream fis = new FileInputStream("C://workspace//가나요.txt")){

            // byte단위로 데이터를 읽어들이는 기능을 갖고 있는 FileInputStream
            int data = 0;

            while(data != -1) {
                data = fis.read(); // 1byte단위로 데이터를 읽어들이는 read()
                System.out.print((char)data);
                // if(data == -1) break; 읽어들일 데이터가 더이상 없다면 반복문을 빠져나간다.

            }

        }catch(Exception e) {
            e.printStackTrace();
        } /*finally{
            try{
                fis.close();
            }catch(IOException e){
                e.printStackTrace();
            }
        }*/
    }
}

 

- 데이터를 읽어들임과 동시에 복사하기 실습

package etc.api.io;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

public class StreamCopy {
    public static void main(String[] args) {

        // auto close할 객체를 두 개 선언할 수도 있다. 세미콜론으로 구분해 준다.
        try (FileInputStream fis = new FileInputStream("C://Users//82103//Desktop//sister.png");
             FileOutputStream fos = new FileOutputStream("C://workspace//copy.png")) {

            // 1. 이미지를 먼저 읽는다.
            int data = 0;

            while (data != -1) {
                data = fis.read(); // 1byte단위로 데이터를 읽어들임과 동시에
                fos.write(data); // 데이터를 작성한다.
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

 

try~with~resources 문법에 auto close할 객체를 2개 선언할 수도 있다는 것, 객체의 구분은 세미콜론으로 한다.

1byte단위로 데이터를 읽어들이는 FileInputStream은 문자가 아닌

이미지, 영상, 오디오 등의 파일을 읽어올 때 사용하는 것이 더 적합하다.

 

- 자바(프로그램)에서 외부 경로로 폴더를 생성하기

자바 프로그램에서 외부 경로로 폴더를 생성할 때는 java.io패키지에 있는 File클래스를 사용한다.

File 생성자의 매개 값으로 폴더를 생성할 경로 + 폴더명을 지정한다.

 

package etc.api.folder;

import java.io.File;

public class CreateFolder {
    public static void main(String[] args) {
        /*
        - 자바에서 외부 경로로 폴더를 생성할 때는 File클래스를 사용한다.
        - 생성자의 매개값으로 폴더를 생성할 경로 + 폴더명을 지정한다.
        *
        * */

        // workspace폴더에 아직 folder_test이 폴더가 없다.

        //**예를 들어 존재하지 않는 폴더를 여러 개 생성하려면
        // (현재 folder_test, test, myTest, apple 4개의 폴더가 모두 없어서 생성하려고 한다)
        // folder_test 폴더 안에 test 폴더 안에 myTest 폴더 안에 apple 폴더를 생성하고 싶다면,
        // 이렇게 여러 개의 폴더를 생성하려면 mkdir() 메서드로는 생성되지 않는다.
        // mkdir() 메서드는 하나의 폴더만 생성한다.

        File file = new File("C://workspace//testmmm//test//myTest//apple");

        // 현재 workspace 폴더에 folder_test 폴더의 존재 유무를 알려주는 exists() 메서드
        // --> 해당 디렉토리 경로가 존재한다면 true, 존재하지 않는다면 false를 리턴하는 exists()
        // workspace 폴더에 folder_test 폴더가 존재하지 않는다면 true를 리턴하도록 작성
        if (!file.exists()) { 
            
            // 폴더를 만들어줘라, mkdir()는 만들고자 하는 경로의
            // 상위 디렉토리가 존재하지 않는다면 동작하지 않는다.
            // file.mkdir(); 

            // 현재 존재하지 않는 경로의 폴더가 여러 개인 경우,
            // 그 폴더들을 만들려면 mkdirs() 메서드를 사용할 것.
            file.mkdirs(); // 폴더 생성 메서드, 지정한 경로의 모든 폴더를 생성해준다.
            System.out.println("폴더 생성이 완료되었습니다.");
        } else {
            System.out.println("이미 존재하는 경로입니다.");
        }

        //////////////////////////////////////////////////////////////////

        // 폴더 지우기
        // file객체에 저장되어있는 경로가 실존한다면 --> file.exists()
        if (file.exists()) {
            file.delete(); // apple 폴더 하나를 삭제하겠다.

            // 여러개의 폴더를 지우는 것이 직접하는 것이 낫다.
            System.out.println("폴더가 삭제되었습니다.");
        } else {
            System.out.println("현재 존재하지 않는 경로로 폴더 삭제가 실패하였습니다.");
        }
    }
}

 

 

- 파일 여러 개 삭제하기

```

File객체를 참조하는[가리키는] 참조변수 file이 갖고 있는 파일 경로가 존재한다면

 

// 자바 프로그램에서 외부의 경로에 파일을 만들 때 사용하는 File객체, File객체가 갖는 경로가 존재한다면

// File 객체 생성시 생성자의 매개 값으로 파일(폴더)을 만들 위치를 전달하기 때문에 File객체는 위치(경로)를 갖고 있다.

// file.exists()는 File객체가 갖는 경로가 실제로 존재한다면 true를 존재하지 않는다면 false를 리턴한다.

// 만약 여러 file객체가 갖는 경로가 여러 개의 폴더를 경유하고 있고,

// 그 여러 개의 폴더를 삭제해야 한다면 복잡하기 때문에 수동으로 삭제하는 것이 좋지만,

// 학습 차원에서 어떻게 접근하고 지워야하는지를 알아가보자. 

 

// 우선 file객체는 C드라이브의 workspace폴더 안에 testmmm 폴더 안에 test폴더 안에

// myTest 폴더 안에 apple이라는 폴더 경로를 갖고 있는 상태 --> "C://workspace//testmmm//test//myTest//apple"

 

// "C://workspace//testmmm//test//myTest//apple" --> 이 경로가 실제로 존재한다면

if(file.exists())  {

  // 해당 경로가 폴더인지 파일인지를 먼저 확인

  if(file.isDirectory()) {

    // 그리고 파일이 맞다면 파일들을 배열로 받는다.

    File[] files = files.listFiles();

 

    // 그리고 반복문으로 폴더를 삭제한다.

    for(File f: files) {

      f.delete();

    }

  }

}

```

폴더를 삭제할 때 해당 폴더에 내부 경로가 더 존재하거나 파일이 있는 경우에는 폴더를 삭제할 수 없다.

- listFiles()를 통해 파일 내부 경로 또는 파일명을 얻어와서 파일부터 삭제한 후에 폴더를 삭제할 수 있다.

- 지우고자 하는 경로가 여러 개라면 listFiles() 메서드를 통해 File객체를 여러 개 얻어온 후 반복문으로 하나하나 삭제한다.

 

- FileWriter로 데이터를 파일에 작성하기(2byte 기반으로 기록하기 때문에 글자가 깨지지 않는다)

package etc.api.io.rw;

import java.io.FileWriter;
import java.io.IOException;

public class FileWriterEx {
    public static void main(String[] args) {
        /*
         * 문자를 써서 저장할 때 사용하는 클래스는 FileWriter다.
         * 기본적으로 2바이트 단위로 처리하기 때문에 문자쓰기에 적합하다.
         *
         * */

        // auto close를 사용할 수 있는 FileWriter, try~with~resources
        // 문법을 사용하여 FileWriter객체 사용을 다하면 자동으로 반납하도록 작성한다.
        try (FileWriter fw = new FileWriter("C://workspace//merong.txt")) {
            // \r은 커서를 맨 앞으로 가져오고, \n은 줄바꿈을 진행한다.
            // \r은 캐리지 리턴: 커서를 맨 앞으로 가져오는 동작
            // 상황과 환경에 따라서 줄 개행시 커서를 맨 앞으로 가져오지 않고,
            // 커서가 그대로 내려오는 경우가 있기 때문에
            // \r\n을 함께 작성해주는 것이 좋다.
            String text = "오늘은 2024년 10월 14일 월요일 입니다.\r\n";
            // 지정한 파일 경로에 데이터를 작성해준다.
            fw.write(text);
            System.out.println("파일 저장 완료");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

 

***문자열 형식으로 파일 경로 작성시 \백슬러시는 탈출기호로 작용한다. 그래서 \백슬러시라는 특수기호를 문자로 표현한다는 것을 나타내기 위해 백슬러시를 \\두 개 작성해주면 된다.

 

- 문자열 기반으로 파일을 읽는 클래스 FileReader를 사용해서 파일의 데이터를 읽어들이기

// FileReader 클래스는 auto close가 가능하다.


// FileReader클래스 생성시 생성자의 매개 값으로 읽어들의 파일의 위치를 전달한다.
try(FileReader fr = new FileReader("C://workspace//merong.txt")) {
	
    // 파일에 작성되어 있는 데이터를 담을 변수를 선언
    int data = 0;
    
    // 그리고 파일의 데이터를 읽어들여 반복문으로 출력한다.
    // data에 담긴 값이 -1이 아니라면 --> 파일에 읽어들이 데이터가 하나도 없다면
    while(data != -1) {
    	data = fr.read();
        System.out.print((char)data);    
    }



// fr객체가 갖고 있는 파일의 위치를 찾지 못했을 때 발생하는 FileNotFoundException과
// 파일 내부의 데이터를 입출력할 때 발생하는 IOException 을 각각 예외 처리해주어도 되지만
// FileNoutFoundException 과 IOException의 조상인 Exception으로 예외처리해도 괜찮다.
} catch (Exception e) {
	e.printStackTrace();



}

 

FileReader객체에 저장되어 있는 파일 경로를 통해 데이터를 읽어들일 파일의 찾고,

해당 파일에 작성되어 있는 데이터를 읽어들일 때 FileReader의 read() 메서드를 사용하는데,

개인적으로 궁금했던 바는 읽어들인 데이터를 변수에 담고 → data = fr.read();

데이터가 존재하는 변수를 char타입으로 변환한 뒤 출력하고 나서 → System.out.print((char)data);

반복문 처음으로 돌아가는데, 다시 파일로부터 출력한 데이터 뒤의 데이터를 읽어들이는데,

증가식?이 있어야 하는게 아닌가 하는 생각이 들었다. 읽었던 데이터와 읽지 않은 데이터를 어떻게 구분하고,

읽었던 데이터 바로 뒤의 데이터를 읽어들이는 그 순서를 어떻게 알고 작동하는지가 궁금해서 FileReader클래스에 작성되어 있는

read() 메서드를 살펴봤다.

 

read() 메서드가 파일의 내용을 한 글자씩 읽어오는 기능이 어떻게 작동할까?

FileReader의 read() 메서드는 하나의 글자를(a single character) 읽어들인다. 파일에 있는 데이터든 사용자로부터 입력을 받은 데이터든 하나의 문자를 읽어들일 때까지 이 메서드는 멈춰있고(block), 읽을 문자가 준비되면 이 메서드가 실행한다. 또 I/O 에러가 발생하거나 stream의 끝에 도달하면 멈춘다? → 문자가 입력될 때까지, 입/출력 도중에 에러가 발생할 때까지, 스트림의 끝에 도달할 때까지(스트림의 끝에 도달한다는 것은 더 이상 읽어들일 데이터가 없다는 것) read() 메서드는 대기한다.

 

반환 값: 읽어들인 글자를 반환하는데, 글자를 숫자타입으로, 범위는 0에서 65535까지, 만약 스트림의 끝에 도달하면

→ 즉 더 이상 읽어들일 데이터가 없다면 -1을 반환한다.

 

```

public int read() throw IOException { 

 

// 하나의 문자를 받기 위해 문자배열을 선언하고, 공간을  초기화한다

// --> 길이가 1인 char 배열을 생성

char[] cb = new char[1];

 

// 파일에서 1개의 문자를 읽어 cb[0]에 저장한다. 만약 더 이상 읽어들일 문자가 없다면 -1을 반환한다.

 // read(cb, 0, 1) → public abstract int read(char[] cbuf, int off, int len) throws IOException;

if(read(cb, 0, 1) == -1)

  {    return -1;  }

else

  {    return cb[0]; }}

```

 

read() 메서드 호출시 내부에서 오버로딩 read(char[] cb, int off, int len) 메서드를 호출하는데,

이 메서드에서(read()) 전달받은 첫 번째 파라미터는 하나의 문자를 저장할 수 있는, 크기가 1인(요소가 없는) char타입의 배열이다

→ 데이터를 임시로 저장하기 위한 버퍼 역할, 파일에 읽은 문자를 cb[0]에 저장할 수 있다. 파일에서 데이터를 읽을 때, 파일 내부에서 커서 또는 파일 포인터라는 것이 존재한다. 이 커서는 현재 읽을 데이터가 있는 위치를 가리킨다. read(cb, 0, 1)가 호출되면 파일 커서가 가리키는 위치에서 한 문자를 읽고, 그 문자를 cb[0]에 저장한다 → 커서가 파일의 처음에 있다면 첫 번째 문자를 읽어서 cb[0]에 저장한다. 그런 다음 커서는 자동으로 다음 문자를 가리키는 위치로 이동한다. *** 커서 이동은 파일 시스템 또는 스트림 처리 시스템이 자동으로 관리하므로, 개발자가 직접 커서를 조작할 필요는 없다(커서의 자동이동)

 

read(cb, 0, 1) 메서드는 (읽어올 데이터를 갖고 있는, 지정한) 파일에서 커서가 가리키는 위치의 문자를 읽어서(1 → 세 번째 파라미터?) 첫 번째 파라미터로 전달한 cb[0 → 두 번째 파라미]에 저장하고, 자동으로 다음 위치로 이동시킨다. 세 번째 파라미터 1은 데이터를 읽어오는 파일에서 커서가 위치하는 곳으로부터 문자 1개를 의미하고, 그 문자 1개를 문자타입 배열의 인덱스가 0인 곳에 저장되는 것이다.

 

위의 방법처럼 파일로부터 읽어들인 데이터를 반복문으로 돌려서 출력하는 방법도 있고,

 

2. 크기가 넉넉한(파일의 데이터를 모두 담을 수 있는 공간을 가진) 배열을 만들어

read() 메서드의 매개 값으로 전달하면 전달한 배열에 파일의 데이터를 모두 넣어서 반환된다.

파일의 데이터가 모두 담긴 반환된 배열을 반복문으로 돌려서 출력하는 방법도 있다.

// 1. 파일에 담긴 데이터를 모두 담을 수 있는 크기가 넉넉한 배열을 생성
char[] arr = new char[50];

// 2. FileReader객체의 read() 메서드에 위 배열을 전달하면
// 파일에 기록된 데이터를 모두 배열에 담아 리턴한다.
// FileReader의 read() 메서드에 배열을 전달하면 반환되는 값은
// 파일에 기록된 문자의 갯수
int result = fr.read(arr);

// 문자들의 갯수가 아닌 문자들을 출력하려면
// 문자들이 담긴 배열을 반복문을 돌려 하나하나 출력하면 된다.
for(char c: arr) {
	System.out.print(c);
    // 배열을 꽉 채우지 않았을 수도 있기에
    // 만약에 c가 0이라면 break를 한다.
    if(c==0) break;
}

 

또 반복문으로 돌리지 말고, 배열을 toString()으로 출력할 수도 있다.

System.out.println(Arrays.toString(arr));

 

char타입의 변수가 0이라는 것

char타입의 변수에는 비어있는 문자가 대입될 수 없다. char c = ''; → 컴파일 과정에서 에러가 난다.

char타입의 변수에는 하나의 문자가 대입될 수 있는데, 하나의 공백 또한 대입될 수 있다.

하나의 공백을 가진 char타입의 변수를 int타입으로 변환해서 출력해보니 32라는 숫자가 나왔다.

하나의 공백을 값으로 가진 char타입의 변수는 int로 형변환 했을 때 32의 값을 갖는다.

 

char타입의 배열에서 공간이 비어있다는 것을 어떻게 알 수 있을까?

char타입의 변수에 값이 없을 수 있고, 값이 없다는 것은 값이 초기화되지 않았다는 것을 의미한다.

초기화하지 않았다면, 값이 대입되지 않았다면 char타입의 변수의 값은 '\u0000' 이며

'\u0000'은 char타입의 변수가 저장할 수 있는 문자의 최소 값이며, null 즉 값이 없음을 의미한다.

그러나 char타입의 변수는 객체타입이 아니기에 null을 직접적으로 대입할 수 없고,

값이 없음을 대입하고 싶다면 char c = Character.MIN_VALUE; 또는 char c = '\u0000'; 또는 char c = 0;  또는 char c; (선언만 하기)이렇게 작성하면 된다. 정수 0은 null 문자의 정수 값을 나타내기 때문

→ Character 클래스에 작성되어 있는 public static final char MIN_VALUE = '\u0000';

 

char타입의 변수의 값이 0이라는 것은 char타입으로 출력했을 때 아무것도 출력되지 않지만정수 값으로는 0이 출력된다. char 타입은 하나의 문자를 저장할 수 있는 타입이고, char 타입의 변수를 출력했는데 문자는 출력되지 않고, 이 변수를 정수로 형변환하여 출력했을 때 0이라면 이는 문자가 없는 것을 의미한다. 즉 해당 변수에는 값이 없음을 의미한다.그래서 if(c == 0) break; char타입의 c가 0이라면 이는 빈 문자를 의미하는 것이고,char 타입의 배열을 반복문으로 돌려 출력했을 때 c가 0이라면 더 이상의 데이터는 없다고 판단하고,출력을 그만하고 반복문을 빠져나가겠다는 의미다 → break;

 

문자의 입출력을 진행할 때는 FileWriter/FileReader 클래스가 적합하다.

 

----------------------------------------------------------------------------------------

 

Buffered를 활용한 stream

Buffered가 붙은 객체들을 생성할 때 기존에 사용했던 객체를 생성자의 매개 값으로 전달한다.

 

```

FileOutputStream fos = new FileOutputStream("C://workspace//hello.txt");

BufferedOutputStream bos = new BufferedOutputStream(fos);

 

```

 

BufferedOutputStream은 스스로 파일에 접근하지 못하기 때문에 파일의 위치(경로)를 갖고, 출력을 수행하는 FileOutputStream객체를 (BufferedOutputStream 객체를 생성할 때 생성자의) 매개 값으로 전달받는다. BufferedOutputStream은 FileOutputStream객체가 있어야 파일에 간접적으로 접근하여 입출력을 수행할 수 있다. BufferedInputStream 또한 FileInputStream을 사용하여 파일에 간접적으로 접근하여 출력을 수행할 수 있다.

BufferedInputStream과 BufferedOutputStream을 사용하는 이유는 스트림의 입출력 효율을 높이기 위해서다. 읽어들이거나 기록할 데이터가 많을수록 버퍼를 사용하여 데이터의 입출력을 수행하는 BufferedInputStream과 BufferedOutputStream을 사용하는 것이 좋다.

 

파일에 있는 데이터를 읽어들일 때

반복문을 아래와 같이 작성해도 된다.

 

```

int data; // 파일로부터 읽어들인 데이터의 값을 담을 변수를 선언

 

// BufferedInputStream객체가 버퍼에 저장된 파일로부터 읽어들인 데이터를

// 읽어서 data라는 변수에 담았고, 해당 변수에 담긴 데이터의 값이 -1이 아니라면

// 콘솔창에 출력을 진행한다.

 

// while((data = fis.read()) != -1)

while((data = bis.read()) != -1) {

    System.out.print((char)data);

}

 

```

 

Buffered I/O

버퍼를 이용해서 읽기/쓰기 성능을 향상시키는 클래스

버퍼는 데이터를 입/출력 하기전에 임시로 저장해 두는 배열 형태의 공간을 의미한다.

 

버퍼를 이용해야 하는 이유: 성능이 향상되고, 시간이 절약된다.

 

예를 들어 OutputStream을 사용한다면 1byte수? 만큼 출력이 발생한다 → 출력이 자주 일어남

그런데 BufferedOutputStream을 사용한다면 버퍼에 한 번에 저장해놓고 한 번의 출력으로 끝난다.

 

BufferedInputStream과 FileInputStream의 읽기 성능 테스트 실습

package etc.api.io.buffered;

import java.io.BufferedInputStream;
import java.io.FileInputStream;

public class BufferedInputEx {

    public static void main(String[] args) {
        long start = System.currentTimeMillis();

        try(FileInputStream fis = new FileInputStream("C://workspace//merong.txt");
        BufferedInputStream bis = new BufferedInputStream(fis)) {
            int data;
            while((data = bis.read()) != -1) {
                System.out.print((char)data);
            }
            System.out.println();


        }catch(Exception e) {
            e.printStackTrace();

        }
        long end = System.currentTimeMillis();
        System.out.println("소요시간: " + (end-start)*0.001+"초");
        // fis: 소요시간: 0.035초
        // bis: 소요시간: 0.017초
    }
}

 

객체의 직렬화로 시작했다가 직렬화에 대한 공부보다는 I/O 클래스만 깊게 판 것 같다.

근데 직렬화란 I/O 클래스랑 연관도가 높은 것 같으니 다음에는 객체의 직렬화 관련해서 마무리할 예정