파일 input/output은 언어에 상관없이 아주 자주 쓰이는 기본적인 기능 중 하나다. 지금부터 자바에서의 파일 input/output에 대해 살펴보도록 하자.

File output: FileOutputStream

첫 번째로, 파일 output을 위해 자바의 라이브러리 클래스인 FileOutSteam을 사용할 수 있다. 아래의 예시는 프로젝트 폴더에 "out.txt"라는 파일을 생성하고 "hello, world"를 출력한다. 파일이 생성될 경로는 변경할 수 있는데, 경로를 따로 지정해 주지 않았을 때 기본 경로는 프로젝트 폴더가 된다.

import java.io.FileOutputStream;
import java.io.IOException;

public class example {
    public static void main(String[] args) throws IOException {
        FileOutputStream output = new FileOutputStream("out.txt");
        String str = "hello, world";
        byte[] bytes = str.getBytes();
        output.write(bytes);
        output.close();
    }
}

FileOutputStream을 사용하기 위해서는 FileOutputStream뿐 아니라 IOException을 import 해 줘야 한다. 일단 알아 두도록 하자.

Exception에 대한 내용은 차차 다루도록 한다.

위 코드가 실행되는 과정은 다음과 같다.

  • 먼저, FileOutputStream 객체가 만들어진다.
  • 생성자의 매개변수는 경로를 포함한 파일의 이름이다.
    • 경로는 절대경로거나 상대경로이다.
    • 절대경로: "c:/out.txt"
    • 상대경로: "src/example/out.txt"
  • FileOuptutStream 객체를 생성하고 나면, write 메서드를 사용해서 파일에 무언가를 쓸 수 있다.

  • write 메서드는 인자로 byte 배열을 받는다.

  • String 클래스의 인스턴스 메서드인 getBytes를 사용해서 String을 byte 배열로 변환할 수 있다.

  • 파일에 원하는 것을 다 쓰고 나면, close 메서드를 사용해서 파일을 닫아 준다.

  • 파일이 누군가에 의해 열릴 수 있기 전에 꼭 닫아 주어야 한다.

File output: FileWriter

두 번째로, FileWriter를 사용해서 파일에 무언가를 쓸 수 있다. FileOutputStream은 파일에 byte를 쓰기 때문에, String을 바로 파일에 쓸 수 없다. 하지만 FileWriterFileOutputStream 대신에 사용하면 String을 바로 쓸 수 있다!

import java.io.FileWriter;
import java.io.IOException;

public class example {
    public static void main(String[] args) throws IOException {
        FileWriter fw = new FileWriter("out.txt");
        for(int i = 0; i < 10; i++) {
            String str = "This is line number" + i + "\n";
            fw.write(str);
        }
        fw.close();
    }
}

FileOutputStream은 byte의 writing stream을 위한 것인 반면 FileWriter는 character의 writing stream을 위한 것이다.

File output: PrintWriter

PrintWriter는 파일 출력을 위해 사용되는 좀 더 편리한 클래스이다. System.out과 유사하다.

import java.io.PrintWriter;
import java.io.IOException;

public class example {
    public static void main(String[] args) throws IOException {
        PrintWriter pw = new PrintWriter("src/cse3040/out.txt");
        for(int i=0; i<10; i++) {
        String str = "This is line number " + i + ".";
        pw.println(str);
        }
        pw.close();
    }
}

File output: appending to a file

파일에 무언가를 쓰기 위해 FileWriter 객체를 생성하면 새 빈 파일이 열린다.

  • 만약 그 파일이 이미 존재하고 있었다면, 내용은 지워진다.

내용이 지워진 상태에서 새로운 내용을 쓰는 게 아니라 내용을 추가하고 싶다면 FileWriter 객체를 생성할 때 파일 open mode를 알려 주는 두 번째 인자를 함께 전달해야 한다.

import java.io.FileWriter;
import java.io.IOException;

public class example {
    public static void main(String[] args) throws IOException {
        FileWriter fw = new FileWriter("out.txt");
        for(int i=0; i<10; i++) fw.write("This is line number " + i + "\n");
        fw.close();
        FileWriter fw2 = new FileWriter("out.txt", true);
        for(int i=10; i<20; i++) fw2.write("This is line number " + i + "\n");
        fw2.close();
    }
}

PrintWriter 생성자의 경우는 append 매개변수를 가지지는 않지만 FileWriter를 사용해서 생성될 수 있다.

import java.io.FileWriter;
import java.io.PrintWriter;
import java.io.IOException;

public class example {
    public static void main(String[] args) throws IOException {
        PrintWriter pw = new PrintWriter("out.txt");
        for(int i=0; i<10; i++) pw.println("This is line number " + i);
        pw.close();

        PrintWriter pw2 = new PrintWriter(new FileWriter("out.txt", true));
        for(int i=10; i<20; i++) pw2.println("This is line number " + i);
        pw2.close();
    }
}

File input: FileInputStream

파일에서 데이터를 읽는 기본적인 방법은 FileInputStream을 사용하는 것이다. FileOutputStream과 유사하다.

  • 먼저 경로를 포함한 파일 이름으로 FileInputStream 객체를 생성한다
  • 파일에서 byte를 읽어 byte 배열에 저장하기 위해 read 메서드를 사용한다.
  • 데이터를 출력하기 위해, 데이터를 String으로 변환한다.
  • byte 배열의 크기는 1024이기 때문에, read 메서드는 1024(또는 차일의 끝) byte를 읽는다.
    • read 메서드는 byte의 수를 반환한다. 만약 EOF에 도달하면 -1을 반환한다.
import java.io.FileInputStream;
import java.io.IOException;

public class example {
    public static void main(String[] args) throws IOException {
        byte[] b = new byte[1024];
        FileInputStream input = new FileInputStream("out.txt");
        input.read(b);
        System.out.println(new String(b));
        input.close();
    }
}

파일이 byte 배열보다 크면, 배열의 크기와 같은 수의 byte만이 한 번의 read 메서드 호출마다 읽힐 것이다. 이 경우, 모든 파일을 읽기 위해 while loop를 사용할 수 있다.

import java.io.FileInputStream;
import java.io.IOException;

public class example {
    public static void main(String[] args) throws IOException {
        byte[] b = new byte[100];
        FileInputStream input = new FileInputStream("out.txt");
        while(input.read(b) != -1) {
            System.out.println(new String(b));
        }
        input.close();
    }
}

그런데 위 방법의 경우 출력해 보면 글자가 중간중간, 그리고 마지막에 이상하게 출력된 경우가 있다. 왜 그럴까? 중간중간에 우리가 보이게 이상하게 출력된 것은 byte 배열의 크기에 맞는 만큼만 읽어서 출력했기 때문인데, 마지막에 이상하게 출력된 이유는 대개 다음과 같다.

  • 가령, 마지막 read에서 읽어야 할 파일이 20byte가 남았다고 가정해 보자.
  • 이 마지막 20byte를 읽고 나면, 남은 80byte에는 전에 읽었던 것들이 그대로 남아 있다.
  • 마지막으로 읽은 byte와 전에 남아 있던 byte가 함께 출력되어 이상하게 보이는 것이다.

File input: FileReader and BufferedReader

FileInputStream보다 파일을 읽는 더 편리한 방법이 있다. FileReaderBufferedReader를 사용하는 것이다. 이들을 사용하면 한 번에 한 줄씩 읽을 수 있다.

FileInputStream은 byte들을 읽기 위한 것인 반면, FileReader는 character들을 읽기 위한 것이다.

BufferedReader는 파일에서 한 줄을 읽는 readLine 메서드를 제공한다.

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class example {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new FileReader("out.txt"));
        while (true) {
            String line = br.readLine();
            if (line == null)
                break;
            System.out.println(line);
        }
        br.close();
    }
}