• Home
  • About
    • Young's Github Pages photo

      一日不作一日不食

    • About
    • Github
  • Posts
    • All Posts
    • All Tags
  • Projects

Java 정리 29

21 Dec 2018

Reading time ~6 minutes

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


파일복사

01

// 복사될 파일 경로명을 설정하는 예제
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

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

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

03

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

    fos.write(b, 0, len); // 0번부터 len까지 출력
}
// 파일복사 예제
@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

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


oos.writeObject(직렬화가된객체); // 스트림에 객체를 기록
oos.flush(); // 목적지로 분출
  • 객체를 byte단위의 일정크기로 자름는 것이 Marshalling
    • byte를 직렬화(일렬로) stream에 내보냄, Stream을 타고 데이터는 나뉜 byte로 출력되어 저장됨
  • 직렬화를 방지하려면 변수앞에 접근지정자로 transient를 붙이면 그 변수는 JVM 외부로 나갈 수 없게된다.
transient String s; // 

05

  • 파일을 읽어와 객체로 다시 만들어 사용하는 것이 Unmarshaling
FileInputStream fis = new FileInputStream("파일명");
ObjectInputStream ois = new ObjectInputStream(fis);

B b = (B)ois.readObject();
// 데이터를 가지고 있는 클래스로 직렬화 대상 클래스
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 + "]";
     }
}
// 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

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

07


serialVersionUID

08

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

09

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

10

// 클래스 객체를 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

  • 실행을 위해 bat파일 생성
    • java_memo_env.bat (기존 env.bat 복사)
    • java_memo_run.bat (기존 eclipse.bat 복사)
# 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;
# java_memo_run
rem path를 사용하기위해 path가 설정된 파일을 호출
call java_memo_env.bat

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

12


메모장 saveFontConfig 수정

  • 기존 fontConfig를 내보냈던걸 ObjectOutputStream으로 구현
  • Font가 Serializable을 구현한 클래스이므로 객체를 밖으로 빼낼 수 있다.
    • 사용자가 악의적인 목적을가지고 변경하기 힘들어진다.
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();
     }
}
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;
}


Java