본문 바로가기

Java의 정석 : 3rd Edition

[Java의 정석 - 연습문제] Chapter15. 입출력(I/O)

Java의 정석 : 3rd Edition, 2016을 개인 학습용으로 정리한 내용입니다.
"ppt 파일 자료, 연습문제"를 학습 용도로 배포할 수 있음을 고지하셨습니다.
저자님께 감사드립니다.

 

 

 

 

[15-1] 커맨드라인으로부터 파일명과 숫자를 입력받아서,

입력받은 파일의 내용을 처음부터 입력받은 숫자만큼의 라인을 출력하는 프로그램(FileHead.java)을 작성하라.

[Hint] BufferedReaderreadLine()을 사용하라.

C:\jdk1.8\work\ch15>java FileHead 10
USAGE: java FileHead 10 FILENAME

C:\jdk1.8\work\ch15>java FileHead 10 aaa
aaa은/는 디렉토리이거나, 존재하지 않는 파일입니다.

C:\jdk1.8\work\ch15>java FileHead 10 FileHead.java
1:import java.io.*;
2:
3:class FileHead
4:{
5:    public static void main(String[] args)
6:    {
7:        try {
8:            int line = Integer.parseInt(args[0]);
9:            String fileName = args[1];
10:

C:\jdk1.8\work\ch15>​

 

답 :

import java.io.*;

public class FileHead {
    public static void main(String[] args) {
        int lineNum = Integer.parseInt(args[0]);  // 출력할 라인
        File fileName = new File(args[1]);  // 출력할 파일명

        FileReader fr = null;
        BufferedReader br = null;

        try {
            if (!fileName.isDirectory() || !fileName.exists()) {
                fr = new FileReader(fileName);
                br = new BufferedReader(fr);

                String line = "";

                for (int i = 1; (line = br.readLine()) != null && i <= lineNum; i++) {
                    System.out.println(i + ": " + line);
                }
            } else {
                throw new FileNotFoundException(fileName + "은/는 디렉토리이거나, 존재하지 않는 파일입니다.");
            }
        } catch (FileNotFoundException e) {
            System.out.println(e.getMessage());
        } catch (IOException e) {
            System.out.println("USAGE: java FileHead 10 FILENAME");
        } finally {
            try {
                if (br != null) br.close();
            } catch (IOException e) { }
        }
    }
}

해설을 본 후 FileNotFoundException 예외를 처리하는 코드를 추가함.

1) args를 통해 입력받은 출력할 라인과 파일명을 각각 lineNum, fileName에 저장한다.

2) 파일을 읽기 위해 FileReader를, 이를 라인 단위로 읽기 위해 BufferedReader(입력 성능 향상)를 선언한다.

    FileReader : 문자 기반 스트림. 파일로부터 텍스트 데이터를 읽는다.(FileReader(읽기), FileWriter(쓰기))

    BufferedReader : 문자 기반 보조 스트림. 버퍼를 사용하여 읽는다.(BufferedReader(읽기), BufferedWriter(쓰기))

3) 데이터를 입출력하는 과정 도중에 예외가 발생할 수 있으므로 예외 처리(try-catch)를 해야 한다.

  3-1) fileName이 디렉토리인지 or 존재하지 않는 파일인지 확인한다.

         디렉토리이거나 존재하지 않는 파일이면 FileNotFoundException가 발생한다.

  3-2) 앞서 선언해뒀던 FileReader BufferedReader를 생성한다.

  3-3) 반복문을 이용하여 lineNum만큼 파일의 내용을 라인 단위로 읽어 line에 저장 후 출력한다.

          readLine() : 더 이상 읽을 라인이 없으면 null을 반환한다.

4) 사용하던 자원은 close()를 호출하여 반환해줘야 한다.

  4-1) 예외의 발생 여부와 관계 없이 항상 수행되는 finally블럭에서 스트림을 닫아줘야 한다.

          try-catch문 : 스트림을 닫기 전 예외가 발생하면 해당 블럭을 빠져나오게 되므로 스트림을 닫을 수 없다.

  4-2) 보조 스트림을 닫으면 기반 스트림의 close()가 자동으로 호출되기 때문에 따로 호출하지 않아도 된다.

(line = br.readLine()) != null
line = br.readLine() // readLine()으로 읽은 라인(문자열)을 line에 저장한다.
line != null               // line에 저장된 값이 null이 아닌지 비교한다.
[실행결과]
Edit Configurations... or Modify Run Configuration...
Working directory : java 파일 경로(예: D:\JAVA\javajungsuk3\src\Exercise\Exercise15)

Program arguments : 10 test (파일이 존재하지 않을 때)
test (지정된 파일을 찾을 수 없습니다)​

Program arguments : 10 file (디렉토리일 때)
file은/는 디렉토리이거나, 존재하지 않는 파일입니다.​

Program arguments : 10 FileHead.java (올바르게 작성했을 때)
1: package Exercise.Exercise15;
2: 
3: import java.io.*;
4: 
5: public class FileHead {
6:     public static void main(String[] args) {
7:         int lineNum = Integer.parseInt(args[0]);  // 출력할 라인
8:         File fileName = new File(args[1]);  // 출력할 파일명
9: 
10:         FileReader fr = null;​

 

 

 

 

[15-2] 지정된 이진 파일의 내용을 실행 결과와 같이 16진수로 보여주는 프로그램(HexaViewer.java)을 작성하라.

[Hint] PrintStreamprintf()를 사용하라.

C:\jdk1.8\work\ch15>java HexaViewer HexaViewer.class
CA FE BA BE 00 00 00 31 00 44 0A 00 0C 00 1E 09
00 1F 00 20 08 00 21 0A 00 08 00 22 0A 00 1F 00
23 07 00 24 0A 00 06 00 25 07 00 26 0A 00 08 00
27 0A 00 06 00 28 08 00 29 07 00 2A 0A 00 2B 00
2C 0A 00 08 00 2D 0A 00 08 00 2E 0A 00 06 00 2F
0A 00 08 00 2F 07 00 30 0A 00 12 00 31 07 00 32
01 00 06 3C 69 6E 69 74 3E 01 00 03 28 29 56 01
00 04 43 6F 64 65 01 00 0F 4C 69 6E 65 4E 75 6D
62 65 72 54 61 62 6C 65 01 00 04 6D 61 69 6E 01
00 16 28 5B 4C 6A 61 76 61 2F 6C 61 6E 67 2F 53
...중간 생략

C:\jdk1.8\work\ch15>

 

답 :

import java.io.*;

public class HexaViewer {
    public static void main(String[] args) {
        if (args.length != 1) {
            System.out.println("USAGE: java HexaViewer FILENAME");
            System.exit(0);
        }

        String fileName = args[0];  // 이진 파일

        FileInputStream input = null;  // 입력 스트림
        PrintStream output = null;     // 출력 스트림

        try {
            input = new FileInputStream(fileName);
            output = new PrintStream(System.out);

            int data = 0;
            int i = 0;

            // %02X : 10진 정수 -> 16진 정수
            while ((data = input.read()) != -1) {
                output.printf("%02X ", data);  // System.out.printf()와 동일
                if (++i % 16 == 0) {
                    output.println();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (input != null) input.close();
                if (output != null) output.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

1) 사용자로부터 파일명을 입력받았는지 확인한다. 입력받지 않았다면 시스템을 종료하여 입력을 유도한다.

2) 입력받은 파일명을 fileName에 저장한다.

3) 입력 스트림 FileInputStream과 출력 스트림 PrintStream를 선언한다.

4) 데이터를 입출력하는 과정 도중에 예외가 발생할 수 있으므로 예외 처리(try-catch)를 해야 한다.

  4-1) input : 지정된 파일 이름(name)을 갖는 실제 파일과 연결된 FileInputStream을 생성한다.

  4-2) output : 지정된 출력 스트림을 기반으로 하는 PrintStream인스턴스를 생성한다.

                    : 출력 대상을 화면(System.out)이 아닌 파일로 변경할 때 인자만 바꿔주면 된다.

  4-3) 반복문을 이용하여 읽어올 데이터가 없을 때까지 read()를 통해 읽어온 데이터를 data 저장한다.

    4-3-1) printfformat옵션인 %02X를 사용하여 빈자리를 0으로 채우는 두 자리의 16진수 형태로 출력한다.

    4-3-2) 가독성을 위해 16배수를 갖는 i번째 요소를 출력할 때마다 줄바꿈을 수행한다.

5) 예외의 발생 여부와 관계 없이 항상 수행되는 finally블럭에서 스트림을 닫아 사용한 자원을 반환한다.

printf()의 format옵션
printf()의 format옵션

FileInputStream
: 파일의 내용을 읽어 화면에 출력하기 위한 바이트 기반 스트림(한글이 깨져 출력된다)
: 파일의 내용에 한글이 포함된 경우 문자 기반 스트림인 FileReader를 사용해야 한다.

FileInputStream read()
: 한 번에 1byte(0~255)씩 데이터를 읽는다. 더 이상 읽어 올 데이터가 없으면 -1을 반환한다.
: 입력 소스로부터 버퍼 크기 만큼의 데이터를 읽어 버퍼에 저장해놓는다(반복)
PrintStream
: 데이터를 적절한 문자로 출력하는 바이트 기반 보조 스트림
: 데이터를 다양한 형태로 출력하는 메서드(print, println, printf)를 오버 로딩하여 제공한다.
PrintWriter(JDK1.1) : 보다 향상된 문자 기반 보조 스트림. 다양한 언어의 문자를 처리하는데 적합하다.
[실행결과]
Edit Configurations... or Modify Run Configuration...
Program arguments : HexaViewer.class
Working directory : 이진 파일 경로(예: D:\JAVA\javajungsuk3\out\production\javajungsuk3\Exercise\Exercise15)
CA FE BA BE 00 00 00 34 00 5B 0A 00 0C 00 34 09 
00 35 00 36 08 00 37 0A 00 08 00 38 0A 00 35 00 
39 07 00 3A 0A 00 06 00 3B 07 00 3C 0A 00 08 00 
3D 0A 00 06 00 3E 08 00 3F 07 00 40 0A 00 41 00 
42 0A 00 08 00 43 0A 00 08 00 44 0A 00 06 00 45 
0A 00 08 00 45 07 00 46 0A 00 12 00 47 07 00 48 
...생략​

 

 

 

 

[15-3] 다음은 디렉토리의 요약 정보를 보여주는 프로그램이다.

파일의 개수, 디렉토리의 개수, 파일의 총 크기를 계산하는 countFiles()를 완성하시오.

import java.io.File;

class DirectoryInfoTest {
    static int totalFiles = 0;
    static int totalDirs = 0;
    static int totalSize = 0;

    public static void main(String[] args) {
        if(args.length != 1) {
            System.out.println("USAGE: java DirectoryInfoTest DIRECTORY");
            System.exit(0);
        }

        File dir = new File(args[0]);

        if(!dir.exists() || !dir.isDirectory()) {
            System.out.println("유효하지 않은 디렉토리입니다.");
            System.exit(0);
        }
        
        countFiles(dir);
        
        System.out.println();
        System.out.println("총 " + totalFiles + "개의 파일");
        System.out.println("총 " + totalDirs + "개의 디렉토리");
        System.out.println("크기 " + totalSize + "bytes");
    }

    /*
        (1) 아래의 로직에 맞게 코드를 작성하시오.
            1. dir의 파일 목록(File[])을 얻어온다.
            2. 얻어온 파일 목록의 파일 중
               디렉토리이면, totalDirs의 값을 증가시키고 countFiles()를 재귀 호출한다.
            3. 파일이면, totalFiles를 증가시키고 파일의 크기를 totalSize에 더한다.
    */
    public static void countFiles(File dir) {
    }
}
C:\jdk1.8\work\ch15>java DirectoryInfoTest .

총 786개의 파일
총 27개의 디렉토리
크기 2566228bytes

C:\jdk1.8\work\ch15>

 

답 :

public static void countFiles(File dir) {
    // 1. dir의 파일 목록(File[])을 얻어온다.
    File[] files = dir.listFiles();

    for (int i = 0; i < files.length; i++) {
        // 2. 얻어온 파일 목록의 파일 중 디렉토리이면, totalDirs의 값을 증가시키고 countFiles()를 재귀 호출한다.
        if (files[i].isDirectory()) {
            totalDirs++;
            countFiles(files[i]);
        // 3. 파일이면, totalFiles를 증가시키고 파일의 크기를 totalSize에 더한다.
        } else {
            totalFiles++;
            totalSize += files[i].length();
        }
    }
}

1) listFiles()를 통해 dir(디렉토리)의 파일 목록(디렉토리 포함)을 File배열로 반환받아 files에 저장한다.

2) 반복문과 조건문을 이용하여 files[i]가 디렉토리인지 파일인지 확인한다.

  2-1) isDirectory() : 디렉토리이면 totalDirs 값을 증가시키고 재귀 호출을 통해 디렉토리 내 파일을 처리한다.

  2-2) else or isFile() : 파일이면 totalFiles 값을 증가시키고 파일의 크기를 totalSize에 더한다.

         long length() : 파일의 크기를 반환한다(단위: byte).

[실행결과]
Edit Configurations...
or Modify Run Configuration...
Program arguments : .
Working directory : 폴더 경로(예: D:\JAVA\javajungsuk3)
총 2410개의 파일
총 110개의 디렉토리
크기 27772818bytes
파일 탐색기(속성)
파일 탐색기(속성)

 

 

 

 

[15-4] 커맨드라인으로 부터 여러 파일의 이름을 입력받고,

이 파일들을 순서대로 합쳐서 새로운 파일을 만들어 내는 프로그램(FileMergeTest.java)을 작성하시오.

단, 합칠 파일의 개수에는 제한을 두지 않는다.

병합할 파일(1.txt, 2.txt, ...)의 끝에 개행을 넣어줘야 함.
C:\jdk1.8\work\ch15>java FileMergeTest
USAGE: java FileMergeTest MERGE_FILENAME FILENAME1 FILENAME2 ...

C:\jdk1.8\work\ch15>java FileMergeTest result.txt 1.txt 2.txt 3.txt

C:\jdk1.8\work\ch15>type result.txt
1111111111
2222222222
33333333333333

C:\jdk1.8\work\ch15>java FileMergeTest result.txt 1.txt 2.txt

C:\jdk1.8\work\ch15>type result.txt
1111111111
2222222222

C:\jdk1.8\work\ch15>type 1.txt
1111111111

C:\jdk1.8\work\ch15>type 2.txt
2222222222

C:\jdk1.8\work\ch15>type 3.txt
33333333333333

C:\jdk1.8\work\ch15>

 

답 :

import java.io.*;

public class FileMergeTest {
    public static void main(String[] args) {
        if (args.length < 3) {
            System.out.println("USAGE: java FileMergeTest MERGE_FILENAME FILENAME1 FILENAME2...");
            System.exit(0);
        }

        File mergeFilename = new File(args[0]);

        FileInputStream fis = null;
        FileOutputStream fos = null;

        try {
            fos = new FileOutputStream(mergeFilename);

            for (int i = 1; i < args.length; i++) {
                File file = new File(args[i]);

                if (file.exists() && file.isFile()) {
                    fis = new FileInputStream(file);

                    int data = 0;

                    while((data = fis.read()) != -1) {
                        fos.write(data);
                    }
                } else {
                    throw new FileNotFoundException(file + "은/는 디렉토리이거나, 존재하지 않는 파일입니다.");
                }
            }
        } catch (FileNotFoundException e) {
            System.out.println(e.getMessage());
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (fos != null) fos.close();
                if (fis != null) fis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

1) 사용자로부터 병합 결과를 담을 파일명과 병합할 n개의 파일명을 입력받았는지 확인한다.

2) 입력받은 병합 결과를 담을 파일명으로 File인스턴스를 생성한다.

3) 입력 스트림 FileInputStream과 출력 스트림 FileOutputStream를 선언한다(파일을 입출력하기 위한 스트림).

4) 데이터를 입출력하는 과정 도중에 예외가 발생할 수 있으므로 예외 처리(try-catch)를 해야 한다.

  4-1) mergeFilename(병합 결과를 담을 파일)을 출력 스트림으로 하는 FileOutputStream를 생성한다.

  4-2) 병합할 파일명으로 File 인스턴스를 생성한 후 해당 파일이 존재하는 파일인지 확인한다.

          file이 디렉토리이거나 존재하지 않는 파일이라면 FileNotFoundException가 발생한다.

  4-3) 반복문을 이용하여 읽어올 데이터가 없을 때까지 read()를 통해 읽어온 데이터를 data 저장한다.

          write()를 통해 data를 fos 출력한다.

5) 예외의 발생 여부와 관계 없이 항상 수행되는 finally블럭에서 스트림을 닫아 사용한 자원을 반환한다.

[실행결과]
Edit Configurations... or Modify Run Configuration...
Program arguments : result.txt 1.txt 2.txt 3.txt
Working directory : 병합할 파일의 경로(예: D:\JAVA\javajungsuk3\src\Exercise\Exercise15\file)

[result.txt]
1111111111
2222222222
3333333333​

 

해설 :

import java.io.*;
import java.util.*;

class FileMergeTest1 {
    public static void main(String[] args) {
        if (args.length < 2) {
            System.out.println("USAGE: java FileMergeTest MERGE_FILENAME FILENAME1 FILENAME2...");
            System.exit(0);
        }

        try {
            Vector v = new Vector();

            for (int i = 1; i < args.length; i++) {
                File f = new File(args[i]);

                if (f.exists()) {
                    v.add(new FileInputStream(args[i]));
                } else {
                    System.out.println(args[i] + " - 존재하지 않는 파일입니다.");
                    System.exit(0);
                }
            }
            
            SequenceInputStream input = new SequenceInputStream(v.elements());
            FileOutputStream output = new FileOutputStream(args[0]);

            int data = 0;
            while ((data = input.read()) != -1) {
                output.write(data);
            }
        } catch (IOException e) {}
    }
}

1) 입력값이 2보다 작으면, 메세지를 출력하고 종료한다.

2) 커맨드라인으로부터 입력받은 병합할 파일을 Vector에 저장한다.

    사용자로부터 입력받은 값은 항상 유효성 체크를 해줘야 한다. 파일이 존재하지 않을 수도 있기 때문이다.

3) 여러개의 파일을 하나로 연결하는 SequenceInputStream를 생성한다.

    SequenceInputStream(Enumeration e) : Enumeration에 저장된 순서대로 입력 스트림을 하나의 스트림으로 연결한다.

    Vector.elements() : Vector에 저장된 데이터(객체)를 열거형으로 반환한다.

    ① Vector에 연결할 입력 스트림들을 저장한다.

    ② Vector.elements()를 호출하여 생성자의 매개변수로 사용한다.

4) 결과를 담을 args[0]를 출력 스트림으로 하는 FileOutputStream를 생성한다.

5) 반복문을 이용하여 읽어올 데이터가 없을 때까지 read()를 통해 읽어온 데이터를 data 저장한다.

  write()를 통해 dataoutput 출력한다.

SequenceInputStream 메서드 및 생성자
SequenceInputStream 메서드 및 생성자

[사용 예1]
Vector files = new Vector();
files.add(new FileInputStream("file.001"));
files.add(new FileInputStream("file.002"));
SequenceInputStream in = new SequenceInputStream(files.elements());

[사용 예2]
FileInputStream file1 = new FileInputStream("file.001");
FileInputStream file2 = new FileInputStream("file.002");
SequenceInputStream in = new SequenceInputStream(file1, file2);

 

 

 

 

[15-5] 다음은 FilterWriter를 상속받아 직접 구현한 HtmlTagFilterWriter를 사용하여 주어진 파일에 있는 태그를

모두 제거하는 프로그램이다. HtmlTagFilterWriter의 write()가 태그를 제거하도록 코드를 완성하시오.

import java.io.*;

class Exercise15_5 {
    public static void main(String[] args) {
        if (args.length != 2) {
            System.out.println("USAGE: java Exercise15_5 TARGET_FILE RESULT_FILE");
            System.exit(0);
        }

        String inputFile = args[0];
        String outputFile = args[1];

        try {
            BufferedReader input = new BufferedReader(new FileReader(inputFile));
            HtmlTagFilterWriter output = new HtmlTagFilterWriter(new FileWriter(outputFile));

            int ch = 0;

            while ((ch = input.read()) != -1) {
                output.write(ch);
            }
            input.close();
            output.close();
        } catch (IOException e) {}
    }
}

class HtmlTagFilterWriter extends FilterWriter {
    StringWriter tmp = new StringWriter();
    boolean inTag = false;

    HtmlTagFilterWriter(Writer out) {
        super(out);
    }

    /*
        (1) 아래의 로직에 맞게 코드를 작성하시오.
            1. 출력할 문자(c)가 '<'이면 inTag의 값을 true로 한다.
            2. 출력할 문자(c)가 '>'이면 inTag의 값을 false로 한다.
               새로운 StringWriter를 생성한다(기존 StringWriter의 내용을 버린다.)
            3. inTag의 값이 true이면, StringWriter에 문자(c)를 출력하고,
               inTag의 값이 false이면, out에 문자(c)를 출력한다.
            [참고] 태그가 시작되면 StringWriter에 출력하고 태그가 끝나면 StringWriter는 비워진다.
    */
    public void write(int c) throws IOException {}

    public void close() throws IOException {
        out.write(tmp.toString());  // StringWriter의 내용을 출력한다.
        super.close();  // 조상의 close()를 호출해서 기반 스트림을 닫는다.
    }
}
C:\jdk1.8\work\ch15>java Exercise15_5 test.html result.txt

C:\jdk1.8\work\ch15>type test.html
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<HTML>
<HEAD>
<TITLE> New Document </TITLE>
</HEAD>
<BODY>
> 안녕하세요. 태그 없애기 테스트용 파일입니다.
</BODY>
< 태그가 열린 채로 끝난 것은 태그로 처리하지 마세요.

C:\jdk1.8\work\ch15>type result.txt



 New Document 

> 안녕하세요. 태그 없애기 테스트용 파일입니다.

< 태그가 열린 채로 끝난 것은 태그로 처리하지 마세요.

 

답 :

public void write(int c) throws IOException {
    // 1. 출력할 문자(c)가 '<'이면 inTag의 값을 true로 한다.
    if (c == '<') {
        inTag = true;
    // 2. 출력할 문자(c)가 '>'이면 inTag의 값을 false로 한다.
    //    새로운 StringWriter를 생성한다(기존 StringWriter의 내용을 버린다.)
    } else if (c == '>' && inTag) {
        inTag = false;
        tmp = new StringWriter();
        return;
    }

    // 3. inTag의 값이 true이면, StringWriter에 문자(c)를 출력하고,
    //    inTag의 값이 false이면, out에 문자(c)를 출력한다.
    if (inTag) {
        tmp.write(c);  // StringWriter
    } else {
        out.write(c);  // FilterWriter
    }
}

1) 태그가 시작(<)된 후 끝(>)날 때까지의 내용을 StringWriter에 출력한다.(예 : <HTML>)

2) 태그가 시작(<)된 후 끝(>)나게 되면 새로운 StringWriter를 생성하여 tmp에 할당한다(기존의 내용은 없어진다).

3) 태그가 아니거나 열지 않고 >로 시작할 때 기반 스트림(out)에 출력한다.(예 : New Document, > 안녕하세요.)

마지막 문장은 어떻게 처리되는 걸까?("< 태그가 열린 채로 끝난 것은 태그로 처리하지 마세요.")
StringWriter에 출력하는 이유이다.
열린채로 끝난 마지막 문장은 tmp.write(c)에 의해 StringWriter에 출력(저장)된다.
그렇기 때문에 "> 안녕하세요. 태그 없애기 테스트용 파일입니다." 문장까지 기반 스트림에 출력된다.
이후 output.close()를 호출함으로써 StringWriter에 출력(저장)된 내용이 기반 스트림에 출력된다.
public void close() throws IOException {
    out.write(tmp.toString());  // StringWriter의 내용을 출력한다.
    super.close();  // 조상의 close()를 호출해서 기반 스트림을 닫는다.
}​
StringWriter : 입출력 대상이 메모리인 스트림. 출력 내용을 내부적으로 갖는 StringBuffer에 저장한다.
StringBuffer getBuffer() : StringWriter로 출력한 데이터가 저장된 StringBuffer를 반환한다.
String toString() : StringWriter로 출력된(StringBuffer에 저장된) 문자열을 반환한다.

 

 

 

 

[15-6] 다음은 콘솔 명령어 중에서 디렉토리를 변경하는 cd명령을 구현한 것이다. 알맞은 코드를 넣어 cd()를 완성하시오.

import java.io.File;
import java.util.Scanner;

class Exercise15_6 {
    static String[] argArr;  // 입력한 매개 변수를 담기 위한 문자열 배열
    static File curDir;      // 현재 디렉토리

    static {
        try {
            curDir = new File(System.getProperty("user.dir"));
        } catch (Exception e) { }
    }

    public static void main(String[] args) {
        Scanner s = new Scanner(System.in);

        while (true) {
            try {
                String prompt = curDir.getCanonicalPath() + ">>";
                System.out.print(prompt);

                // 화면으로부터 라인 단위로 입력받는다.
                String input = s.nextLine();

                input = input.trim();  // 입력 받은 값에서 불필요한 앞뒤 공백을 제거한다.
                argArr = input.split(" +");

                String command = argArr[0].trim();

                if ("".equals(command)) continue;

                command = command.toLowerCase();  // 명령어를 소문자로 변경한다.

                if (command.equals("q")) {  // q또는 Q를 입력하면 실행을 종료한다.
                    System.exit(0);
                } else if (command.equals("cd")) {
                    cd();
                } else {
                    for (int i = 0; i < argArr.length; i++) {
                        System.out.println(argArr[i]);
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
                System.out.println("입력 오류입니다.");
            }
        }
    }

    public static void cd() {
        if (argArr.length == 1) {
            System.out.println(curDir);
            return;
        } else if (argArr.length > 2) {
            System.out.println("USAGE: cd directory");
            return;
        }

        String subDir = argArr[1];

        /*
            (1) 아래의 로직에 맞게 코드를 작성하시오.
                1. 입력된 디렉토리(subDir)가 ".."이면, 현재 디렉토리의 조상 디렉토리를 얻어 현재 디렉토리로 지정한다.
                   (File클래스의 getParentFile()를 사용)
                2. 입력된 디렉토리(subDir)가 "."이면, 단순히 현재 디렉토리의 경로를 화면에 출력한다.
                3. 1 또는 2의 경우가 아니면,
                    3.1 입력된 디렉토리(subDir)가 현재 디렉토리의 하위 디렉토리인지 확인한다.
                    3.2 확인 결과가 true이면, 현재 디렉토리(curDir)을 입력된 디렉토리(subDir)로 변경한다.
                    3.3 확인 결과가 false이면, "유효하지 않은 디렉토리입니다."라고 화면에 출력한다.
        */
    }
}
C:\jdk1.8\work\ch15>java Exercise15_6
C:\jdk1.8\work\ch15>>
C:\jdk1.8\work\ch15>>cd ch15
유효하지 않은 디렉토리입니다.
C:\jdk1.8\work\ch15>>cd ..
C:\jdk1.8\work>>cd ch15
C:\jdk1.8\work\ch15>>
C:\jdk1.8\work\ch15>>cd .
C:\jdk1.8\work\ch15
C:\jdk1.8\work\ch15>>q

C:\jdk1.8\work\ch15>

 

답 :

public static void cd() {
    if (argArr.length == 1) {
        System.out.println(curDir);
        return;
    } else if (argArr.length > 2) {
        System.out.println("USAGE: cd directory");
        return;
    }

    String subDir = argArr[1];
    
    // 1. 입력된 디렉토리(subDir)가 ".."이면, 현재 디렉토리의 조상 디렉토리를 얻어 현재 디렉토리로 지정한다.
    if (subDir.equals("..")) {
        File newDir = curDir.getParentFile();

        if (newDir == null) {
            System.out.println("유효하지 않은 디렉토리입니다.");
        } else {
            curDir = newDir;
        }
    // 2. 입력된 디렉토리(subDir)가 "."이면, 단순히 현재 디렉토리의 경로를 화면에 출력한다.
    } else if (subDir.equals(".")) {
        System.out.println(curDir);
    // 3. 1 또는 2의 경우가 아니면,
    } else {
        File newDir = new File(curDir, subDir);
        // 3.1 입력된 디렉토리(subDir)가 현재 디렉토리의 하위 디렉토리인지 확인한다.
        if (newDir.exists() && newDir.isDirectory()) {
            // 3.2 확인 결과가 true이면, 현재 디렉토리(curDir)을 입력된 디렉토리(subDir)로 변경한다.
            curDir = newDir;
        } else {
           // 3.3 확인 결과가 false이면, "유효하지 않은 디렉토리입니다."라고 화면에 출력한다.
            System.out.println("유효하지 않은 디렉토리입니다.");
        }
    }
}

1) String getParent()       : 파일의 조상 디렉토리를 String으로 반환한다.
    File     getParentFile() : 파일의 조상 디렉토리를 File로 반환한다.

3) 하위 디렉토리인 경우를 처리하기 위함. File(File parent, String child)를 생성자로 하는 File인스턴스를 생성한다.

더보기

[실행결과]

D:\JAVA\javajungsuk3\src\Exercise>>cd .
D:\JAVA\javajungsuk3\src\Exercise
D:\JAVA\javajungsuk3\src\Exercise>>cd ..
D:\JAVA\javajungsuk3\src>>cd .
D:\JAVA\javajungsuk3\src
D:\JAVA\javajungsuk3\src>>cd ..
D:\JAVA\javajungsuk3>>cd src
D:\JAVA\javajungsuk3\src>>cd Exercise
D:\JAVA\javajungsuk3\src\Exercise>>cd t
유효하지 않은 디렉토리입니다.
D:\JAVA\javajungsuk3\src\Exercise>>q

 

 

 

 

15-7번 문제부터 GUI가 등장해서 당황했지만, TextArea의 내용을 파일에 저장 및 입력하는 방법만 알면 된다.

 

[15-7] 다음의 코드는 대화 내용을 파일에 저장할 수 있는 채팅 프로그램이다. '저장' 버튼을 누르면 대화 내용이 저장되도록 알맞은 코드를 넣어 완성하시오.

import java.awt.*;
import java.awt.event.*;
import java.io.*;

class ChatWin extends Frame {
    String nickname = "";
    TextArea ta = new TextArea();
    Panel p = new Panel();
    TextField tf = new TextField(30);
    Button bSave = new Button("저장");

    ChatWin(String nickname) {
        super("Chatting");
        this.nickname = nickname;

        setBounds(200, 100, 300, 200);

        p.setLayout(new FlowLayout());
        p.add(tf);
        p.add(bSave);

        add(ta, "Center");
        add(p, "South");

        addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent e) {
                System.exit(0);
            }
        });

        bSave.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent ae) {
                FileDialog fileSave = new FileDialog(ChatWin.this,
                    "파일 저장", FileDialog.SAVE);
                fileSave.setVisible(true);
                String fileName = fileSave.getDirectory() + fileSave.getFile();
                saveAs(fileName);
            }
        });

        EventHandler handler = new EventHandler();
        ta.addFocusListener(handler);
        tf.addFocusListener(handler);
        tf.addActionListener(handler);

        ta.setText("#" + nickname + "님 즐거운 채팅되세요.");
        ta.setEditable(false);

        setResizable(false);
        setVisible(true);
        tf.requestFocus();
    }

    void saveAs(String fileName) {
        // (1) 알맞은 코드를 넣어 완성하시오.
    }

    public static void main(String[] args) {
        if (args.length != 1) {
            System.out.println("USAGE : java ChatWin NICKNAME");
            System.exit(0);
        }

        new ChatWin(args[0]);
    }

    class EventHandler extends FocusAdapter implements ActionListener {
        public void actionPerformed(ActionEvent ae) {
            String msg = tf.getText();
            if ("".equals(msg)) return;

            ta.append("\r\n" + nickname + ">" + msg);
            tf.setText("");
        }

        public void focusGained(FocusEvent e) {
            tf.requestFocus();
        }
    }
}

[15-7] 실행 결과
[15-7] 실행 결과

 

답 :

void saveAs(String fileName) {
    FileWriter fw = null;      // 문자 기반 스트림
    BufferedWriter bw = null;  // 문자 기반 보조 스트림

    try {
        fw = new FileWriter(fileName);
        bw = new BufferedWriter(fw);
        bw.write(ta.getText());  // TextArea의 내용을 파일에 저장한다.
        // bw.close();  // bw.write()에서 예외가 발생하면 이 문장은 수행되지 않는다.
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        try {
            if (bw != null) bw.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

1) 파일에 출력하기 위한 FileWriterBufferedWriter를 선언한다.

    FileWriter : 문자 기반 스트림. 데이터를 파일에 출력한다.(FileReader(읽기), FileWriter(쓰기))
    BufferedWriter : 문자 기반 보조 스트림. 버퍼를 사용하여 출력한다.(BufferedReader(읽기), BufferedWriter(쓰기))

2) 데이터를 출력하는 과정 도중에 예외가 발생할 수 있으므로 예외 처리(try-catch)를 해야 한다.

  2-1) FileWriter인스턴스를 생성하여 보조 스트림 생성자의 인자에 넣는다.

         보조 스트림은 자체적으로 입출력을 수행할 수 없기 때문에 기반 스트림을 필요로 한다.

  2-2) getText()를 이용하여 채팅 내용을 읽은 다음 write(String str)를 통해 파일에 출력(저장)한다.

3) 예외의 발생 여부와 관계 없이 항상 수행되는 finally블럭에서 스트림을 닫아줘야 한다.

    bw.close()도 예외가 발생할 수 있으므로 예외 처리(try-catch)를 해줘야 한다.

더보기

[실행결과]

Edit Configurations... 또는 Modify Run Configuration...
-> Working directory: 파일이 저장되는 경로(예: D:\JAVA\javajungsuk3\src\Exercise\Exercise15)
-> Program arguments : hee  // 사용자의 이름 설정

Chatting 창을 닫으면 프로그램이 종료된다.

ChatWin 실행 결과
ChatWin 실행 결과
저장 버튼 &rarr; 파일 이름 입력 &rarr; 저장
저장 버튼 &rarr; 파일 이름 입력 &rarr; 저장한 결과

 

 

 

 

[15-8] 다음의 코드는 파일로부터 한 줄씩 데이터를 읽어서 보여주는 프로그램이다.

버튼을 이용해서 첫 줄, 다음 줄, 이전 줄, 마지막 줄로 이동할 수 있으며, 각 줄의 개행문자는 '|'를 사용한다.

(1)~(2)에 알맞은 코드를 넣어 완성하시오.

[word_data.txt]
mandatory|1. 명령의|2. 통치를 위임받은|3. 강제의, 의무의(obli-gatory); 필수의
preliminary|1. 사전 준비|2. 예비 시험|3. 주 경기 이전에 펼쳐지는 개막 경기
commitment|1. 위탁, 위임; 위원회 회부|2. 인도; 투옥, 구류, 수감|3. 언질[공약]을 주기
prominent|1. 현저한, 두드러진|2. 돌기한, 양각된|3. 탁월한, 걸출한, 유명한; 중요한
Tell me the reason for coming here.|여기에 온 이유를 내게 말해라.
There is something different about you today.|너 오늘 평소와 좀 다르구나.
He jumped up and down when he got the news.|그는 뉴스를 듣고 펄쩍 뛰었다.
When I opened it, I found a surprise.|그 것을 열었을 때, 나는 놀라운 것을 발견했다.
I have known him since he was a child.|나는 그를 어려서부터 알고 있다.
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.StringTokenizer;

class WordStudy extends Frame {
    Button first = new Button("<<");
    Button prev = new Button("<");
    Button next = new Button(">");
    Button last = new Button(">>");
    Panel buttons = new Panel();
    TextArea ta = new TextArea();
    ArrayList wordList = new ArrayList();

    final String WORD_FILE = "word_data.txt";
    final String CR_LF = System.getProperty("line.separator");

    int pos = 0;

    WordStudy(String title) {
        super(title);

        buttons.add(first);
        buttons.add(prev);
        buttons.add(next);
        buttons.add(last);
        add("South", buttons);
        add("Center", ta);

        EventHandler handler = new EventHandler();
        addWindowListener(handler);
        first.addActionListener(handler);
        prev.addActionListener(handler);
        next.addActionListener(handler);
        last.addActionListener(handler);

        loadFile(WORD_FILE);

        setBackground(Color.BLACK);
        setSize(300, 200);
        setLocation(200, 200);
        setResizable(true);
        setVisible(true);

        showFirst();
    }

    void showFirst() {
        pos = 0;
        display(pos);
    }

    void showPrevious() {
        pos = (pos > 0) ? --pos : 0;
        display(pos);
    }

    void showNext() {
        int size = wordList.size();
        pos = (pos < size - 1) ? ++pos : size - 1;
        display(pos);
    }

    void showLast() {
        pos = wordList.size() - 1;
        display(pos);
    }

    /*
        (1) 아래의 로직에 맞게 코드를 작성하시오.
            1. wordList에서 pos번째의 위치에 있는 데이터를 읽어온다.
            2. StringTokenizer를 이용해서 '|'를 구분자로 자른다.
            3. 잘라진 Token에 개행 문자(CR_LF)를 붙여 StringBuffer에 붙인다(append).
            4. StringBuffer의 내용을 뽑아 TextArea에 보여준다.
    */
    // pos 위치에 있는 라인의 내용을 보여준다.
    void display(int pos) {}

    /*
        (2) 아래의 로직에 맞게 코드를 작성하시오.
            1. BufferedReader와 FileReader를 이용해서 파일의 내용을 라인 단위로 읽는다.
            2. 읽어온 라인을 wordList에 저장한다.
            3. 만일 예외가 발생하면 프로그램을 종료한다.
    */
    void loadFile(String fileName) {}

    public static void main(String args[]) {
        WordStudy mainWin = new WordStudy("Word Study");
    }

    class EventHandler extends WindowAdapter implements ActionListener {
        public void actionPerformed(ActionEvent ae) {
            Button b = (Button) ae.getSource();

            if (b == first) {
                showFirst();
            } else if (b == prev) {
                showPrevious();
            } else if (b == next) {
                showNext();
            } else if (b == last) {
                showLast();
            }
        }

        public void windowClosing(WindowEvent e) {
            System.exit(0);
        }
    }
}

[15-8] 실행 결과

 

답 :

// pos 위치에 있는 라인의 내용을 보여준다.
void display(int pos) {
    // 1. wordList에서 pos번째의 위치에 있는 데이터를 읽어온다.
    String data = (String) wordList.get(pos);
    // 2. StringTokenizer를 이용해서 '|'를 구분자로 자른다.
    StringTokenizer st = new StringTokenizer(data, "|");
    // 3. 잘라진 Token에 개행 문자(CR_LF)를 붙여 StringBuffer에 붙인다(append).
    StringBuffer sb = new StringBuffer(data.length());  // StringBuffer(int length)

    while (st.hasMoreTokens()) {
        sb.append(st.nextToken()).append(CR_LF);
    }
    
    // 4. StringBuffer의 내용을 뽑아 TextArea에 보여준다.
    ta.setText(sb.toString());
}

1) Object get(int index) : 지정된 위치(index)에 저장된 객체를 반환한다.

2) StringTokenizer(String str, String delim) : 문자열(str)을 지정된 구분자(delim)로 나누는 StringTokenizer를 생성한다.

    (지정된 구분자를 기준으로)문자열을 여러 개의 문자열(토큰(token) = 조각)로 잘라낼 때 사용한다.

    구분자도 토큰으로 간주하려면 StringTokenizer(String str, String delim, boolean returnDelims)를 사용해야 한다.

3) StringBuffer(int length) : length만큼의 문자를 담을 수 있는 StringBuffer인스턴스를 생성한다.

    생성자로 버퍼의 크기를 지정할 때 저장할 문자열의 길이를 고려하여 버퍼(배열)의 크기를 충분히 지정해야 한다.

    문자열을 토큰으로 자르려면 StringTokenizer객체에 잘라야 할 토큰이 있는지 확인 후 잘라야 한다.

    nextToken()는 더 이상 잘라야 할 토큰이 없는데 자르면 NoSuchElementException 예외를 발생시킨다.

4) void setText(String t) : 지정된 문자열을 TextArea에 보여줄 텍스트로 설정한다.

    String toString() : StringBuffer인스턴스의 문자열을 String으로 반환한다.

StringBuffer : 문자열 편집을 위한 버퍼(buffer)를 내부적으로 갖기 때문에 문자열의 내용을 변경할 수 있다.
StringBuffer append(String str) : 문자열(str)을 StringBuffer인스턴스에 저장된 문자열의 뒤에 덧붙인다.
                                                  : 객체 자신(StringBuffer)을 반환하므로 Method Chaining이 가능하다.
Method Chaining(메서드 체이닝) : 메서드를 연결하여 호출하는 패턴(코드 간결)
boolean hasMoreTokens() : 다음으로 읽어 들일 토큰이 남아 있다면 true를, 없으면 false를 반환한다.
String nextToken() : StringTokenizer객체에서 토큰을 하나 꺼내온다(해당 토큰은 객체에서 삭제된다).
void loadFile(String fileName) {
    FileReader fr = null;
    BufferedReader br = null;

    try {
        fr = new FileReader(fileName);
        br = new BufferedReader(fr);

        String data = "";

        // 1. BufferedReader와 FileReader를 이용해서 파일의 내용을 라인 단위로 읽는다.
        while ((data = br.readLine()) != null) {
            // 2. 읽어온 라인을 wordList에 저장한다.
            wordList.add(data);
        }
        // 3. 만일 예외가 발생하면 프로그램을 종료한다.
    } catch (IOException e) {
        System.out.println("데이터 파일을 읽을 수 없습니다.");
        System.exit(1);
    }
}

FileReader : 문자 기반 스트림. 파일로부터 텍스트 데이터를 읽는다.(FileReader(읽기), FileWriter(쓰기))

BufferedReader : 문자 기반 보조 스트림. 버퍼를 사용하여 읽는다.(BufferedReader(읽기), BufferedWriter(쓰기))

 

 

 

 

[15-9] 다음은 메모장 프로그램의 일부인데, fileOpen()saveAs()가 아직 구현되어 있지 않다.

이 두 메서드를 구현하여 프로그램을 완성하시오.

import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.*;

class Exercise15_9 extends Frame {
    String fileName;
    TextArea content;
    MenuBar mb;
    Menu mFile;
    MenuItem miNew, miOpen, miSaveAs, miExit;

    Exercise15_9(String title) {
        super(title);
        content = new TextArea();
        add(content);

        mb = new MenuBar();
        mFile = new Menu("File");

        miNew = new MenuItem("New");
        miOpen = new MenuItem("Open");
        miSaveAs = new MenuItem("Save As...");
        miExit = new MenuItem("Exit");

        mFile.add(miNew);
        mFile.add(miOpen);
        mFile.add(miSaveAs);
        mFile.addSeparator();  // 메뉴 분리선(separator)을 넣는다.
        mFile.add(miExit);

        mb.add(mFile);  // MenuBar에 Menu를 추가한다.
        setMenuBar(mb);  // Frame에 MenuBar를 포함시킨다.

        // 메뉴에 이벤트 핸들러를 등록한다.
        MyHandler handler = new MyHandler();
        miNew.addActionListener(handler);
        miOpen.addActionListener(handler);
        miSaveAs.addActionListener(handler);
        miExit.addActionListener(handler);

        setSize(300, 200);
        setVisible(true);
    }

    // 선택된 파일의 내용을 읽어 TextArea에 보여주는 메서드
    /*
        (1) 아래의 로직에 맞게 코드를 작성하시오.
            1. BufferedReader와 FileReader를 이용해서 지정된 파일을 읽는다.
            2. StringWriter에 출력한다.
            3. StringWriter의 내용을 content(TextArea)에 보여준다.
    */
    void fileOpen(String fileName) {}

    // TextArea의 내용을 지정된 파일에 저장하는 메서드
    /*
        (2) 아래의 로직에 맞게 코드를 작성하시오.
            1. BufferedWriter와 FileWriter를 생성한다.
            2. content에 있는 내용을 가져와 BufferedWriter에 출력한다.
            3. BufferedWriter를 닫는다.
    */
    void saveAs(String fileName) {}

    public static void main(String args[]) {
        Exercise15_9 mainWin = new Exercise15_9("Text Editor");
    }

    // 메뉴를 클릭했을 때 메뉴별 처리코드
    class MyHandler implements ActionListener {
        public void actionPerformed(ActionEvent e) {
            String command = e.getActionCommand();

            if (command.equals("New")) {
                content.setText("");
            } else if (command.equals("Open")) {
                FileDialog fileOpen = new FileDialog(Exercise15_9.this, "파일 열기");
                fileOpen.setVisible(true);
                fileName = fileOpen.getDirectory() + fileOpen.getFile();
                System.out.println(fileName);
                fileOpen(fileName);  // 선택된 파일의 내용을 TextArea에 보여준다.
            } else if (command.equals("Save As...")) {
                FileDialog fileSave = new FileDialog(Exercise15_9.this, "파일 저장", FileDialog.SAVE);
                fileSave.setVisible(true);
                fileName = fileSave.getDirectory() + fileSave.getFile();
                System.out.println(fileName);
                saveAs(fileName);  // 현재 TextArea의 내용을 선택된 파일에 저장한다.
            } else if (command.equals("Exit")) {
                System.exit(0);  // 프로그램을 종료시킨다.
            }
        }
    }
}

 

답 :

// 선택된 파일의 내용을 읽어 TextArea에 보여주는 메서드
void fileOpen(String fileName) {
    FileReader fr = null;
    BufferedReader br = null;
    StringWriter sw = null;

    try {
        fr = new FileReader(fileName);
        br = new BufferedReader(fr);
        sw = new StringWriter();

        String data = "";

        // 1. BufferedReader와 FileReader를 이용해서 지정된 파일을 읽는다.
        while ((data = br.readLine()) != null) {
        // 2. StringWriter에 출력한다.
            sw.write(data);
            sw.write('\n');  // 개행 문자
        }
        // 3. StringWriter의 내용을 content(TextArea)에 보여준다.
        content.setText(sw.toString());
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        try {
            if (br != null) br.close();
        } catch (IOException e) { }
    }
}

1) FileReader : 문자 기반 스트림. 파일로부터 텍스트 데이터를 읽는다.(FileReader(읽기), FileWriter(쓰기))

    BufferedReader : 문자 기반 보조 스트림. 버퍼를 사용하여 읽는다.(BufferedReader(읽기), BufferedWriter(쓰기))

2) [15-5]처럼 파일을 읽어 바로 화면에 출력하지 않고 StringWriter를 통해 한 번에 모아 출력한다.

    라인 단위로 데이터를 읽으면(readLine()) 개행 문자(\n)가 지워지므로 줄바꿈을 포함하는 파일은 개행 문자를 출력해야 줄바꿈이 반영된다.

3) void setText(String t) : 지정된 문자열을 TextArea에 보여줄 텍스트로 설정한다.

    String toString() : StringWriter인스턴스의 문자열을 String으로 반환한다.

StringWriter : 입출력 대상이 메모리인 스트림. 출력 내용을 내부적으로 갖는 StringBuffer에 저장한다.
StringBuffer getBuffer()StringWriter로 출력한 데이터가 저장된 StringBuffer를 반환한다.
String toString()StringWriter로 출력된(StringBuffer에 저장된) 문자열을 반환한다.