java

사용하는 패키지

대부분, 파일이나 통신에서의 입출력을 처리해야 할 때에는 java.io 패키지를 많이 사용한다. 거의 모든 언어에서도 지원하는 바이트 스트림(Byte Stream, C언어 파일 입출력에서도 사용하는 그것이다) 처리 인터페이스는 대표적으로 InputStream과 OutputStream 클래스를 사용한다. 또한 자바에는 문자열을 사용할 수 있기 때문에, 문자열 스트림(Character Stream) 입출력을 위해 추상 클래스인 Reader와 Writer가 정의돼 있다.


파일 쓰기

파일 생성

예제 코드를 통해 간단히 자바의 파일 생성에 대해 알아보도록 하자.

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

public class WriteFile {
    public static void main(String[] args) throws IOException {
        FileOutputStream output = new FileOutPutStream("c:/example.md");
        output.close();
    }
}

참고로, 위의 코드는 Windows 환경에 기반한 코드이다. UNIX나 임베디드에서도 비슷한 코드를 작성하면 작동하지만, 파일 경로에서 차이가 있을 수 있다.

파일을 생성하기 위해서 어떤 클래스를 사용했는가? FileOutputStream 클래스를 사용했다. 이 클래스를 사용하려면 생성자로 파일의 경로와 이름을 전달해야만 한다. 위의 예제에서는 c:/ [1]example.md 라는 파일을 생성한다.
단어를 보면서 직관적으로 이해를 해보면, 위 코드는 output 이라는 이름의 새로운 출력 스트림을 생성하는 코드라고 볼 수 있으며, 그 출력 스트림을 c:/example.md 에 연결하는 것이라고 볼 수 있다.
코드의 마지막줄에서는 output.close(); 라고 돼 있는데, 이것은 사용한 출력 스트림을 닫아주는 것이다. 이 코드는 ‘자바에서는’ 필수적이지는 않다. 자바 프로그램이 종료될 때, 알아서 출력 스트림을 닫아주기 때문이다. 그렇지만 스트림이 프로그램이 실행되는 동안 열려있어서 생기는 에러나 보안과 같은 이유를 생각하면, 프로그래머가 직접 닫아주는 것이 여러모로 낫다고 볼 수 있다.


내용 쓰기

출력할 파일을 생성했으면, 이번엔 파일 안에 내용을 작성해보자.

FileOutputStream

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

public class WriteFile {
    public static void main(String[] args) throws IOException {
        FileOutputStream output = new FileOutputStream("c:/example.md");
        for (int i = 1; i < 11; i++) {
            String data = i + " 번째 줄입니다.rn";
            output.write(data.getBytes());
        }
        output.close();
    }
}

대개 바이트 스트림이 그렇듯, InputStream과 OutputStream은 바이트 단위로 데이터를 처리하는 클래스이다. 이 OutputStream 클래스를 상속받아 만든 클래스가 FileOutputStream이다. 예상할 수 있듯이, 이 클래스 역시 바이트 단위로 데이터를 처리한다.
코드를 잠깐 살펴보면, 파일에 줄 수를 표시하는 내용을 쓰고 있음을 알 수 있다. 참고로, rn 이라고 기술한 것은 Windows 환경을 기준으로 작성한 코드이기 때문이다. Windows에서는 개행 문자로써 CRLF 형태를 사용하기 때문에, 위와 같이 작성해야 메모장과 같은 프로그램에서 정상적으로 표현이 된다.
[2]
그런데, 문자열을 바이트 단위로 처리하려니 해당 바이트 수 만큼 써주어야 하기 때문에 문자열인 data에서 data.getBytes() 를 해서 바이트 수를 받아와야 하는데, 어느정도 불편한 부분이 있다. 그래서 이를 해결하기 위해 FileWriter 클래스를 사용해 보자.

FileWriter

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

public class WriteFile {
    public static void main(String[] args) throws IOException {
        FileWriter output = new FileWriter("c:/example.md");
        for (int i = 1; i < 11; i++) {
            String data = i + " 번째 줄입니다.rn";
            output.write(data);
        }
        output.close();
    }
}

바뀐 점은 FileOutputStream을 FileWriter로 대체한 것과, 그로 인해서 .getBytes() 를 할 필요가 없어졌다는 점이다.
그럼에도 불구하고, rn 을 항상 붙여야 하는 점은 아직도 불편하다. 이 때 우리의 뇌리를 스쳐 지나가는 메소드가 하나 있다. println()이다. 콘솔 창에서 출력 할 때, 자바에서는 println() 으로 한 줄 씩 출력할 수 있었다. 이는 파일 입출력에서도 동일하게 적용된다. 이를 사용하기 위해서는 FileWriter 대신 PrintWriter를 사용하면 된다.

PrintWriter

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

public class WriteFile {
    public static void main(String[] args) throws IOException {
        PrintWriter output = new PrintWriter("c:/example.md");
        for (int i = 1; i < 11; i++) {
            String data = i + " 번째 줄입니다.";
            output.println(data);
        }
        output.close();
    }
}

이와 같이 다양한 패키지를 사용해서 파일 입출력 코드를 다양한 방법으로 작성할 수 있다.


내용 추가

여러분들이 간단한 게임을 개발한다고 가정하자. 세이브 데이터가 하나 파일에 작성되어 있는데, 유저가 세이브 데이터를 하나 더 만들려고 한다. 이 때, 우리는 필연적으로 파일에 내용을 하나 추가해야 하는 상황에 직면하게 된다.
자바에서 이와 같은 상황을 처리하기 위해서는 파일 출력 클래스(FileWriter, PrintWriter 등)를 객체화 할 때 생성자의 매개 변수를 변경해주면 된다. FileWriter의 경우엔 간단한데, PrintWriter의 경우엔 다소 복잡하다. rn 을 안 쓰기 위한 수고라고 생각하자.

FileWriter

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

public class WriteAddFile {
    public static void main(String[] args) throws IOException {
        FileWriter output = new FileWriter("c:/example.md");
        for (int i = 1; i < 11; i++) {
            String data = i + " 번째 줄입니다.rn";
            output.write(data);
        }
        output.close();

        FileWriter add = new FileWriter("c:/example.md", true);
        for (int i = 11; i < 16; i++) {
            String data = i + " 번째 줄입니다.rn";
            add.write(data);
        }
        add.close();
    }
}

add라는 객체를 생성하는 줄을 잘 보자. 생성자의 두 번째 매개변수로 true를 전달하고 있다. 두 번째 매개변수는 boolean 형으로, 파일을 추가모드(append mode)로 열 것인지를 전달하는 매개변수이다. 파일을 추가 모드로 열면, 기존 파일의 내용(기존 파일의 마지막 바이트) 이후부터 파일이 쓰여지게 된다.

PrintWriter

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

public class WriteAddFile {
    public static void main(String[] args) throws IOException {
        PrintWriter output = new PrintWriter("c:/example.md");
        for (int i = 1; i < 11; i++) {
            String data = i + " 번째 줄입니다.";
            output.println(data);
        }
        output.close();

        PrintWriter add = new PrintWriter(new FileWriter("c:/example.md", true));
        for (int i = 11; i < 16; i++) {
            String data = i + " 번째 줄입니다.";
            add.println(data);
        }
        add.close();
    }
}

마찬가지로 add 객체를 생성하는 줄을 잘 보자. 매개변수로써 FileWriter 객체를 전달하고 있다. 이 때, PrintWriter는 FileWriter의 출력 스트림을 사용하게 되고, 추가 모드로 열리게 된다.


파일 읽기

파일을 읽는 경우도 출력과 비슷하게 FileInputStream 처럼 바이트 단위로 읽어오거나 FileReader와 BufferedReader를 함께 사용하여 라인 단위로 읽어오는 방법이 있다.

FileInputStream

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

public class ReadFile {
    public static void main(String[] args) throws IOException {
        byte[] buffer = new byte[1024];
        FileInputStream input = new FileInputStream("c:/example.md");
        input.read(buffer);
        System.out.println(new String(buffer));
        input.close();
    }
}

위와 같이 FileInputStream을 사용해서 버퍼의 사이즈 만큼 파일의 내용을 읽어올 수 있다. 다만, 버퍼의 크기 만큼 읽어오기 때문에 정확히 읽고 싶은 데이터의 길이를 알지 못한다면 불편할 수 있다. 반복문과 조건문을 이용해서 1 바이트 씩 읽어오는 식으로, 어느정도 코드가 길어질 수 있다.
이럴 때, FileReader와 BufferedReader를 함께 사용하면 된다.

FileReader, BufferedReader

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

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

위의 코드와 같이, BufferedReader의 매개변수로써 FileReader 객체를 전달하면 BufferedReader의 readLine() 메소드를 사용하여 파일을 라인 단위로 읽을 수 있다. 만약 더 읽을 라인이 없으면, 메소드에서는 null을 반환한다. 즉, 반복문을 빠져나가게 된다.

참고

점프 투 자바 파일 입출력


  1. 대개 Windows OS가 설치돼 있는 그 경로 최상단 ↩︎
  2. 궁금하다면 GoogleLF CRLF 라는 키워드로 검색해보도록 하자 ↩︎