Back-end/Java

다시 하자 기초! 입출력(I/O)#2

이안_ian 2019. 5. 8. 21:28
반응형

표준입출력 - System.in, System.out, System.err

표준입출력은 콘솔을 통한 데이터 입력과 콘솔로의 데이터 출력을 의미한다. 자바에서는 표준 입출력을 위해 3가지 입출력 스트림 System.in, System.out, System.err을 제공하는데, 이 들은 자바 어플리케이션의 실행과 동시에 사용할 수 있게 자동적으로 생성되기 때문에 개발자가 별도로 스트림을 생성하는 코드를 작성하지 않고도 사용이 가능하다. 자바를 처음 시작할 때부터 지금까지 줄 곧 사용해온 System.out을 스트림의 생성없이 사용할 수 있었던 것이 바로 이러한 이유다.

File

파일은 기본적이면서도 가장 많이 사용되는 입출력 대상이기 때문에 중요하다. 자바에서는 File클래스를 통해서 파일과 디렉토리를 다룰 수 있도록 하고 있다. 그래서 File인스턴스는 파일일 수도 있고 디렉토리일 수도 있다. 앞으로 File클래스의 생성자와 메서드를 관련된 것들 끼리 나누어서 예제와 함께 설명하고자 한다. 먼저 File의 생성자와 메서드를 알아보자.

위 표를 보면 알 수 있는 것과 같이 파일의 경로와 디렉토리나 파일의 이름을 구분하는데 사용 되는 구분자가 OS마다 다를 수 있기 때문에, OS독립적으로 프로그램을 작성하기 위해서는 반드시 위의 멤버변수들을 이용해야한다. 만일 윈도우에서 사용하는 구분자를 코드에 직접 적어 놓았다면, 이 코드는 다른 OS에서는 오류를 일으킬 수 있다.

절대경로(absolute path)는 파일시스템의 루트(root)로부터 시작하는 파일의 전체경로를 의미한다. OS에 따라 다르지만, 하나의 파일에 대해 둘 이상의 절대경로가 존재할 수 있다. 현재 디렉토리를 의미하는 '.'와 같은 기호나 링크를 포함하고 있는 경우가 이에 해당한다. 그러나 정규경로(canonical path)는 기호나 링크 등을 포함하지 않는 유일한 경로를 의미한다.

 

예제에서 사용된 File f = new File("c:\\jdk1.8\\word\\ch15", "FileEx1.java");

대신에 아래와같은 생성자를 이용할 수 있다.

 

File dir = new File("c:\\jdk1.8\\word\\ch15");

File f = new File(dir, "FileEx1.java");

 

한 가지 더 알아두어야 할 것은 File인스턴스를 생성했다고 해서 파일이나 디렉토리가 생성되는 것은 아니라는 것이다. 파일명이나 디렉토리명으로 지정된 문자열이 유효하지 않더라도 컴파일 에러나 예외를 발생시키지 않는다. 새로운 파일을 생성하기 위해서는 File인스턴스를 생성하고 출력스트림을 생성하거나 createNewFile()을 호출해야한다.

1. 이미 존재하는 파일을 참조할 때
File f = new File("c:\\jdk1.8\\word\\ch15", "FileEx1.java");

2. 기존에 없는 파일을 생성할 때
File f = new File("c:\\jdk1.8\\word\\ch15", "FileEx1.java");
f.createNewFile();

boolean canExecute() 실행할 수 있는 파일인지 검사한다.
int compareTo(File pathname) 지정된 파일과 비교한다.
long lastModified() 파일의 마지막으로 수정된 시간을 지정된 시간으로 반환
boolean mkdir() 파일에 지정된 경로로 디렉토리를 생성, 성공하면 true
boolean setExecitable(boolean executable) 파일의 속성을 변경한다.소유자만 해당 속성을 변경 가능
boolean setLastModified(long t) 파일의 마지막으로 수정된 시간을 지정된 시간(t)으로 변경
Path toPath() 파일을 Path로 변환해서 반환
URI toURI() 파일을 URI로 변환해서 반환

직렬화(Serialization)

객체를 컴퓨터에 저장했다가 다음에 다시 꺼내 쓸 수는 없을지 또는 네트웍을 통해 컴퓨터 간에 서로 객체를 주고받을 수는 없을까라고 고민해본적이 있는가? 가능하다. 지금부터 배울 직렬화가 이런일을 가능하게 만들어준다.

직렬화란 객체를 데이터 스트림으로 만드는 것을 말한다. 다시 얘기하면 객체에 저장된 데이터를 스트림에 쓰기 위해 연속적인 데이터로 변환하는 것을 말한다. 반대로 스트림으로부터 데이터를 읽어서 객체를 만드는 것을 역직렬화라고 한다.

 

용어 때문에 어렵게 느껴질 수 있는데 사실 객체를 저장하거나 전송하려면 당연히 이렇게 할 수 밖에 없다. 객체는 클래스에 정의된 인스턴스변수들의 집합이다. 객체에는 클래스변수나 메서드가 포함되지 않는다. 객체는 오직 인스턴스변수들로만 구성되어 있다. 전에는 이해를 돕기 위해 객체를 생성하면 인스턴스변수와 메서드를 함께 그리곤 했지만 사실 객체에는 메서드가 포함되지 않는다. 인스턴스변수는 인스턴스마다 다른값을 가질수 있어야하기에 별도의 메모리공간이 필요하지만 메서드는 변하는 것이 아니라서 메모리를 낭비해가면서 인스턴스마다 같은 내용의 코드(메서드)를 포함시킬 이유가 없다.

 

어떤 객체를 저장하고자 한다면, 현재 객체의 모든 인스턴스변수의 값을 저장하기만 하면된다. 그리고 저장했던 객체를 다시 생성하려면, 객체를 생성한 후에 저장했던 값을 읽어서 생성한 객체의 인스턴스변수에 저장하면 되는 것이다.

클래스에 정의된 인스턴스변수가 기본형일 때는 인스턴스변수의 값을 저장하는 일이 간단하지만, 인스턴스변수의 타입이 참조형일 때는 그리 간단하지 않다. 예를 들어 인스턴스변수의 타입이 배열이라면 배열에 저장된 값들도 모두 저장되어야 할 것이다. 그러나 우리는 객체를 어떻게 직렬화해야 하는지 전혀 고민할 필요가 없다. 다만 객체를 직렬화/역직렬화할 수 있는 ObjectInputStream과 ObjectOutputStream을 사용하는 방법만 알면된다.

 

ObjectInputStream, ObjectOutputStream

직렬화(스트림에 객체를 출력)에는 ObjectOutputStream이 사용 

역직렬화(스트림으로부터 객체를 입력)에는 ObjectInputStream을 사용한다.

 

두 스트림은 기반스트림을 필요로 하는 보조스트림이다. 그래서 객체를 생성할 때 입출력할 스트림을 지정해줘야한다.

//직렬화 하는 방법
FileOutputStream fos = new FileOutputStream("objectfile.ser");
ObjectOutputStream out = new ObjectOutputStream(fos);

out.writeObject(new UserInfo());

위 코드는 objectfile.ser이라는 파일에 UserInfo객체를 직렬화하여 저장한다. 출력할 스트림을(FileOutputStream)을 생성해서 이를 기반스트림으로 하는 ObjectOutputStream을 생성한다.

ObjectOutputStream의 writeObject(Object obj)를 사용해서 객체를 출력하면, 객체가 파일에 직렬화되어 저장된다.

역직렬화 역시 간단하다. 직렬화할 때와는 달리 입력스트림을 사용하고 readObject()를 사용하여 저장된 데이터를 읽기만 하면 객체로 역직렬화된다. 다만 readObject()의 반환타입이 Object이기 때문에 객체 원래의 타입으로 형변환 해야함

//역직렬화 하는 방법
FileInputStream fis = new FileInputStream("objectfile.ser");
ObjectInputStream in = new ObjectInputStream(fis);

UserInfo info = (UserInfo)in.readObject();

직렬화가 가능한 클래스 만들기 - Serializable, transient

직렬화가 가능한 클래스를 만드는 방법은 간단하다. Serializable인터페이스를 구현하도록 하면된다.

public class SuperUserInfo implements Serializable {
 String name;
 String password;
}

아니면 Serializable을 구현한 클래스를 상속받으면 따로 구현 하지 않아도 된다. 그리고 또하나 주의 해야되는 점은 위와같은 클래스에 인스턴스 변수로 Object obj = new Object();는 사용할 수 없다. 왜냐하면 최고조상인 Object는 직렬화처리가 안되어있기 때문이다. 그래서 직렬화가 가능한 String같은 객체를 이용해서 저장을 해야한다.

Object obj = new String("abc");

 

그리고 직렬화가 안되는 객체에 대한 참조를 포함하고 있다면, 제어자 transient를 붙여서 직렬화 대상에서 제외되도록 할 수 있다. 또는 패스워드와 같이 보안상 직렬화되면 안 되는 값에 대해서 transient를 사용할 수 있다. 그리고 그 참조형 값은 null이된다.

직렬화가능한 클래스의 버전관리 

직렬화된 객체를 역직렬화할 때는 직렬화 했을 때와 같은 클래스를 사용해야한다.

그러나 클래스 이름이 같더라도 클래스의 내용이 변경된 경우 역직렬화에 실패하며 다음과 같은 예외가 발생한다.

클래스의 버전이 같아야하는데 다르다는 것이다. 객체가 직렬화될 때 클래스에 정의된 멤버들의 정보를 이용해서 serialVersionUID라는 클래스의 버전을 자동생성해서 직렬화 내용에 포함된다. 그래서 역직렬화 할 때 클래스의 버전을 비교함으로써 직렬화할 때의 클래스의 버전과 일치하는지 확인할 수 있는 것

 

그러나 static변수나 상수 또는 transient가 붙은 인스턴스변수가 추가되는 경우에는 직렬화에 영향을 미치지 않기 때문에 클래스의 버전을 다르게 인식하도록 할 필요는 없다. 

이렇게 클래스 내에 serialVersionUID를 정의해주면, 클래스내의 내용이 바뀌어도 클래스의 버전이 자동생성된 값으로 변경되지않는다. serialver.exe 뒤에 serialVersionUID를 얻고자 하는 클래스의 이름만 적어주면 클래스의 serialVersionUID를 알아낼 수 있다.

 

 

 

 

 

반응형