본문 바로가기

국비 교육

2020.9.17 일자 수업 : 파일 입출력, 바이트 스트림, 캐릭터 스트림

 파일 입출력 

git/eomcs-java-basic/src/main/java com.eomcs.io.ex01.Exam0710~0731.java
git/eomcs-java-basic/src/main/java com.eomcs.io.ex02
git/eomcs-java-basic/src/main/java com.eomcs.io.ex03

하위 디렉토리 모두 조회하기

특정 디렉토리 안에 있는 파일과 하위 디렉토리의 리스트를 뽑는 훈련을 했다. 이제는 리스트안에 들어가는 디렉토리들의 하위 디렉토리들도 모두 검색하여 모든 파일과 디렉토리를 끝까지 출력하려고 한다. 이것은 다음과 같은 과정으로  프로그램을 짤 수 있다.

  • 특정 폴더의 파일과 하위 디렉토리를 출력하는 메서드를 만든다. 
  • 그 메서드 안에서 배열 안에 들어있는 디렉토리를 대상으로만 재귀 호출을 한다.
  • 폴더 깊이에 따라 다르게 인덴트를 처리한다.
package com.eomcs.io.ex01;

import java.io.File;

public class Exam0710 {
  public static void main(String[] args) {

    File dir = new File(".");

    printFiles(dir, 0);
  }

  static void printFiles(File dir, int level) {
    File[] files = dir.listFiles();

    for (File file : files) {
      printIndent(level);
      System.out.println(file.getName());
      if (file.isDirectory()) {
        printFiles(file, level + 1);
      }
    }
  }

  static void printIndent(int level) {
    for (int i = 0; i < level; i++) {
      System.out.print("  ");
    }
    System.out.print("|-- ");
  }
}

하위 디렉토리 모두 삭제하기

특정 폴더를 삭제할 때 그 밑에 파일이나 디렉토리가 존재하면 삭제할 수가 없다. 따라서 그 밑에 있는 모든 하위 디렉토리를 추적하여 모든 파일을 지운 후에 폴더를 삭제해야한다. 위의 하위 디렉토리들을 모두 조회할 때와 같은 방식으로 재귀호출을 통해 하위 파일들부터 차례로 지우며 삭제할 수 있다.

import java.io.File;

public class Exam0720_05 {

  public static void main(String[] args) throws Exception {

    // temp 디렉토리를 삭제하기
    File dir = new File("temp");

    deleteFile(dir);

    System.out.println("삭제하였습니다!");
  }

  static void deleteFile(File dir) {
    if (dir.isDirectory()) {
      File[] files = dir.listFiles();
      for (File file : files) {
        deleteFile(file);
      }
    }
    dir.delete();
  }
}



파일 입출력 API 주요 클래스 (java.io 패키지)

Data Sink Stream Class - File과 Memory에서 직접 입출력된 데이터가 보관되어야하는 곳에 가라앉는다는 의미

1) 데이터 읽기

  • InputStream (추상 클래스)
    • FileInputStream : 바이트 단위로 읽기 (binary stream)
  • Reader (추상 클래스)
    • FileReader : 문자 단위로 읽기 (character stream)

2) 데이터 쓰기

  • OutputStream (추상 클래스)
    • FileOutputStream : 바이트 단위로 쓰기 (binary stream)
  • Writer (추상 클래스)
    • FileWriter : 문자 단위로 쓰기 (character stream) 

바이너리 파일 vs 텍스트 파일

  • 바이너리 파일
    • 기본 텍스트 편집기(메모장, vi 에디터 등)로 편집할 수 없는 파일을 말한다.
    • 만약 텍스트 편집기로 변경한 후 저장하면, 파일 포맷이 깨지기 때문에 무효한 파일이 된다.
    • 바이너리 파일을 편집하려면 해당 파일 포맷을 이해하는 전용 프로그램이 필요하다.
    • 예) .pdf, .ppt, .xls, .gif, .mp3, .jpg, .hwp, .mov, .avi, .exe, .lib 등
    • InputStream / OutputStream 계열의 클래스를 이용해서 바이트 단위로 읽고 써야 한다.
  • 텍스트 파일
    • 기본 텍스트 편집기(메모장, vi 에디터 등)로 편집할 수 있는 파일을 말한다.
    • 텍스트 파일은 전용 에디터가 필요 없다.
    • 예) .txt, .csv, .html, .js, .css, .xml, .bat, .c, .py, .php, .docx, .pptx, .xlsx 등
    • 읽고 쓰는 과정에서 Reader / Writer 계열의 클래스를 이용해서 문자 코드표에 따라 변환하는 과정이 있다.

파일 입출력 방법

입출력 도구 생성

파일에 데이터를 입력하기 위해서는 자바에서 제공하는 API 클래스의 객체를 생성하여 사용해야 한다.

  • 바이트 단위로 데이터를 파일에 출력하고 싶다면 FileOutputStream을 생성한다.
  • 바이트 단위로 데이터를 파일에 입력하고 싶다면 FileInputStream을 생성한다.
  • 캐릭터 단위로 데이터를 파일에 출력하고 싶다면 FileWriter을 생성한다.
  • 캐릭터 단위로 데이터를 파일에 입력하고 싶다면 FileReader을 생성한다.

이때, 생성자의 파라미터로 입출력하고자 하는 파일의 경로를 지정해주어야한다.

  • 파일의 경로를 절대 경로로 지정하려면
    • 리눅스, 유닉스: "/"로 시작한다.
    • 윈도우: "c:\", "d:\" 등으로 시작한다.
FileOutputStream out = new FileOutputStream("temp/test1.data");
FileInputStream in = new FileInputStream("temp/test1.data");

close() 호출

이외의 경우에는 모두 현재 JVM이 실행되는 디렉토리가 기준이 된다. 만약 지정된 경로에 파일이 없다면 파일을 자동으로 생성한다. 단. 지정된 경로에 포함된 상위 디렉토리가 없을 경우에는FileNotFoundException을 띄울 가능성이 있어 예외처리가 필요하다.

 

OS에서 관리하는 자원 중에서 한정된 개수를 갖는 자원(파일, 메모리, 네트워크 연결 등)에 대해 여러 프로그램이 공유하는 경우, 항상 사용 후 OS에 바로바로 반납해줘야 다음 프로그램이 최대한 빨리 자원을 사용할 수 있다.


파일 입출력 클래스에도 close()가 있다. 따라서 객체를 사용한 후에는 close()를 호출하여 사용한 자원을 해제시켜야 한다. FileOutputStream이 작업하는 동안 사용했던 버퍼(임시메모리)를 비운 후, OS에서 제공한 파일과의 연결을 끊는다.

물론, JVM을 종료하면 JVM을 실행하는 동안 사용했던 모든 자원은 자동으로 해제된다. 이런 예제처럼 짧은 시간동안 실행되는 경우, close()를 호출하지 않아도 자원을 해제시키는데 문제가 없다. 문제는 24시간 365일 멈추지 않고 실행하는 서버 프로그램인 경우 사용한 자원을 즉시 해제시키지 않으면 자원 부족 문제가 발생한다.

out.close();
in.close();

바이트 단위 입출력

FileOutputStream 객체로 write()이라는 메서드를, FileInputStream 객체로는 read() 메서드를 호출할 수 있다. 단, 이것으로 입출력되는 것은 단 한 개의 byte 값이다.

  • 출력하고자 하는 값을 받는 write 메서드의 파라미터의 타입이 int(C언어에서 파라미터 타입을 int로 먼저 정하여 일관성을 위해 그대로 따라했다.)이지만, 가장 마지막 1바이트만 출력된다.
  • read 메서드는 입력받은 값을 int 타입으로 리턴하지만, 읽어들인 바이트 값이 int타입으로 암시적 형변환되어 24개의 부호비트가 채워진다.
out.write(0x7a6b5c4d); // -> 4d만 출력
int b = in.read(); // -> 0x4d가 b에 들어감

따라서 위와 같은 과정으로 특정 파일에 0x4d 1바이트를 입력하고자 한다면 다음과 같이 코드를 짜면 된다. 단, 4d 앞에 지정된 데이터들은 입력되지 않고 오로지 0x4d만 입력될 것이다.

public class Exam0110 {

  public static void main(String[] args) throws Exception {

    FileOutputStream out = new FileOutputStream("temp/test1.data");

    out.write(0x7a6b5c4d);

    out.close();

  }
}

public class Exam0120 {

  public static void main(String[] args) throws Exception {

    FileInputStream in = new FileInputStream("temp/test1.data");

    int b = in.read();
    
    in.close();

    System.out.printf("%x\n", b);
  }
}

byte 배열 입출력

byte 배열 출력

출력하고 싶은 바이트들을 담은 바이트 배열을 생성한 후 이 바이트 배열을 FileOutputStrea의 write() 메서드의 파라미터로 주면 배열에 담긴 바이트들을 차례대로 출력해준다.

public class Exam0210 {

  public static void main(String[] args) throws Exception {

    FileOutputStream out = new FileOutputStream("temp/test1.data");

    byte[] bytes = new byte[] {0x7a, 0x6b, 0x5c, 0x4d, 0x3e, 0x2f, 0x30};

    out.write(bytes); 

    out.close();
  }
}

바이트 배열의 일부만 출력하고 싶다면, write()의 파라미터로 출력하고자 하는 배열 주소, 출력하고자하는 바이트의 첫 인덱스, 출력하고자 하는 바이트 개수를 주면 된다.

import java.io.FileOutputStream;

public class Exam0310 {

  public static void main(String[] args) throws Exception {
    FileOutputStream out = new FileOutputStream("temp/test1.data");

    byte[] bytes = new byte[] {0x7a, 0x6b, 0x5c, 0x4d, 0x3e, 0x2f, 0x30};

    out.write(bytes, 2, 3); // 2번 데이터부터 3 바이트를 출력한다.

    out.close();
  }
}
// 결과!
// temp/test1.data
// 5C 4D 3E

byte 배열 입력

다음은 여러개의 바이트를 입력받아 바이트 배열에 담는 방법이다.

  • 바이트들을 저장할 배열을 먼저 준비한다. 이렇게 임시 데이터를 저장하기 위해 만든 바이트 배열을 보통 "버퍼(buffer)"라고 한다.
  • 이렇게 준비한 배열을 파라미터로 넣어주면 버퍼가 꽉 찰 때까지 읽는다. 만약 버퍼 크기보다 파일의 데이터가 적으면 마지막까지 모두 읽고 뒤에 남은 요소들은 남겨둔다.
  • 리턴 값은 읽은 바이트의 개수이다.
package com.eomcs.io.ex02;

import java.io.FileInputStream;

public class Exam0220 {

  public static void main(String[] args) throws Exception {
    
    FileInputStream in = new FileInputStream("temp/test1.data");
    
    byte[] buf = new byte[100];
    
    int count = in.read(buf);
    
    System.out.printf("읽은 바이트 수 : %d\n", count);
    
    for (int i = 0; i < count; i++)
      System.out.printf("%x", buf[i]);
  }
}
// 결과!
// 읽은 바이트 수 : 7
// 7a6b5c4d3e2f30

다음은 파일에서 바이트들을 원하는 개수만큼 읽어 배열의 원하는 인덱스에서부터 저장하는 방법이다.

  • 넉넉한 크기의 바이트 배열을 준비한다.
  • 준비한 배열과, 저장하고 싶은 배열의 인덱스 시작점, 읽을 바이트 수를 차례대로 파라미터로 넣어 read() 메서드를 호출한다.
  • 이렇게하면 원하는 바이트 개수만큼 읽은 후, 두번째 파라미터로 준 인덱스에서부터 차례대로 저장해나간다. 단, 파일에 있는 바이트가 원하는 바이트 개수보다 적을 경우, 덜 입력될 수 있다.
  • 리턴값은 실제로 읽어들인 바이트의 개수이다.
import java.io.FileInputStream;

public class Exam0320 {

  public static void main(String[] args) throws Exception {
    FileInputStream in = new FileInputStream("temp/test1.data");
    
    byte[] buf = new byte[100];
    
    int count = in.read(buf, 10, 40);
    
    System.out.printf("입력 개수 : %d\n", count);
    
    for (byte b : buf)
      System.out.printf("%x ", b);
  }
}
// 입력 개수 : 3
// 0 0 0 0 0 0 0 0 0 0 5c 4d 3e 0 0 .....

바이너리 파일 읽기

보통 바이트 단위로 파일을 입출력하는 도구들은 텍스트 파일보다는 바이너리 파일을 다루는 데 자주 사용된다. 예를 들어, 바이너리 파일에 초반 내용은 파일에 대한 전반적인 정보, 즉 메타 정보를 담고 있는데, 이것을 읽기 위해서 바이트 단위 입출력 도구를 사용하는 것이다. 그런데 파일의 형식마다 이 메타 정보가 바이트로 늘어진 형식들이 각각 다르다. 이러한 파일 형식을 파일 포맷이라고 부른다. 따라서 어떤 파일의 메타 정보를 알고 싶을 때는 그 파일의 포맷에 따라 올바르게 바이트들을 읽어 변환해야한다.

 

JPEG 파일 읽기

FileInputStream 도구를 이용해서 JPEG 파일의 메타 정보를 읽어 들일 수 있다. JPEG의 파일 포맷은 다음과 같다.

  • SOI(Start of Image) Segment : 2바이트
  • JFIF-APP0 Segment Marker : 2바이트
  • JFIF-APP0 Length : 2바이트
  • JFIF-APP0 정보 : 16바이트(위에서 알아낸 길이만큼 읽는다.)
  • JFIF-APP0 Identifier : 5바이트
  • S0F0(Start of Frame) 정보 - 그림 이미지의 크기, 샘플링에 관한 정보
    • SOF Marker 찾기 (0xC0 ~ 0xC2)
    • SOF Length : 2바이트
    • SOF 데이터 : 17바이트(위에서 알아낸 길이만큼 읽는다.
    • SOF 샘플링 정밀도 : 1바이트
    • SOF 이미지 높이 : 2바이트
    • SOF 이미지 너비 : 2바이트

이 파일 포맷에 따라 정확히 바이트들을 읽어내고 그것을 올바르게 변환해야 한다.

import java.io.File;
import java.io.FileInputStream;

public class Exam0410 {

  public static void main(String[] args) throws Exception {

    File file = new File("sample/photo1.jpg");

    FileInputStream in = new FileInputStream(file);

    // => SOI(Start of Image) Segment 읽기: 2바이트
    int b1 = in.read(); // 00 00 00 ff
    int b2 = in.read(); // 00 00 00 d8
    int soi = b1 << 8 | b2;
    System.out.printf("SOI: %x\n", soi);

    // => JFIF-APP0 Segment Marker 읽기: 2바이트
    int jfifApp0Marker = in.read() << 8 | in.read();
    System.out.printf("JFIF APP0 Marker: %x\n", jfifApp0Marker);

    // => JFIF-APP0 Length: 2바이트
    int jfifApp0Length = in.read() << 8 | in.read();
    System.out.printf("JFIF APP0 정보 길이: %d\n", jfifApp0Length);

    // => JFIF-APP0 정보: 16바이트(위에서 알아낸 길이)
    byte[] jfifApp0Info = new byte[jfifApp0Length];
    in.read(jfifApp0Info);

    // => JFIF-APP0 Identifier: 5바이트
    String jfifApp0Id = new String(jfifApp0Info, 0, 4);
    System.out.printf("JFIF APP0 ID: %s\n", jfifApp0Id);

    // => SOF Marker 찾기
    int b;
    while (true) {
      b = in.read();
      if (b == -1) { // 파일 끝에 도달
        break;
      }

      if (b == 0xFF) {
        b = in.read();
        if (b == -1) { // 파일 끝에 도달
          break;
        }
        if (b >= 0xC0 && b <= 0xC2) {
          break;
        }
      }
    }

    if (b == -1) {
      System.out.println("유효한 JPEG 파일이 아닙니다.");
      return;
    }

    // => SOF Length 읽기: 2바이트
    int sofLength = in.read() << 9 | in.read();
    System.out.printf("SOF 데이터 크기: %d\n", sofLength);

    // => SOF 데이터 읽기: 17바이트(위에서 알아낸 크기)
    byte[] sofData = new byte[sofLength];
    in.read(sofData);

    // => SOF 샘플링 정밀도: 1바이트
    System.out.printf("SOF 샘플링 정밀도: %d\n", sofData[0]);

    // => SOF 이미지 높이: 2바이트
    int height = ((sofData[1] << 8) & 0xff00) | (sofData[2] & 0xff);

    // => SOF 이미지 너비: 2바이트
    int width = ((sofData[3] << 8) & 0xff00) | (sofData[4] & 0xff);
    System.out.printf("SOF 이미지 크기(w x h): %d x %d\n", width, height);


    // 3) 읽기 도구를 닫는다.
    in.close();
  }
}

바이트 단위로 텍스트 입출력

바이트 단위로 텍스트 출력

바이트 단위로 원하는 텍스트를 출력하기 위해서는 출력될 바이트들의 인코딩 정보를 확실히 인지 후 출력해야 한다. 자바에서 String으로 표현되는 바이트들은 모두 UCS2 형식이지만, 이 String 객체를 getBytes()를 통해 바이트 배열로 변환하면, 그것은 UCS2 형식이 아니라, JVM의 환경 변수인 file.encoding에 설정된 문자집합으로 인코딩된다. 대부분의 웹 브라우저에서 텍스트 인코딩에 사용되는 문자집합은 UTF-8이므로 만약 MS949와 같이 다른 문자집합으로 인코딩된 바이트 배열을 출력한다면 브라우저에서는 폰트가 깨진다. 따라서 바이트들의 인코딩 정보가 중요하다.

 

자바에서 String을 바이트로 변환할 때에는 앞에서 말한 것처럼 file.encoding에 설정된 문자집합으로 인코딩하는데, 그 환경 변수는 프로그램을 실행할 때 특정 옵션을 달아주지 않는 한, OS의 기본 인코딩을 따른다. OS의 기본 인코딩은 다음과 같다.

  • Windows : MS949
  • Linux/macOS : UTF-8

만약 OS의 기본 인코딩 정보와 상관없이 file.encoding의 문자집합을 지정하고 싶다면, 콘솔에서 자바 파일을 실행할 때 다음과 같은 옵션을 달아주면 된다.

$ java -Dfile.encoding=원하는 인코딩 문자집합 -cp bin/main .....

혹은 String 에서 byte로 변환하는 getBytes()를 호출할 때 파라미터로 원하는 인코딩 문자집합을 넣어줘도 된다.

str.getBytes("인코딩 문자 집합")

이클립스에서 자바 파일을 실행한다면, 자동으로 file.encoding이 UTF-8로 지정된다.

 

만약 프로그램이 실행되는 시점에 file.encoding에 설정된 문자집합 정보를 알고 싶다면, System.getProperty("file.encoding))을 통해 환경변수에 저장된 문자집합 정보를 리턴받을 수 있다.

 

바이트 단위로 텍스트 입력

파일에 써진 텍스트들을 바이트 단위로 읽어낼 때도 마찬가지이다. 파일에 쓰여진 바이트들에 대한 인코딩 정보를 먼저 알아야한다. 그리고 이 읽어들인 바이트들을 자바에서 String으로 변환할 때 , 바이트들의 인코딩 정보를 JVM에게 넘겨 올바르게 UCS2로 변환할 수 있도록 해야한다. 만약 JVM에게 인코딩 정보를 알려주지 않고 바이트 배열을 String 으로 변환했을 경우, String -> byte와 마찬가지로 변환할 바이트 배열이 file.encoding에 저장된 문자집합으로 인코딩되었다고 인지하여 그것에 따라 UCS2로 변환할 것이다. 따라서 바이트 배열의 인코딩 정보를 JVM에게 알려주려면 자바 파일을 실행할 때, 다음과 같은 옵션을 붙여야한다.

$ java -Dfile.encoding=원하는 인코딩 문자집합 -cp bin/main .....

혹은 바이트 배열을 갖고 String 객체를 생성하는 String 생성자의 마지막 파라미터로 바이트 배열의 인코딩 문자집합 정보를 넣는다.

String str = new String(buf, 0, count, "UTF-8");

문자 단위 입출력

문자 단위로 파일을 입출력하기 위해서는 FileWriter, FilerReader과 같은 캐릭터 스트림 클래스들을 사용할 수 있으며, 바이트 스트림과 마찬가지로 read()나 write()와 같이 일관적인 메서드를 통해서 간단히 입출력이 가능하다. 

 

다음과 같이 원하는 데이터를 문자 단위로 입출력할 수가 있다.

  • FileWriter 혹은 FileReader 등의 사용할 입출력 도구를 생성한다.
  • FileWriter로 한개의 문자를 출력하는 write 메서드를 호출할 때 int 값을 파라미터로 넣어주면, 해당 int 값의 앞 2바이트는 버리고 뒤의 2바이트만 UCS2 문자집합으로 인식하여 읽은 후 특정 문자집합으로 변환하여 출력한다.
  • FileReader로 한개의 문자를 입력받는 read 메서드를 호출하면 특정 문자집합으로 파일의 문자를 읽은 후 UCS2 형식의 2바이트로 변환한 값을 int 값에 담아 리턴한다.
import java.io.FileWriter;
import java.io.FileReader;

public class Exam0111 {
  public static void main(String[] args) throws Exception {

    FileWriter out = new FileWriter("temp/test2.txt");

    out.write(0x7a6bac00); 

    out.write(0x7a5f0041);

    out.close();

    System.out.println("출력 완료!");

  }
}

public class Exam0120 {

  public static void main(String[] args) throws Exception {
    FileReader in = new FileReader("sample/utf8.txt"); // 41 42 ea b0 81 ea b0 81

    int ch1 = in.read(); // 41 => 0041('A')
    int ch2 = in.read(); // 42 => 0042('B')

    int ch3 = in.read(); // ea b0 80 => ac00('가')
    int ch4 = in.read(); // ea b0 81 => ac01('각')

    in.close();

    System.out.printf("%x, %x, %x, %x\n", ch1, ch2, ch3, ch4);
  }
}

FileInputStream이나 FileOutputStream과 같은 바이트 스트림 도구로 텍스트를 입출력할 수도 있지만, 항상 한개의 바이트와 한개의 문자가 대응되지 않으므로, 한개의 문자에 해당하는 여러개의 바이트들을 조합하는 수고로움이 있었다. 캐릭터 스트림은 바이트들을 바이트 단위가 아닌 문자 단위로 입출력하기 때문에 한번에 여러개의 바이트를 받아 하나의 문자로 만드는 과정이 생략되어 편리하다. 그러나 여전히 문자를 출력하기 위해서는 인코딩 문자집합을 염두에 두어야 한다.

 

자바에서 표현되는 문자 데이터들은 모두 UCS2 인코딩 문자집합을 사용하지만, 이것을 출력할 떄에는 바이트 스트림과 마찬가지로 file.encoding에 저장된 문자집합으로 인코딩한다. 또한 파일에서 문자를 입력받을 때에도 파일에 써진 문자들이 어떤 문자집합으로 인코딩되어있는 지 JVM에게 알려주어야만 알맞게 그것을 UCS2로 변환할 수 있다.

 

문자 단위로 텍스트 입출력

UCS2로 표현되는 모든 문자들은 2바이트이다. 따라서 FileWriter의 write() 메서드로 int 값을 주면, 앞의 2바이트는 버리고 뒤의 2바이트만 읽어 그것을 특정 문자집합으로 인코딩한 후 출력한다. 디폴트 상태에서는 JVM은 file.encoding에 지정된 문자집합에 따라 읽은 2바이트를 변환한다. 따라서 OS 종류에 상관없이 원하는 문자집합으로 인코딩하고 싶다면, 자바 프로그램을 실행할 때 다음과 같이 옵션을 추가한다.

$ java -Dfile.encoding=원하는 인코딩 문자집합 -cp bin/main .....

혹은 자바 11부터 추가된 기능으로, 원하는 문자집합 정보를 가진 Charset 객체를 생성하여 그것을 FileWriter 생성자의 두번째 파라미터로 넣어주면, write() 메서드를 호출할 때마다 Charset에 지정된 문자집합으로 변환하여 출력한다.

Charset charset = Charset.forName("MS949");
FileWriter out = new FileWriter("temp/test2.txt", charset);
FileReader in = new FileReader("temp/test2.txt", charset);

 


문자 배열 입출력

char 배열 출력

char[]에 출력하고 싶은 문자들을 모두 담은 후 한번에 출력하는 방법도 있다. FileOutputStream에서 byte[] 을 출력하는 방법과 동일하게 v준비한 char[]에 원하는 문자를 모두 저장한 후, 그 배열을 write 메서드의 파라미터로 넘겨주는 것이다.

그럼 각 배열에 있는 char 데이터가 file.encoding에 설정된 문자집합에 따라 변환되어 차례대로 출력될 것이다.

import java.io.FileWriter;

public class Exam0210 {

  public static void main(String[] args) throws Exception {
    FileWriter out = new FileWriter("temp/test2.txt");

    char[] chars = new char[] {'A', 'B', 'C', '가', '각', '간', '똘', '똥'};

    out.write(chars); // 문자 배열 전체를 출력한다.

    out.close();

    System.out.println("데이터 출력 완료!");

  }
}

원하는 배열의 인덱스부터 원하는 개수만큼 문자를 출력하고 싶다면 write 메서드의 두번째 파라미터로 출력하고자 하는 배열 인덱스의 시작점, 세번째 파라미터로 출력하고자 하는 문자 개수를 넘겨주면 된다.

import java.io.FileWriter;

public class Exam0310 {

  public static void main(String[] args) throws Exception {
    FileWriter out = new FileWriter("temp/test2.txt");

    char[] chars = new char[] {'A','B','C','가','각','간','똘','똥'}; 

    out.write(chars, 2, 3); // 2번 문자부터 3 개의 문자를 출력한다.

    out.close();

    System.out.println("데이터 출력 완료!");
  }
}

문자열 출력

문자열을 통째로 출력하고 싶다면 그냥 write 메서드의 파라미터로 String 객체를 넘겨주면 된다. 물론 이 때도 항상 출력될 문자의 인코딩 정보를 염두에 두어야 한다.

import java.io.FileWriter;

public class Exam0410 {

  public static void main(String[] args) throws Exception {
    FileWriter out = new FileWriter("temp/test2.txt");

    String str = new String("AB가각");

    out.write(str); 

    out.close();

    System.out.println("데이터 출력 완료!");

  }
}

char 배열 입력

한번에 읽고 싶은 만큼 문자를 읽어 char 배열에 담는 방법도 있다. 이것도 FileInputStream에서 byte 배열 길이만큼 byte를 읽은 후 배열에 담았을 때와 동일한 방법으로 원하는 크기의 배열을 준비해서 read()의 파라미터로 넘겨주면 이 배열이 꽉 찰때까지 char 단위로 읽은 후 UCS2 문자 집합으로 변환하여 배열에 담을 것이다. 단 배열이 꽉 차기 전에 파일에 더 이상 읽을 문자가 없다면 배열의 길이보다 덜 입력될 것이다. 실제로 입력된 문자의 개수가 read()의 리턴 값이다.

package com.eomcs.io.ex03;

import java.io.FileReader;

public class Exam0220 {

  public static void main(String[] args) throws Exception {
    FileReader in = new FileReader("temp/test2.txt");

    char[] buf = new char[100];

    int count = in.read(buf); 

    in.close();

    System.out.printf("%d\n", count);
    for (int i = 0; i < count; i++)
      System.out.printf("%c(%x)\n", buf[i], (int)buf[i]);

    System.out.println();

  }
}

원하는 개수만큼 문자를 입력받아 배열에 원하는 인덱스부터 차례로 담고 싶다면, read 메서드의 두번째 파라미터로 입력받아 담고자하는 인덱스의 시작점, 세번째 파라미터로 입력받고자하는 문자의 개수를 넘겨주면 된다. 다만, 원하는 개수만큼 입력되기 전에 파일에 더 이상 읽을 문작 수가 없는 경우, 원하는 개수보다 덜 입력될 수도 있다. read() 메서드의 리턴 값은 실제로 입력받은 문자의 개수이다.

import java.io.FileReader;

public class Exam0320 {

  public static void main(String[] args) throws Exception {
    FileReader in = new FileReader("temp/test2.txt");

    char[] buf = new char[100];

    int count = in.read(buf, 10, 40); // 40개의 문자를 읽어 10번 방부터 저장한다.

    in.close();

    System.out.printf("%d\n", count);

    for (int i = 0; i < 20; i++)
      System.out.printf("%c(%x) ", buf[i], (int) buf[i]);

    System.out.println();
  }
}


 

캐릭터 버퍼에 문자 입력

한번에 많은 char 들을 입력할 때, char[]이 아닌 CharBuffer를 read() 메서드의 파라미터로 넘겨줄 수도 있다. 단, CharBuffer에 입력받은 문자를 담을 때마다 커서의 위치가 한칸씩 뒤로 옮겨지기 때문에 CharBuffer에 담긴 char들을 처음부터 읽으려면 커서의 위치를 처음으로 되돌려야 한다. 이처럼 커서의 위치를 처음으로 위치시키는 메서드는 flip() 메서드이다.

import java.io.FileReader;
import java.nio.CharBuffer;

public class Exam0420 {

  public static void main(String[] args) throws Exception {
    FileReader in = new FileReader("temp/test2.txt");

    CharBuffer charBuf = CharBuffer.allocate(100);

    int count = in.read(charBuf);

    in.close();

    System.out.printf("%d\n", count);

    charBuf.flip();

    System.out.printf("[%s]\n", charBuf.toString());
  }
}