본문 바로가기

WEB개발/JAVA

[JAVA] 객체 직렬화 serialization, IOStream

1. 직렬화 serialization

직렬화 serialization

객체에 저장된 데이터를 스트림에 쓰기 위해 연속적인 데이터로 변환하는 것을 말한다. 반대로 스트림에서 데이터를 읽어 객체로 변환하는 것을 역직렬화(deserialization)라 한다.

 

역직렬화, deserialize

역직렬화는 ObjectInputStream의 readObject()를 이용한다. 다만 한 가지 주의 사항이 있는데 하나 이상의 객체를 직렬화 했을 경우 반드시 같은 순서로 역직렬화 해야 한다는 것이다.

 

package test.serialize;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.util.Base64;

public class TestMain {

	public static void main(String[] args) throws Exception{
		UserInfo userInfo = new UserInfo();

		userInfo.setAddr("주소주소");
		
		//OutputStream os = System.out;
		FileOutputStream fos = new FileOutputStream("test.txt");
		ObjectOutputStream oos = new ObjectOutputStream(fos);
		oos.writeObject(userInfo);
		
		FileInputStream fis = new FileInputStream("test.txt");
		ObjectInputStream ois = new ObjectInputStream(fis);
		UserInfo userInfo2 = (UserInfo) ois.readObject();
		
		System.out.println(userInfo2.getAddr());
		
	}

}

OUTPUT

주소주소

 

선언부에 transient 제어자를 사용하면 직렬화에서 제외된다.

package test.serialize;

import java.io.Serializable;

public class UserInfo implements Serializable {

	private String name;
	private int age;
	private transient String addr;
	
	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;
	}
	public String getAddr() {
		return addr;
	}
	public void setAddr(String addr) {
		this.addr = addr;
	}
}

 

serialVersionUID

serialVersionUID란 역직렬화 시 클래스간 버전 비교에 사용되는 해시값이다.

명시하지 않을 경우 객체가 직렬화될 때 클래스에 정의된 멤버들의 정보를 이용해서 serialVersionUID에 클래스의 버전을 JVM이 자동으로 생성하여 직렬화 내용에 포함한다.

 


 

데이터입출력 (Stream)

 

: 프로그램에서는 데이터를 외부에서 읽고 다시 외부로 출력하는 작업

 

 

자바 입출력과 스트림(Stream)

 

 자바에서 데이터는 스트림(Stream)을 통해 입출력 됩니다. 스트림은 단일 방향으로 연속적으로 흘러가는 것을 말하는데 물이 높은 곳에서 낮은곳으로 흐르듯이 데이터는 출발지에서 나와 도착지로 흘러간다는 개념입니다.

 

 

 

Java.io 패키지

자바의 기본적인 데이터 입출력은 Java.io 패키지에서 제공합니다. java.io 패키지에서는 파일 시스템의 정보를 얻기 위한 File클래스와 데이터를 입출력하기 위한 다양한 입출력 스트림 클래스를 제공합니다.

 

 바이트 기반 스트림/보조 스트림

  • InputStream (바이트 스트림: Byte Stream): 1과 0으로 구성된 Binary 데이터의 입력처리를 위한 스트림(이미지, 사운드)

 

 

  • OutputStream (바이트 스트림: Byte Stream): 1과 0으로 구성된 Binary 데이터의 출력 처리를 위한 스트림 (이미지, 사운드)

 

 

DataOutputStream 클래스는 데이터를 이진 형식으로 출력하기 위한 스트림 클래스입니다. 

이 클래스는 writeByte, writeShort, writeInt, writeLong, writeFloat, writeDouble, writeBoolean, writeChar 등의 메서드를 제공합니다. 이러한 메서드를 사용하여 이진 형식으로 데이터를 출력할 수 있습니다. 이 클래스는 데이터를 직렬화하거나 네트워크를 통해 전송하기 위한 용도로 사용됩니다.

반면에  BufferedOutputStream은 출력 스트림의 버퍼링을 제공하는 클래스입니다. 버퍼링은 출력 스트림이 바로 전송되는 것이 아니라, 일정한 크기의 버퍼에 모아두었다가 한 번에 전송함으로써 입출력 성능을 향상시키는 기능입니다. 이 클래스는 생성자에서 지정된 출력 스트림에 데이터를 출력하기 전에 먼저 내부 버퍼에 데이터를 저장합니다. 이렇게 하면 매번 출력할 때마다 파일에 접근하는 비용이 줄어들어 성능이 향상됩니다. 또한 flush() 메서드를 호출하여 내부 버퍼에 있는 모든 데이터를 출력 스트림에 즉시 전송할 수 있습니다.

따라서 DataOutputStream 클래스는 이진 형식의 데이터를 출력하기 위한 것이며, BufferedOutputStream 클래스는 출력 성능을 향상시키기 위한 것입니다. 필요에 따라 두 클래스를 조합하여 사용할 수도 있습니다. 예를 들어, DataOutputStream으로 이진 형식의 데이터를 출력하면서, BufferedOutputStream으로 출력 성능을 향상시킬 수 있습니다.

 

 

버퍼가 불필요한 경우

 

 

  1. 입출력 대상의 크기가 작은 경우

    입출력 대상의 크기가 작은 경우작은 크기의 데이터를 출력할 때는, 출력 스트림이나 버퍼가 큰 의미가 없습니다. 이 경우에는 입출력 대상의 크기보다 큰 버퍼를 사용하거나, 버퍼를 사용하지 않는 것이 오히려 성능이 더 좋을 수 있습니다.

  2. 입출력 대상이 빈번하게 발생하는 경우

    입출력 대상이 빈번하게 발생하면, 버퍼가 가득 차기 전에 매번 출력을 수행해야 하므로, 버퍼링이 더 이상 성능을 향상시키지 못할 수 있습니다. 이 경우에는 버퍼 크기를 줄이거나, 버퍼를 사용하지 않는 것이 성능 향상에 더 효과적일 수 있습니다.
  3. 입출력 속도가 매우 빠른 경우

    입출력 속도가 매우 빠른 경우에는, 버퍼링이 오히려 성능을 저하시킬 수 있습니다. 이 경우에는 버퍼링을 사용하지 않는 것이 성능 향상에 더 효과적일 수 있습니다.

  4. 버퍼 크기가 너무 큰 경우

    버퍼 크기가 너무 큰 경우에는, 메모리를 과도하게 사용하게 되어 성능 저하를 초래할 수 있습니다. 이 경우에는 적절한 버퍼 크기를 선택하여 성능을 향상시켜야 합니다.




 

문자 스트림/보조 스트림

  • Reader (문자 스트림: Character Stream): 문자, 텍스트 형태의 데이터 입력 처리를 위한 스트림(텍스트, 웹페이지, 키보드 등)

 

  • Writer (문자 스트림: Character Stream): 문자, 텍스트 형태의 데이터 출력 처리를 위한 스트림(텍스트, 웹페이지, 키보드)

 

 

InputStreamReader, OutputStreamWriter

 

InputStream을 통해 InputStreamReader 객체를 생성, BufferReader을 통해 읽을 수 있음

  • 바이트 기반 스트림을 문자 기반 스트림처럼 쓸 수 있게 해줌
  • 인코딩(encoding)을 변환하여 입출력 할 수 있게 해줌
  • 인코딩 변환하기
InputStream fis = new FileInputStream("C:\\javaStudy\\file\\MS949.txt");
// 맥 사용자들은 /로 경로만 설정해주면 됨
InputStreamReader isr = new InputStreamReader(fis, "MS949");

 

바이트 단위 입출력 스트림 : 그림, 멀티미디어, 문자등 모든 종류의 데이터들을 주고받을 수 있습니다.

문자 단위 입출력 스트림 : 오로지 문자만 주고받을 수 있게 특화되어 있습니다.

 

 

InputStream

InputStream은 바이트 기반 입력 스트림의 최상위 클래스로 추상 클래스입니다. 모든 바이트 기반 입력 스트림은 이 클래스를 상속받아서 만들어 집니다. InputStream 클래스에는 바이트 기반 입력 스트림이 기본적으로 가져야 할 메소드들이 정의 되어 있습니다.

 

 메서드 설명 
 read() 입력 스트림으로부터 1바이트를 읽고 읽은 바이트를 리턴합니다. 
 read(byte[ ] b)  입력 스트림으로부터 읽은 바이트들을 매개값으로 주어진 바이트 배열b에 저장하고 실제로 읽은 바이트 수를 리턴합니다.
 read(byte[] b, int off, int len) 입력 스트림으로부터 len개의 바이트만큼 읽고 매개값으로 주어진 바이트 배열 b[off]부터 len개까지 저장합니다. 그리고 실제로 읽은 바이트 수인 len개를 리턴합니다. 만약 len개를 모두 읽지 못하면 실제로 읽은 바이트 수를 리턴합니다. 
 close() 사용한 시스템 자원을 반납하고 입력스트림을 닫습니다. 

 

 

OutputStream

OutputStream은 바이트 기반 출력 스트림의 최상위 클래스로 추상클래스입니다. 모든 바이트 기반 출력 스트림 클래스는 이 클래스를 상속받아서 만들어집니다. OutputStream 클래스에는 모든 바이트 기반 출력 스트림이 기본적으로 가져야 할 메소드가 정의되어 있습니다.

 

 메서드 설명 
 write(int b)  출력 스트림으로부터 1바이트를 보냅니다.(b의 끝 1바이트) 
 write(byte[ ] b)  출력 스트림으로부터 주어진 바이트 배열 b의 모든 바이트를 보냅니다.
 write(byte[ ] b, int off, int len)  출력 스트림으로 주어진 바이트 배열 b[off]부터 len개까지의 바이트를 보냅니다. 
 flush()   버퍼에 잔류하는 모든 바이트를 출력합니다. 
 close()  사용한 시스템 자원을 반납하고 출력 스트림을 닫습니다. 

 

 


(1) 1byte 입.출력



1byte 출력

콘솔 출력용
- FileOutputStream fos = new FileOutputStream(FileDescripter.out);
- BufferedOutputStream bos = new BufferedOutputStream(fos);
- DataOutputStream dos = new DataOutputStream(bos);
- dos.write...


파일 출력용
- File file = new File("파일명");
- FileOutputStream fos = new FileOutputStream(file);
- BufferedOutputStream bos = new BufferedOutputStream(fos);
- DataOuputStream dos = new DataOutputStream(bos);
- dos.write...


 네트워크 출력용
- Socket soc = new Socket(...);
- BufferedOutputStream bos = new BufferedOutputStream(soc.getOutputStream);
- DataOutputStream dos = new DataOutputStream(bos);
- dos.write...


1byte 입력


 콘솔 입력
- FileInputStream fis = new FileInputStream(FileDescripter.in);
- BufferedInputStream bis = new BufferedInputStream(fis);
- DataInputStream dis = new DataInputStream(bis);
- dis.read...


파일 입력
String filePath = input.getHeadMsg().getServiceId() + ".xml";
try (BufferedReader br = new BufferedReader(new FileReader(filePath))) {
    String line;
    while ((line = br.readLine()) != null) {
        System.out.println(line);
    }
} catch (IOException e) {
    e.printStackTrace();
}​



네트워크 입력
- Socket soc = new Socket(...);
- BufferedInputStream bis = new BufferedInputstream(soc.getInputStream);
- DataInputStream dis = new DataInputStream(bis);
- dis.read...


(2) 텍스트 입.출력
  텍스트의 입력과 출력은 앞서 본 스트림을 변형시켜 주는 클래스를 사용한다는 것 외에는 다른 점이 없다. 텍스트 스트림은 2byte의 문자 체계를 가진( 한글 ) 형태의 입력가 출력에 사용된다. 기본클래스는 Writer와 Reader가 있다. ( 자세한 건 API 참고 )


2byte 출력.
콘솔 출력용.
- OutputStreamWriter osw = new OutputStreamWriter(System.out);
- BufferedWriter bw = new BufferedWriter(osw);
- PrintWriter pw= new PrintWriter(bw);
- pw.println(...);


파일 출력용.
- File file = new File("파일명");
- FileWriter fw = new FileWriter(file);
- BufferedWriter bw = new BufferedWrier(fw);
- PrintWriter pw - nw PrintWriter(bw);
- pw.println(...);


네트워크 출력용.
- Socket soc = new Socket(...);
- OutputtreamWriter osw = new OutputStreamWriter(soc.getOutputStream());
- BufferedWriter bw = new BufferedWriter(osw);
- PrintWriter pw = new Printriter(bw);
- pw.println(....);


2byte 입력.
키보드 입력용.
- InputStreamReader isr = new InputStreamReader(System.in);
- BufferedReader br = new BufferedReader(isr);
- br.readLine();


파일 입력용.
- File file = new File("파일명");
- FileReader fr = new FileReader(file);
- BufferedReader br = new BufferedReader(fr);
- br.readLine();


네트워크 입력용.
- Socket soc = new Socket(...);
- InputStreamReader isr = new InputStreamReader(soc.getInputStream());
- BufferedReader br = new BufferedReader(isr);
- br.readLine();


(3) 객체의 입.출력
  객체 단위의 입력과 출력도 마찬가지로 스트림을 기반으로 한다. 그러나 이 클래스의 상속관계는 1byte 계열에서 이미 나타났었다. 그래서 여기에서 사용 형식을 바로 살펴보기로 했다. 또한 객체의 입력이나 출력은 콘솔과 키보드를 통해서는 이루어지지 않기 때문에 파일과 네트워크에 대해서만 설명하겠다.


  한가지 주의 해야할 점은 객체의 입,출력에서는 직렬화라는 개념이 반드시 필요하다. 직렬화라는 것은 클래스의 구성을 파일이나 네트워크로 정상적으로 전달하기 위해 형태를 재구성하는 것이라고 생각하면 쉽다. 다시 말해 일렬로 줄을 지어서 전송하여야 한다는 것이다. 이것을 프로그래머가 매번 구현하려고 한다면 이것 또한 너무 힘든 일이 될 것이다. 그랫 자바 측에서는 이러한 번거로움을 해결하기 위하여 java.io.Serializabl이라는 인터페이스를 제공해 주었다. 그래서 우리는 전송하고자 하는 객체에 대해 Serializable을 구현해 주기만 하면 되는데, 이 인터페이스 내부에는 아무런 메서드도 있지 않다. 그래서 재정의할 메서드도 없다. 그냥 클래스의 선언부에서 "implements Serializable"이라고 적어 주기만 하면된다.


객체 출력
파일 출력용
- File file = new File("파일명");
- FileOutputStream fos = new FileOutputStream(file);
- BufferedOutputStream bos = new BufferedOutputStream(fos);
- ObjectOutputStream oos = new ObjectOutputStream(bos);
- oos.writeObject(...);


 네트워크 출력용.
- Socket soc = new Socket(...);
- BufferedOutputStream bos = new BufferedOutputStream(soc.getOutputStream());
- ObjectOutputStream oos = new ObjectOutputStream(bos);
- oos.writeObject(...);


객체 입력
파일 입력용.
- File file = new File("파일명");
- FileInputStream fis = new FileInputStream(file);
- BufferedInputStream bis = new BufferedInputStream(fis);
- ObjectInputStream ois = new ObjectInputStream(bis);
- try{
      Object obj = ois.readObject();
  } catch(ClassNotFoundException ee) {}


네트워크 입력용
- Socket soc = new Socket(...);
- BufferedInputStream bis = new BufferedInputStream(soc.getInputStream());
- ObjectInputStream ois = new ObjectInputStream(bis);
- try{
      Object obj = ois.readObject();
  } catch(ClassNotFoundException ee) {}

 

 

 

 

 

 

 

 

 

 

 

참고

https://noritersand.github.io/java/java-%EA%B0%9D%EC%B2%B4-%EC%A7%81%EB%A0%AC%ED%99%94-serialization/
 
https://coding-factory.tistory.com/281

https://devlog2829.tistory.com/94

https://m.blog.naver.com/slrkanjsepdi/90140711064