---
title: "Java 정리 29"
date: "2018-12-21"
tags: ["Java"]
status: archived
verified: false
---

## Java 정리 29 - IO Stream(3), 실행가능한 JAR파일 만들기

---

### 파일복사

![01](/md/legacy-assets/java/29/01.webp)

```java
// 복사될 파일 경로명을 설정하는 예제
public class Test {
     public static void main(String[] args) {
           
           String s =  "C:/Users/owner/youngRepositories/SSangYoung/dev/temp/java_read.txt";
           StringBuilder sb = new StringBuilder(s);
           
           // 파일명에 '.'이 여러개 나올 수 있으므로 파일 확장자는 마지막 .뒤에 문자열이다.
           // 따라서 lastIndexOf를 사용해서 확장자를 잘라내야  함.
           // StringBuilder에 삽입하는 insert method로 .있는 부분부터 "_bak"을 추가함
          System.out.println(sb.insert(s.lastIndexOf("."),"_bak"));
     }
}
```


* **FileInputStream, FileOutputStream**으로 파일 복사 구현
     * hdd에서 데이터를 읽을때 access arm이 움직여 헤드로 데이터를 읽음
     * **read 메서드**는 헤드가 얼마나 많이 읽을 수 있는지와는 상관없이 **1byte씩 읽어온다.**
          * 오버로드된 read 메서드를 보면 파라미터로 byte배열을 주면 한번에 배열의 크기만큼 읽어들일 수 있음

![02](/md/legacy-assets/java/29/02.webp)

```java
byte[] b = new byte[512];
int len = 0;

// 읽어들일 때 byte배열의 크기만큼 데이터가 없을 수 있다.
len = fis.read(b); // int로 읽어들인 byte의 수를 반환
```

![03](/md/legacy-assets/java/29/03.webp)

```java
// byte배열의 크기만큼 데이터가 존재하지 않을 수 있기 때문에 읽어들인 길이만큼만 출력
while(len = fis.read(b) != -1) {

    fos.write(b, 0, len); // 0번부터 len까지 출력
}
```

```java
// 파일복사 예제
@SuppressWarnings("serial")
public class FileCopy extends JFrame implements ActionListener {
     private JButton jb;
     private JProgressBar jpb;
     
     public FileCopy() {
           super("파일 복사");
           
           jb = new JButton("파일 선택");
           jpb = new JProgressBar(0, 100);
           jpb.setString("진척도");
           jpb.setStringPainted(true);
           jpb.setValue(50);
           
           JPanel jp = new JPanel();
           jp.add(jb);
           
           add("Center",jp);
           add("South",jpb);
           
           jb.addActionListener(this);
           
           setBounds(100, 100, 500, 200);
           setVisible(true);
           setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
     }
     
     @Override
     public void actionPerformed(ActionEvent e) {
           FileDialog fd = new FileDialog(this, "선택",  FileDialog.LOAD);
           fd.setVisible(true);
           
           String path = fd.getDirectory();
           String name = fd.getFile();
           
           if (path != null) { // 파일 선택시
                File file = new File(path+name);
                
                try {
                     copy(file);
                     JOptionPane.showMessageDialog(this, file+"  복사 성공.");
                } catch (FileNotFoundException fnfe) {
                     JOptionPane.showMessageDialog(this,  "파일이 존재하지 않습니다.");
                     fnfe.printStackTrace();
                } catch (IOException ie) {
                     JOptionPane.showMessageDialog(this,  "입출력 작업에 문제가 발생.");
                     ie.printStackTrace();
                }
           }
     }
     
     public void copy(File file) throws FileNotFoundException,  IOException {
           int selectValue = JOptionPane.showConfirmDialog(this,  "파일을 복사하시겠습니까?");

           switch(selectValue) {
           case JOptionPane.OK_OPTION :
                StringBuilder copyFileName = new  StringBuilder(file.getAbsolutePath());
                copyFileName.insert(copyFileName.lastIndexOf("."),  "_bak");
                
                FileInputStream fis = null;
                FileOutputStream fos = null;
                
                try {
                     // 원본 파일에 스트림 연결
                     fis = new FileInputStream(file);
                     fos = new  FileOutputStream(copyFileName.toString());
                     
                     // 파일과 연결된 스트림에서 값을 얻는다.
                     // HDD가 읽어들이는 크기를 무시하고  1byte씩 읽어들여 사용
                     int temp = 0;
                     long fileLen = file.length();
                     int i = 0;
                     while( (temp = fis.read()) != -1 ) {
                           // 읽어 들인 내용을 _bak가 붙은 파일을 생성하여 출력(복사)
                           fos.write(temp);
                           fos.flush();
                          jpb.setValue((int)(i/(double)fileLen*100));
                            // Thread를 사용해야 프로그레스바 구현가능, 여기선 값만 찍음
                           System.out.println(jpb.getValue());
                           i++;
                     }
                     
                     /*// HDD가 읽어들이는 크기를 그대로 사용
                     byte[] temp = new byte[512];
                     int len = 0;
                     while ((len = fis.read(temp)) != -1) {
                           
                           fos.write(temp,0,len);
                           fos.flush();
                     }*/
                     
                } finally {
                     if (fis != null) fis.close();
                     if (fos != null) fos.close();
                }
           }
     }
     public static void main(String[] args) {
           new FileCopy();
     }
}

```

---

### ObjectStream 

* **객체(instance)는 Stream을 타고 JVM 밖으로 나갈 수 없다.**
     * 객체의 주소는 4byte이기는 하나 객체 자체의 **크기를 알 수 없기 때문**에 쪼갤 수 없다.
* **기본형 데이터형은 Stream을 타고 JVM 밖으로 나갈 수 있다.**
     * **크기를 알 수 있기 때문**에 쪼개져서 나갈 수 있다.
* **8bit Stream**
     * **ObjectInputStream**
          * JVM 외부 객체를 읽기
     * **ObjectOutputStream**
          * JVM 외부에 객체를 내보내기
* 객체가 **직렬화(Serialization)**되면 JVM 외부로 나갈 수 있다.
     * 객체는 **Serializable이란 인터페이스를 구현**하면 직렬화 될 수 있다.
     * 직렬화된 객체는 **instanceof**를 사용해서 판별

![04](/md/legacy-assets/java/29/04.webp)

```java
FileOutputStream fos = new FileOutputStream(파일객체);
ObjectOutputStream oos = new ObjectOutputStream(fos);


oos.writeObject(직렬화가된객체); // 스트림에 객체를 기록
oos.flush(); // 목적지로 분출
```


* **객체를 byte단위의 일정크기로 자름는 것이 Marshalling**
     * byte를 직렬화(일렬로) stream에 내보냄, Stream을 타고 데이터는 나뉜 byte로 출력되어 저장됨
* 직렬화를 방지하려면 변수앞에 접근지정자로 **transient**를 붙이면 그 변수는 JVM 외부로 나갈 수 없게된다.

```java
transient String s; // 
```

![05](/md/legacy-assets/java/29/05.webp)

* **파일을 읽어와 객체로 다시 만들어 사용하는 것이 Unmarshaling**

```java
FileInputStream fis = new FileInputStream("파일명");
ObjectInputStream ois = new ObjectInputStream(fis);

B b = (B)ois.readObject();
```

```java
// 데이터를 가지고 있는 클래스로 직렬화 대상 클래스
public class UserData implements Serializable {
     
     private int age;
     private double weight;
     private String name;
     
     public UserData() {
          
     }
     public UserData(int age, double weight, String name)  {
          super();
          this.age = age;
          this.weight = weight;
          this.name = name;
     }
     public int getAge() {
          return age;
     }
     public void setAge(int age) {
          this.age = age;
     }
     public double getWeight() {
          return weight;
     }
     public void setWeight(double weight) {
          this.weight = weight;
     }
     public String getName() {
          return name;
     }
     public void setName(String name) {
          this.name = name;
     }
     
     @Override
     public String toString() {
          return "UserData [age=" + age + ", weight=" +  weight + ", name=" + name + "]";
     }
}
```

```java
// UserData를 객체로 생성, 출력스트림을 통해 파일로 내보내고 읽어들이기
public class UseObjectStream {
     public void useObjectOutputStream() throws  IOException {
          ObjectOutputStream oos = null;
          
          try {
              File file = new  File("C:/Users/owner/youngRepositories/SSangYoung/dev/temp/obj_data.dat");
              // 객체(JVM)를 직렬화하여 목적지 (HDD  File)로 내보내는 스트림
              oos = new ObjectOutputStream(new  FileOutputStream(file));
              // 객체는 직렬화가 되지 않으면 스트림으로  내보낼 수 없다.
              UserData ud = new  UserData(26,70.1,"이재현");
              oos.writeObject(ud); // 객체를 직렬화하여  스트림에 쓴다.
              oos.flush(); // 스트림에 기록된 내용을  목적지로 분출한다.
              System.out.println("객체를 파일로 기록!");
          } finally {
              if (oos != null) { oos.close(); }
          }
     }
     
     public void useObjectInputStream() throws  IOException, ClassNotFoundException {
          // 객체를 읽기위한 스트림 열기
          ObjectInputStream ois = null;
          
          try {
              File file = new  File("C:/Users/owner/youngRepositories/SSangYoung/dev/temp/obj_data.dat");
              ois = new ObjectInputStream(new  FileInputStream(file));
              UserData ud = (UserData)ois.readObject();
              
              System.out.println(ud.toString());
          } finally {
              if (ois != null) ois.close();
          }
     }
     
     public static void main(String[] args) {
     
          UseObjectStream uos = new UseObjectStream();
          
          try {
              uos.useObjectOutputStream();
          } catch (IOException ie) {
              ie.printStackTrace();
          }
          System.out.println("=================================================");
          try {
              uos.useObjectInputStream();
          } catch (ClassNotFoundException e) {
              e.printStackTrace();
          } catch (IOException e) {
              e.printStackTrace();
          }
     }
}
```

![06](/md/legacy-assets/java/29/06.webp)

```java
// transient로 직렬화를 막기
public class UserData implements Serializable {
     
     private int age;
     private transient double weight;
     private transient String name;
```

![07](/md/legacy-assets/java/29/07.webp)

---

### serialVersionUID

![08](/md/legacy-assets/java/29/08.webp)


* JVM 밖으로 내보내고 들어오는 instance는 코드의 변형 또는 가공이 될 수 있기 때문에 내보냈던 코드를 검증하기 위해 **serialVersionUID**를 사용
     * serialVersionUID는 클래스의 **hashcode**로 사용
     * 대표적인 예가 공인인증서

![09](/md/legacy-assets/java/29/09.webp)

* serialVersionUID 추가 후 파일 내보내고 serialVersionUID값변경 후 다시 읽어들이려고하면 에러발생

![10](/md/legacy-assets/java/29/10.webp)

```java
// 클래스 객체를 JVM 밖으로 내보낸다면 serial Version ID를 생성해준다.
// 클래스를 안내보낸다면 SuppressWarning처리를 해주면 됨

@SuppressWarnings("serial")
public class UserData implements Serializable {
```

---

### 실행가능한 jar파일 만들기

* 프로젝트 선택 - Export - **Runnable JAR File Export**
  * **Launch configuration**  : main method가진 클래스 선택
  * **Export destination** : 파일을 내보낼 위치, 파일명 지정

![11](/md/legacy-assets/java/29/11.webp)


* **실행을 위해 bat파일 생성**
  * java_memo_env.bat (기존 env.bat 복사)
  * java_memo_run.bat (기존 eclipse.bat 복사)

```bash
# java_memo_env
rem path - 특정프로그램(javac.exe, java.exe)를 설치된 경로에 상관없이 사용하기위한 path
rem xxx_home - 프로그램끼리 경로를 참조하기 위해 설정하는 path

set dev_home=C:\Users\owner\youngRepositories\SSangYoung\dev
set java_home=%dev_home%\Java\jdk1.8.0_191

set path=%java_home%\bin;
```

```bash
# java_memo_run
rem path를 사용하기위해 path가 설정된 파일을 호출
call java_memo_env.bat

rem 설정된 경로에서 필요한 실행파일을 실행
rem java -jar 실행가능한 jar의 이름
java -jar java_memo.jar
```

![12](/md/legacy-assets/java/29/12.webp)

---

### 메모장 saveFontConfig 수정

* 기존 fontConfig를 내보냈던걸 ObjectOutputStream으로 구현
* Font가 Serializable을 구현한 클래스이므로 객체를 밖으로 빼낼 수 있다.
     * 사용자가 악의적인 목적을가지고 변경하기 힘들어진다.

```java
private void saveFontConfig() throws IOException {
     
     ObjectOutputStream oos = null;
     
     try {
          Font fontTemp =  mf.getLblPreview().getFont();
          
          oos = new ObjectOutputStream(new  FileOutputStream(
                    "C:/Users/owner/youngRepositories/SSangYoung/dev/temp/fontConfig.dat"));
          
          oos.writeObject(fontTemp);
          oos.flush();

     } finally {
          if (oos != null) oos.close();
     }
}
```

```java
public Font readFontConfig() throws IOException,  ClassNotFoundException {
     
     Font fontConfig = null;
     ObjectInputStream ois = null;
     
     try {
          
          ois = new ObjectInputStream(new  FileInputStream(
                    "C:/Users/owner/youngRepositories/SSangYoung/dev/temp/fontConfig.dat"));
          fontConfig = (Font)ois.readObject();
          
     } finally {
          if (ois != null) ois.close();
     }
     
     return fontConfig;
}
```
