11일차 - 파일입출력

은채의 성장통·2025년 6월 12일

KCC정보통신

목록 보기
15/30

파일에 입출력 프로그래밍

  • 파일에 데이터를 저장하고 불러올 수 있는 여러가지 클래스를 제공한다.

1. 파일에 데이터 저장하기(자주 쓰지는 않는다)

파일에 데이터를 저장하는 여러 가지 방법을 정리해보았어. 주로 CSV 형식으로 저장하거나, DataOutputStream을 사용하여 데이터를 저장하는 방식이 있어.

1.1 단순 텍스트로 저장하기

  • CSV 파일 저장

CSV 파일은 필드와 컬럼을 구분하여 데이터를 저장하는 방식이야. 데이터를 저장할 때는 필드 구분자와 줄바꿈 기호를 추가해야 해.

import java.io.FileWriter;

public class CSVWriter {
    public static void main(String[] args) throws Exception {
        String data1 = "홍길동,서울,hong@test.com,30";
        String data2 = "길남,부산,kil@hong.com,25";

        FileWriter out = new FileWriter("member.csv");
        out.write(data1 + "\n"); // 줄바꿈 추가
        out.write(data2);
        out.close(); // close 하면 자동 저장
        System.out.println("파일에 저장됨");
    }
}
  • PrintWriter를 활용한 CSV 저장

PrintWriter를 사용하면 println을 통해 줄 단위로 저장할 수 있어.

import java.io.FileOutputStream;
import java.io.PrintWriter;

public class CSVWriter2 {
    public static void main(String[] args) throws Exception {
        String data1 = "홍길동,서울,hong@test.com,30";
        String data2 = "길남,부산,kil@hong.com,25";

        FileOutputStream fos = new FileOutputStream("member.csv");
        PrintWriter out = new PrintWriter(fos);

        out.println(data1);
        out.println(data2);

        out.close();
        System.out.println("파일에 저장됨");
    }
}

CSV 방식의 장점과 단점

✅ 범용적이며 다양한 플랫폼에서 사용 가능

❌ 데이터의 순서를 알고 있어야 정확한 처리 가능


1.2 DataOutputStream 이용하기

DataOutputStream을 사용하면 데이터를 타입에 따라 저장할 수 있어. 단순 텍스트 저장 방식과는 다르게 필드 구분자가 필요 없고, 데이터를 읽을 때 형변환할 필요도 없음

데이터 저장

import java.io.DataOutputStream;
import java.io.FileOutputStream;

public class DataWriter {
    public static void main(String[] args) {
        try (FileOutputStream fos = new FileOutputStream("member.data");
             DataOutputStream dos = new DataOutputStream(fos)) {

            dos.writeUTF("홍길동");
            dos.writeUTF("서울");
            dos.writeUTF("hong@test.com");
            dos.writeInt(30);

            dos.writeUTF("길남");
            dos.writeUTF("부산");
            dos.writeUTF("kil@hong.com");
            dos.writeInt(25);

            System.out.println("File Saved");
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
    }
}

데이터 읽기

DataInputStream을 사용하여 저장된 데이터를 순서대로 읽어야 해.

import java.io.DataInputStream;
import java.io.FileInputStream;

public class DataReader {
    public static void main(String[] args) {
        try (FileInputStream fis = new FileInputStream("member.data");
             DataInputStream dis = new DataInputStream(fis)) {

            while (dis.available() > 0) { // 데이터가 남아있는 동안 읽기
                System.out.println(dis.readUTF());
                System.out.println(dis.readUTF());
                System.out.println(dis.readUTF());
                System.out.println(dis.readInt());
            }
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
    }
}

DataOutputStream 방식의 장점과 단점

✅ 필드 구분자를 추가할 필요 없음

✅ 데이터를 읽을 때 형변환이 필요 없음

❌ 저장한 순서대로 읽어야만 올바르게 데이터를 가져올 수 있음

❌ 자바에서만 사용 가능


2. 스트림

  • 입출력 스트림은 소스(Source)에서 싱크(Sink)로의 데이터흐름을 말한다.(소스는 데이터흐름의 출발점이고 싱크는 데이터흐름이 끝나는 지점)
    • in.txt파일을 읽는 프로그램을 개발하려면 in.txt파일이 입력스트림이 되고 반대로 out.txt파일에 무언가를 쓰려고하면 out.txt.파일이 출력스트림이 된다.
    • 소스와 싱크를 합쳐서 노드라고 한다. 노드의 종류로는 디스크의 파일, 메모리등등이 존재하는데
바이트 스트림character Stream
소스 스트림InputStream(바이트 단위 입력스트림)Reader(문자 단위 입력)
싱크 스트림OutputStream(바이트 단위 출력스트림)Writer(문자 단위 출력)
바이트 스트림character Stream
file(파일 대상)FileInputStream
FileOutputStreamFileReader
FileWriter
Memory : Array(메모리상의 배열대상)ByteArrayInputStream
ByteArrayOutputStreamCharArrayReader
CharArrayWriter
Memory : String(메모리상의 String)-StringReader
StringWriter
Pipe(파이프 대상)PipedInputStream
PipedOutputStreamPipedReader
PipedWriter

2.1 노드스트림

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

public class ReadFileExample {
    public static void main(String[] args) {
        FileInputStream fis = null;

        try {
		        //프로젝트 파일안에 in.txt가 있거나 만들어야함
            fis = new FileInputStream("in.txt"); // 입력 스트림 객체 생성 , 파일을 읽어온다
            byte[] buffer = new byte[256]; // 데이터를 저장할 버퍼, 배열을 생성해서 데이터를 저장한다.
            int readCount;

            while (((readCount = fis.read(buffer))) != -1) { // 파일 끝까지 읽기 , read()메서드를 활용해서 파일의 끝 (0-1)까지 읽어온다
            	// fis.read(buffer)는 fis라는 파일 입력스트림을 사용해서 읽어오고 이를 buffer에 저장한다. 그리고 fis.read(buffer))는 긁어온 바이트만큼 readCount에 저장한다.
                String data = new String(buffer, 0, readCount);
                // buffer는 파일에서 읽어온 바이트데이터가 저장된 배열
                // 0 : 변환을 시작할 인덱스 즉 buffer의 처음(0번쨰 위치부터 변환한다)
                // readCount : buffer에서 실제로 읽어온 데이터의 길이를 의미
                // new String(바이트배열, 시작 인덱스, 변환할 길이);
                System.out.print(data);
            }
        } catch (FileNotFoundException e) {
            System.out.println("파일이 존재하지 않습니다.");
        } catch (IOException e) {
            System.out.println(e.getMessage());
        } finally {
            try {
                if (fis != null) fis.close(); // 스트림 닫기
            } catch (Exception e) {
                System.out.println("파일 닫는 중 오류 발생");
            }
        }
    }
}
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

public class NodeStreamExample {
    public static void main(String[] args) {
        FileReader input = null; // 입력 스트림 객체 선언 (텍스트 파일을 읽음)
        FileWriter output = null; // 출력 스트림 객체 선언 (텍스트 파일을 씀)

        try {
            // 파일 경로 설정
            String inFile = "in.txt";   // 입력할 파일
            String outFile = "out.txt"; // 복사할 출력 파일

            // 파일을 읽고 쓰기 위한 객체 생성
            input = new FileReader(inFile);   // 문자 단위 입력 스트림 생성
            output = new FileWriter(outFile); // 문자 단위 출력 스트림 생성

            // 버퍼 설정: 한번에 128개의 문자씩 읽고 저장
            char[] buffer = new char[128]; 
            int readCount; // 읽은 문자 수를 저장할 변수

            // 파일 끝까지 반복해서 읽기
            while ((readCount = input.read(buffer)) != -1) { 
                // FileReader의 read() 메서드는 파일에서 읽은 문자 수를 반환함
                // 파일 끝에 도달하면 -1을 반환하여 반복문이 종료됨
                
                output.write(buffer, 0, readCount); // 읽어온 데이터를 그대로 출력 파일에 저장
            }

            System.out.println("파일이 복사되었습니다."); // 파일 복사 완료 메시지 출력
        } catch (IOException e) {
            // 📌 입출력 예외 발생 시 처리
            e.printStackTrace();
        } finally {
            // 📌 사용한 파일 스트림을 닫아 리소스를 해제
            try {
                if (input != null) input.close(); // 입력 스트림 닫기
                if (output != null) output.close(); // 출력 스트림 닫기
            } catch (IOException e) {
                System.out.println("파일 닫는 중 오류 발생");
            }
        }
    }
}

2.2 필터 스트림

  • 이를 처리 스트림이라고도하며 다른 객체를 둘러싸는 역활을 한다.
  • 특정한 목적을 가지고 만든 클래스(성능을 높인다던가, 입출력의 흐름을 캐릭터에서 바이트로 바꾼다던가)
  • 사용하는 이유는 노드스트림의 부족한 기능을 보완하여 좀 더 정밀한 입출력을 하기 위해서 입니다.
    • 예로 FileReader클래스의 노드 스트림은 파일에서 텍스트를 읽을떄 한 문자씩 낮은 수준의 메서드(read())만 가지고 있지만 BufferReader클래스 등의 필터 스트림은 줄단위로 읽어 String으로 반환하는 고급 메서드 readLine()메서드를 포함하므로 좀더 편하게 작업이 가능하다

      import java.io.BufferedReader;
      import java.io.BufferedWriter;
      import java.io.FileReader;
      import java.io.FileWriter;
      import java.io.IOException;
      
      public class FilterStreamExample {
          public static void main(String[] args) {
              // 파일 입출력 객체 선언
              FileReader input = null;
              FileWriter output = null;
              BufferedReader bufInput = null;
              BufferedWriter bufOutput = null;
      
              try {
                  // 파일 경로 지정
                  String inFile = "in.txt";   // 입력 파일
                  String outFile = "out2.txt"; // 출력 파일
      
                  // 파일을 읽고 쓰기 위한 객체 생성
                  input = new FileReader(inFile);   // FileReader: 텍스트 파일을 문자 단위로 읽음
                  output = new FileWriter(outFile); // FileWriter: 텍스트 파일을 문자 단위로 씀
                  
                  // 버퍼를 활용한 입력 및 출력 객체 생성
                  bufInput = new BufferedReader(input);   // BufferedReader: 더 빠른 텍스트 읽기를 지원
                  bufOutput = new BufferedWriter(output); // BufferedWriter: 더 빠른 텍스트 쓰기를 지원
      
                  // 한 줄씩 파일 읽기
                  String line;
                  line = bufInput.readLine(); // 첫 번째 줄 읽기
                  while (line != null) { // 파일 끝까지 반복
                      bufOutput.write(line, 0, line.length()); // 현재 줄을 출력 파일에 저장
                      bufOutput.newLine(); // 줄 바꿈 추가
                      line = bufInput.readLine(); // 다음 줄 읽기
                  }
      
                  // 파일 복사 완료 메시지 출력
                  System.out.println(inFile + " >> " + outFile);
              } catch (IOException e) {
                  // 입출력 예외 발생 시 처리
                  e.printStackTrace();
              } finally {
                  // 파일 닫기 (자원 해제)
                  try {
                      if (bufInput != null) bufInput.close();
                      if (bufOutput != null) bufOutput.close();
                  } catch (IOException e) {
                      System.out.println("파일 닫는 중 오류 발생");
                  }
              }
          }
      }

3. 입출력 클래스(교재참고를 하는데 이런 클래스가 있구나 정도로 하자)

4. 입출력과 관련된 클래스

4.1 파일(File) 객체

파일 객체란?

  • 파일을 지정할 때 단순히 문자열 경로로 지정할 수도 있지만, 자바에서 제공하는 File 클래스를 사용하면 더 다양한 기능을 활용할 수 있다.
  • 파일 객체는 파일뿐만 아니라 디렉토리(폴더)도 지칭할 수 있다.

파일 객체 생성 예제

import java.io.File;

public class FileExample {
    public static void main(String[] args) {
        // 파일 객체 생성
        File someFile = new File("C:/autoexec.bat");  // 특정 파일을 지정
        File someDir = new File("C:/");              // 디렉토리를 지정
        File otherFile = new File(someDir, "config.sys");  // 기존 디렉토리에서 파일 생성

        // 파일 이름 출력
        System.out.println("파일 이름: " + someFile.getName());
        System.out.println("파일 경로: " + someFile.getPath());
    }
}

파일 관련 메서드 정리

  • 파일 이름과 경로 관련 메서드
메서드설명
String getName()파일 이름 반환
String getPath()파일 경로 반환
String getAbsolutePath()파일의 절대 경로 반환
String getParent()파일이 속한 폴더 경로 반환
boolean renameTo(File newName)파일명을 변경 (변경 실패 시 false 반환)
boolean delete()파일 삭제 (false 반환 시 삭제 실패)
  • 파일 정보 관련 메서드
메서드설명
long lastModified()마지막으로 수정된 날짜 반환 (long 타입)
long length()파일 크기 반환 (바이트 단위)
  • 파일 테스트 관련 메서드
메서드설명
boolean exists()파일 존재 여부 확인 (true/false)
boolean canWrite()파일 쓰기 가능 여부 확인 (true/false)
boolean canRead()파일 읽기 가능 여부 확인 (true/false)
boolean isFile()파일인지 확인 (true: 파일, false: 폴더)

  • 파일 탐색기 예제 (File 클래스 활용)
import java.io.File;
import java.text.DateFormat;
import java.text.NumberFormat;
import java.util.Date;

public class FileExplorerExample {
    public static void main(String[] args) {
        // 탐색할 디렉토리 지정
        File myDir = new File("C:/"); // "C:/" 디렉토리 탐색

        // 해당 디렉토리 내 파일 목록 가져오기
        File[] listing = myDir.listFiles();

        // 파일 목록 출력
        for (int i = 0; i < listing.length; ++i) {
            File file = listing[i]; // 배열에서 파일 또는 폴더 가져오기

            System.out.print(file.getName() + "\t"); // 파일 또는 폴더 이름 출력

            if (file.isFile()) { // 파일인지 검사
                NumberFormat nf = NumberFormat.getInstance(); // 숫자 포맷 객체 (파일 크기 표시)
                DateFormat df = DateFormat.getDateInstance(); // 날짜 포맷 객체 (수정 날짜 표시)

                // 파일 크기(KB 단위) 출력
                System.out.print(nf.format(file.length() / 1024) + " Kbyte\t");

                // 마지막 수정 날짜 출력
                System.out.print(df.format(new Date(file.lastModified())));
            } else { // 폴더인 경우
                System.out.print("폴더");
            }
            System.out.println(); // 개행
        }
    }
}

  • 정리
  • File 클래스를 활용하면 파일뿐만 아니라 폴더도 조작 가능하다.
  • listFiles()를 사용하면 디렉토리 내 파일 목록을 가져올 수 있다.
  • isFile()을 사용해 파일인지 폴더인지 구분할 수 있다.
  • lastModified()length()를 활용해 파일 크기 및 수정 날짜 조회 가능하다.

4.2 URL 객체 (요즘 잘 안 씀)

URL 객체란?

  • 인터넷상의 주소(URL)를 참조할 수 있는 클래스가 java.net.URL 클래스다.
  • URL 객체를 사용하면 웹사이트에 접근하거나, 데이터(HTML, JSON 등)를 가져오는 기능을 쉽게 구현할 수 있다.

URL 객체 생성

import java.net.URL;

public class URLExample {
    public static void main(String[] args) {
        try {
            // URL 객체 생성 (웹 주소 지정)
            URL url = new URL("http://www.example.com");

            // URL의 주요 정보 출력
            System.out.println("프로토콜: " + url.getProtocol());  // http
            System.out.println("호스트: " + url.getHost());        // www.example.com
            System.out.println("포트: " + url.getPort());          // -1 (지정되지 않음)
            System.out.println("기본 포트: " + url.getDefaultPort()); // 80 (HTTP 기본 포트)
            System.out.println("파일 경로: " + url.getFile());     // / (루트 경로)
        } catch (Exception e) {
            System.out.println("URL 형식 오류: " + e.getMessage());
        }
    }
}

📌 URL 클래스의 주요 메서드 정리

메서드설명
String getProtocol()프로토콜 반환 (예: http, https, ftp 등)
String getHost()URL의 호스트 이름 반환
int getPort()URL에 지정된 포트 번호 반환 (없으면 -1)
int getDefaultPort()프로토콜의 기본 포트 반환 (http80, https443)
String getPath()URL의 경로 반환 (예: /index.html)
String getFile()URL에서 지정된 파일명 반환
Object getContent()URL의 컨텐츠 반환
URLConnection openConnection()URL과 연결되는 객체 반환
InputStream openStream()URL에서 데이터를 읽는 입력 스트림 객체 반환

📌 HTML 가져오기 예제 (URL 활용)

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;

public class GetHTMLExample {
    public static void main(String[] args) {
        // 📌 가져올 웹 페이지 주소 설정
        String urlStr = "http://www.javaspecialist.co.kr/";
        String file = "html.txt"; // 저장할 파일 이름
        byte[] inputString = new byte[1024]; // 읽어올 데이터를 저장할 배열

        // 📌 URL 객체 생성
        URL url = null;
        try {
            url = new URL(urlStr);
        } catch (MalformedURLException e) {
            System.out.println("URL 형식 오류");
            return;
        }

        // 📌 URL에서 데이터를 가져올 입력 스트림 생성
        InputStream is = null;
        try {
            is = url.openStream();
        } catch (IOException e) {
            System.out.println("URL을 열지 못했습니다.");
            return;
        }

        // 📌 HTML을 저장할 파일 출력 스트림 생성
        FileOutputStream fos = null;
        try {
            fos = new FileOutputStream(file);

            // 📌 데이터를 읽어 파일에 저장
            while (is.read(inputString, 0, inputString.length) != -1) {
                fos.write(inputString);
            }

            // 📌 URL 정보 출력
            System.out.println("Path: " + url.getPath());
            System.out.println("Protocol: " + url.getProtocol());
            System.out.println("Port: " + url.getPort());
            System.out.println("Default Port: " + url.getDefaultPort());
            System.out.println("File: " + url.getFile());

            System.out.println(file + " 파일을 확인하세요.");
        } catch (IOException e) {
            System.out.println("파일 저장 오류: " + e.getMessage());
        } finally {
            try {
                if (fos != null) fos.close(); // 출력 스트림 닫기
            } catch (IOException e) {
                System.out.println("파일 닫는 중 오류 발생");
            }
        }
    }
}

  • 코드 설명
  • 1. URL 객체 생성
URL url = new URL(urlStr);
  • new URL(String urlStr) → 웹 주소를 URL 객체로 변환하여 활용할 수 있게 함.
  • 2. URL에서 입력 스트림(openStream())을 통해 HTML 가져오기
InputStream is = url.openStream();
  • url.openStream()을 사용하면 웹 페이지 데이터를 InputStream을 통해 가져올 수 있음.
  • 3. FileOut^putStream을 활용해 가져온 데이터를 파일(html.txt)에 저장
FileOutputStream fos = new FileOutputStream(file);
  • 가져온 HTML을 파일에 저장하기 위해 FileOutputStream을 사용.
  • 4. while 반복문을 활용해 URL 데이터를 읽고 파일에 저장
while (is.read(inputString, 0, inputString.length) != -1) {
    fos.write(inputString);
}
  • is.read(buffer, 0, buffer.length) → 웹 페이지 데이터를 읽어 buffer 배열에 저장
  • 읽어온 데이터를 fos.write()를 이용하여 파일에 저장
  • 5. URL 정보 출력
System.out.println("Path: " + url.getPath());
System.out.println("Protocol: " + url.getProtocol());
System.out.println("Port: " + url.getPort());
System.out.println("Default Port: " + url.getDefaultPort());
System.out.println("File: " + url.getFile());
  • URL 객체에서 경로, 프로토콜, 포트 번호 등의 정보를 출력.
  • 6. finally 블록을 활용하여 파일을 안전하게 닫기
try {
    if (fos != null) fos.close();
} catch (IOException e) {
    System.out.println("파일 닫는 중 오류 발생");
}
  • 파일 스트림을 닫아 리소스 누수를 방지.

정리

  • URL 클래스를 사용하면 웹 주소를 직접 참조하고 데이터를 가져올 수 있다.
  • openStream()을 사용하면 웹 페이지의 HTML을 읽을 수 있다.
  • FileOutputStream을 활용하면 가져온 데이터를 파일로 저장할 수 있다.
  • getProtocol(), getHost(), getPort() 등을 활용해 URL 정보를 쉽게 확인할 수 있다.


4.3 RandomAccessFile (파일의 원하는 위치로 이동한 후 읽거나 쓰기)

RandomAccessFile 클래스란?

  • 일반적인 파일 입출력 방식(FileReader, FileWriter, FileInputStream, FileOutputStream)은 순차적으로 데이터를 읽고 쓸 수밖에 없지만, RandomAccessFile을 사용하면 파일의 특정 위치로 이동하여 데이터를 읽거나 쓸 수 있다.
  • 즉, 파일 포인터를 조작하여 원하는 위치에서 데이터를 수정하거나 추가할 수 있다.

주요 메서드

메서드설명
long getFilePointer()현재 파일 포인터 위치를 반환
void seek(long position)원하는 위치로 파일 포인터 이동
long length()파일의 길이(크기)를 반환
void writeUTF(String s)UTF-8 문자열을 파일에 기록
String readUTF()UTF-8 문자열을 읽어 반환

로그 기록 예제 (RandomAccessFile 활용)

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.Date;

public class LogWriteExample {
    public static void main(String[] args) {
        // 저장할 로그 파일명 및 모드 설정
        String fileName = "runtime.log";
        String mode = "rw"; // "rw" → 읽기와 쓰기가 가능하도록 설정

        RandomAccessFile raf = null;

        try {
            // RandomAccessFile 객체 생성
            raf = new RandomAccessFile(fileName, mode);

            // 파일 포인터를 파일 끝으로 이동 (새로운 로그를 추가할 위치)
            raf.seek(raf.length());

            // 현재 날짜와 시간을 UTF 형식으로 저장
            raf.writeUTF(new Date().toString() + "\n");

            System.out.println(fileName + "에 현재 시간이 기록되었습니다.");
        } catch (FileNotFoundException e) {
            System.out.println("파일이 존재하지 않습니다.");
        } catch (IOException e) {
            System.out.println("파일 기록 중 오류 발생: " + e.getMessage());
        } finally {
            try {
                if (raf != null) raf.close(); // 파일 스트림 닫기 (리소스 해제)
            } catch (IOException e) {
                System.out.println("파일 닫는 중 오류 발생");
            }
        }
    }
}

  • 코드 설명
  • 1. RandomAccessFile 객체 생성
raf = new RandomAccessFile(fileName, mode);
  • fileName → 저장할 파일 이름
  • mode"rw" 모드를 지정하여 읽기(r)와 쓰기(w)가 가능하도록 설정
  • 2. 파일 포인터를 마지막 위치로 이동 (seek())
raf.seek(raf.length());
  • 파일의 마지막 위치로 포인터를 이동하여 기존 데이터를 유지하면서 새로운 데이터를 추가
  • 3. 새로운 로그 데이터를 기록 (writeUTF())
raf.writeUTF(new Date().toString() + "\n");
  • new Date().toString() → 현재 날짜와 시간을 문자열로 변환
  • writeUTF() → UTF 형식으로 파일에 문자열을 기록
  • 4. 예외 처리 (FileNotFoundException, IOException)
catch (FileNotFoundException e) {
    System.out.println("파일이 존재하지 않습니다.");
} catch (IOException e) {
    System.out.println("파일 기록 중 오류 발생: " + e.getMessage());
}
  • 파일이 존재하지 않을 경우, FileNotFoundException 발생
  • 입출력 과정에서 문제가 발생하면 IOException 처리
  • 5. 파일을 안전하게 닫기 (close())
finally {
    try {
        if (raf != null) raf.close();
    } catch (IOException e) {
        System.out.println("파일 닫는 중 오류 발생");
    }
}
  • 파일 작업이 끝난 후 close()를 호출하여 스트림을 닫아야 함
  • 리소스 누수를 방지하고, 다른 프로그램에서 파일을 안전하게 사용할 수 있도록 처리

5. 객체 직렬화

객체 직렬화란?

  • 객체 직렬화는 생성된 객체가 스트림을 통해 이동하는 것을 의미한다.
  • 자바에서는 메모리에서 생성된 객체를 파일에 저장하여 영속성을 부여하거나, 객체를 다른 JVM 환경으로 이동시키는 과정을 직렬화라고 한다.
  • 직렬화라는건 결국 객체의 데이터를 특정한 방식으로 변환해서 파일에 저장하거나 네트워크로 전송되게 하게끔 하는것

5.1 Serializable 인터페이스

import java.io.Serializable;

public class Customer implements Serializable {
    private String name;
    private char gender;
    private String email;
    private int birthYear;

    // 생성자: 고객 정보를 설정
    public Customer(String name, char gender, String email, int birthYear) {
        this.name = name;
        this.gender = gender;
        this.email = email;
        this.birthYear = birthYear;
    }

    // 객체의 내용을 문자열로 반환
    @Override
    //toString() 메서드는 객체가 문자열로 출력될떄 자동으로 실행되는 메서드
    public String toString() {  
        return "Customer [name=" + name + ", gender=" + gender + ", email=" + email + ", birthYear=" + birthYear + "]";
    }
}
  • 설명
  • Serializable 인터페이스를 구현하면 객체를 직렬화할 수 있음.
  • 이 인터페이스에는 메서드가 없으며, 객체를 직렬화 가능하게 표시하는 역할만 수행.

  • 직렬화된 객체 저장 예제
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

public class WriteCustomerExample {
    public static void main(String[] args) {
        // 직렬화할 객체 생성
        Customer cust1 = new Customer("허현정", 'M', "heojk2@daum.net", 21);
        Customer cust2 = new Customer("허현준", 'M', "loid@gmail.com", 20);

        FileOutputStream fos = null;
        ObjectOutputStream oos = null;

        try {
            // 파일 출력 스트림 생성
            fos = new FileOutputStream("customer.ser");
            oos = new ObjectOutputStream(fos);

            // 객체를 파일에 직렬화하여 저장
            oos.writeObject(cust1);
            oos.writeObject(cust2);

            System.out.println("Customer 데이터가 저장되었습니다.");
        } catch (IOException e) {
            System.out.println(e.getMessage());
        } finally {
            try {
                if (oos != null) oos.close();
            } catch (IOException e) {
                System.out.println("파일 닫는 중 오류 발생");
            }
        }
    }
}

설명

  • ObjectOutputStream을 사용하여 객체를 직렬화하고 파일에 저장.
  • writeObject()를 호출하면 객체가 바이트 형태로 변환되어 저장됨.
  • 직렬화된 파일이기에 텍스트 에디터로는 내용을 알아볼수 없다.
    • 문자열 그대로 저장이 되는게 아니라 객체의 데이터를 바이트형태로 변환하기에 객체의 구조(클래스 정보, 필드 값등)를 유지하면서 바이트형태로 변환이 된다
      • 즉 텍스트형식이 아니라 이진데이터로 저장이 된다.

image.png

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

public class ReadCustomerExample {
    public static void main(String[] args) {
        FileInputStream fis = null; // 파일 입력 스트림 객체 선언
        ObjectInputStream ois = null; // 객체 입력 스트림 객체 선언

        try {
            // 📌 직렬화된 파일을 읽을 FileInputStream 생성
            fis = new FileInputStream("customer.ser");
            
            // 📌 파일에서 객체를 읽을 ObjectInputStream 생성
            ois = new ObjectInputStream(fis);
						//ois는 직렬화된 파일을 읽은 것에서 객체를 읽는다.
            // 📌 파일에서 객체를 읽어 Customer 객체로 변환
            Customer cust1 = (Customer) ois.readObject();
            Customer cust2 = (Customer) ois.readObject();

            // 📌 읽어온 객체 출력
            System.out.println(cust1.toString());
            System.out.println(cust2.toString());

        } catch (FileNotFoundException e) {
            System.out.println("파일이 존재하지 않습니다.");
        } catch (IOException e) {
            System.out.println("파일을 읽는 중 오류 발생: " + e.getMessage());
        } catch (ClassNotFoundException e) {
            System.out.println("클래스를 찾을 수 없습니다: " + e.getMessage());
        } finally {
            // 📌 파일을 안전하게 닫기
            try {
                if (ois != null) ois.close();
            } catch (IOException e) {
                System.out.println("파일 닫는 중 오류 발생");
            }
        }
    }
}
  • 파일을 읽기 위해서 FIleInputStream객체를 생성하고, 객체를 읽기위한 필터스트림인 ObjectInputStream객체를 생성한다.

5.2 transient

  • transient 키워드란?
  • transient 키워드는 직렬화에서 특정 변수 제외할 때 사용하는 제한자이다.
  • 객체를 직렬화할 때 보안이 필요한 변수(비밀번호 등)를 제외하고 싶을 때 사용한다.

  • 직렬화된 객체 읽기 예제
import java.io.FileInputStream;
import java.io.ObjectInputStream;

public class ReadAccountExample {
    public static void main(String[] args) throws Exception {
        // 파일 입력 스트림을 사용해 직렬화된 객체 읽기
        FileInputStream fis = new FileInputStream("account.ser");
        ObjectInputStream ois = new ObjectInputStream(fis);

        // 객체 복원 및 출력
        Account a2 = (Account) ois.readObject();
        System.out.println(a2);

        ois.close();
    }
}

설명

  • ObjectInputStream을 사용하면 직렬화된 객체를 다시 읽어올 수 있음.
  • transient 키워드로 직렬화에서 제외된 변수는 복원되지 않음.

5.3 serialVersionUID

serialVersionUID란?

  • 클래스가 변경되더라도 이전에 직렬화했던 데이터를 정상적으로 불러오기 위해 필요한 변수.
  • 클래스의 구조가 변경되어도 같은 serialVersionUID 값을 유지하면 기존 직렬화된 데이터를 읽을 수 있다.

  • serialVersionUID 예제
import java.io.Serializable;

public class Account implements Serializable {
    private static final long serialVersionUID = 5004258855763033943L;

    String accountNo;
    String userName;
    int balance;
    transient String password; // 직렬화 제외
    String newField;

    // 생성자: 계좌 정보 설정
    public Account(String accountNo, String userName, int balance, String password) {
        this.accountNo = accountNo;
        this.userName = userName;
        this.balance = balance;
        this.password = password;
    }

    // 객체 정보를 문자열로 반환
    @Override
    public String toString() {
        return accountNo + "[" + userName + ", " + password + "] : " + balance;
    }
}

설명

  • serialVersionUID를 설정하면 클래스 구조가 변경되더라도 직렬화된 데이터를 불러올 수 있음.
  • transient 키워드가 적용된 변수는 객체 직렬화에서 제외됨.

  • 객체 직렬화 정리
  • 객체 직렬화는 객체를 바이트 스트림으로 변환하여 파일에 저장하거나 네트워크를 통해 전송할 수 있도록 하는 기술이다.
  • Serializable 인터페이스를 구현하면 객체를 직렬화할 수 있다.
  • transient 키워드를 사용하면 직렬화 과정에서 특정 변수를 제외할 수 있다.
  • serialVersionUID를 설정하면 클래스 변경 후에도 이전 직렬화 데이터를 불러올 수 있다.
profile
인생 별거 없어

0개의 댓글