Java에서 파일과 스트림(Stream)은 데이터를 저장하거나 읽고 쓰는 작업에 중요한 역할을 한다.
1. File
파일은 컴퓨터의 저장 장치(디스크)에 데이터를 영구적으로 저장할 수 있는 구조이다.
어떤 소프트웨어로 생성한 데이터를 저장하면 데이터 파일이 생성되고 그 파일을 읽을 수 있다.
파일은 크게 텍스트 파일(사람이 읽을 수 있음)과 바이너리 파일(사람 못 읽음)로 나뉜다.
예: java 프로그램(텍스트파일), 텍스트파일을 컴파일한 파일(바이너리 파일)
Java에서는 파일을 읽고 다루기 위해 java.io 패키지와 java.nio.file 패키지를 제공한다.
java.io.File
파일 및 디렉토리를 표현하는 클래스
파일의 존재 여부, 크기, 경로 확인, 삭제 등 메타 정보 처리에 사용한다.
import java.io.File;
public class FileExample {
public static void main(String[] args) {
File file = new File("example.txt");
if (file.exists()) {
System.out.println("파일 이름: " + file.getName());
System.out.println("파일 경로: " + file.getAbsolutePath());
} else {
System.out.println("파일이 존재하지 않습니다.");
}
}
}
java.nio.file.Files
파일과 디렉토리를 읽고 쓰는 작업을 간단하게 처리
Path 객체와 함께 사용한다
import java.nio.file.*;
public class FilesExample {
public static void main(String[] args) throws Exception {
Path path = Paths.get("example.txt");
Files.writeString(path, "Hello, Java!"); // 파일에 쓰기
String content = Files.readString(path); // 파일 읽기
System.out.println(content);
}
}
2. Stream
스트림은 데이터를 입력(Input) 또는 출력(Output) 형태로 연속적인 데이터의 흐름이다.
java에서는 데이터의 소스(파일, 네트워크, 메모리 등)에서 데이터를 읽거나 쓸 때 사용한다.
스트림은 취급하는 데이터에 따라 문자 스트림과 바이트 스트림으로 나뉜다.
스트림 | 입력 스트림 | 출력 스트림 |
문자 스트림 | Reader | Writer |
바이트 스트림 | InputStream | OutputStream |
1. 바이트 스트림 (InputStream, OutputStream)
- 바이트 단위(1 byte)로 데이터를 처리.
- 텍스트 파일뿐만 아니라 이미지, 동영상 등 이진 데이터 처리에 적합하다.
- 주요 클래스: FileInputStream / FileOutputStream - 파일에서 바이트 데이터를 읽거나 쓰는 데 사용
import java.io.FileOutputStream; // import문을 작성하지않으면 컴파일 에러 발생
public class ByteStreamExample {
public static void main(String[] args) throws Exception {
FileOutputStream fos = new FileOutputStream("example.txt");
fos.write("Hello, Java!".getBytes());
fos.close();
}
}
2. 문자 스트림 (Reader, Writer)
- 문자 단위(2 bytes)로 데이터를 처리.
- 유니코드(UTF-16)를 사용하여 텍스트 데이터 처리에 적합하다.
- 주요 클래스: FileReader / FileWriter - 파일에서 문자 데이터를 읽거나 쓰는 데 사용
import java.io.FileWriter;
public class CharStreamExample {
public static void main(String[] args) throws Exception {
FileWriter writer = new FileWriter("example.txt");
writer.write("Hello, Java!");
writer.close();
}
}
3. 버퍼 스트림 (BufferedInputStream, BufferedReader, 등)
- 데이터를 임시 버퍼에 저장하여 입출력 효율을 높임.
- BufferedReader는 텍스트 데이터 처리에 주로 사용한다.
import java.io.BufferedReader;
import java.io.FileReader;
public class BufferedStreamExample {
public static void main(String[] args) throws Exception {
BufferedReader reader = new BufferedReader(new FileReader("example.txt"));
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
reader.close();
}
}
텍스트 파일 읽어오기
1) 파일 열기
텍스트 파일을 읽기 위해서는 FileReader 스트림 클래스를 사용해야한다.
FileReader 클래스 오브젝트를 생성하여 파일 열기 (FileReader는 Reader의 서브 클래스)
FileReader inStr = new FileReader("file.txt");
2) 데이터 읽어오기
데이터를 읽기 위해 read()메소드를 사용한다.
read()
읽어온 문자를 int형 정부로 반환한다.
만약 읽어올 데이터가 없다면 -1을 반환한다.
int a;
a = in.read();
3) 파일 닫기
파일을 닫을때는 close()메소드를 사용한다.
in.close();
예시 코드
read()메소드로 파일을 끝까지 읽고 read()가 -1을 반환할때까지 읽기를 반복한다.
import javaq.io.*;
...
int a;
String str = new String();
while(a = in.read()) != -1)
str = str + (char)c;
System.out.print(str);
in.close();
...
바이너리 파일 읽기
1) 파일 열기
바이너리 파일을 읽기 위해서는 FileInputStream이라는 스트림 클래스의 오브젝트를 사용한다.
FileIInputStream in(오브젝트명) = new FileInputStream("파일명.dat");
2) 데이터 읽어오기
텍스트 파일과 같이 read()메소드를 사용해서 데이터를 읽어온다.
3) 파일 닫기
close()를 사용해서 파일을 닫는다.
바이너리 파일 쓰기
1) 파일 열기
바이너리 파일을 쓰기 위해서는 FileOutputStream 이라는 스트림 클래스의 오브젝트를 사용한다.
FileInputStream은 InputStream의 서브 클래스이다.
FileOutputStream out(오브젝트명) = new FileOutputStream("파일명.dat");
2) 데이터 써 넣기
데이터를 쓸때는 write() 메소드를 사용한다.
write()
인수로 주어진 데이터를 파일에 써 넣는다.
인수로는 int형, byte형 값을 지정한다.
3) 파일 닫기
close()를 사용해서 파일을 닫는다.
바이트 스트림 데이터를 문자 스트림으로 변환하기
Java에서는 InputStreamReader와 OutputStreamWriter 클래스를 사용하여 바이트 스트림을 문자 스트림으로 변환할 수 있다.
InputStreamReader
- 정의: 바이너리 데이터를 문자 입력으로 변환하는 클래스
- 용도: 파일, 네트워크 등에서 읽어온 바이트 데이터를 문자열로 처리할 때 사용
- 특징: 바이너리 데이터를 읽을 InputStream 객체를 인수로 받음
FileInputStream inFile = new FileInputStream("파일명.dat");
InputStreamReader in = new InputStreamReader(infile);
OutputStreamWriter
- 정의: 문자 데이터를 바이너리 출력으로 변환하는 클래스
- 용도: 문자열 데이터를 파일이나 네트워크에 바이너리 형태로 출력할 때 사용
- 특징: 바이너리 데이터를 출력할 OutputStream 객체를 인수로 받음
FileOutputStream outFile = new FileOutputStream("파일명.dat");
OutputStreamWriter out = new OutputStreamWriter(outFile);
예시 코드
import java.io.*;
public class InOut{
purblic static void main(String[] args){
try{
String filename = "file.dat";
FileOutputStream out = new FileOutputStream(filename);
FileInputStream file = new FileInputStream(filename);
InputStreamReader in = new InputStream(file);
// 데이터 입력(쓰기)
for(byte i = 1; i <= 10; i++){
out.write(i);
}
// 데이터 읽고 출력하기
int a;
while((a =in.read()) != -1 ){
System.out.println( a + " ");
}
in.close();
out.close();
} catch (IOException e){ // 입출력 예외 클래스
System.out.println("파일이 존재하지 않습니다");
}
}
}
// 실행 결과
1 2 3 4 5 6 7 8 9 10
3. 키보드 입력 스트림으로 읽기
1) System.in.read()
- 키보드로 입력한 반각 문자(1byte)를 입력 받을때 read() 메소드를 사용할 수 있다.
- 이 메서드는 입력된 문자(또는 기호)의 ASCII 코드 값을 반환하며, System.in 객체와 함께 사용된다.
int a;
a = System.in.read(); // 키보드 입력 값을 ASCII 값으로 읽음
System.out.println("입력된 값의 ASCII 코드: " + a);
2) BufferReader
- BufferedReader 클래스는 버퍼를 사용하여 입력 데이터를 효율적으로 처리할 수 있다.
- 키보드로 입력한 데이터를 행 단위로 읽을 수 있으며, readLine() 메서드를 사용하면 문자열 형태로 반환됩니다.
readLine() 메소드
읽어들인 한 행의 데이터를 String 클래스의 문자열로 반환한다.
- 만약 읽을 데이터가 없다면 null을 반환한다.
import java.io.*;
public class Main {
public static void main(String[] args) throws IOException {
// InputStreamReader를 사용해 System.in을 문자 스트림으로 변환
InputStreamReader isr = new InputStreamReader(System.in);
// BufferedReader로 행 단위 입력 처리
BufferedReader br = new BufferedReader(isr);
// 문자열 입력 요청
System.out.print("입력하세요: ");
String input = br.readLine(); // 한 줄을 문자열로 읽음
System.out.println("입력된 값(문자열): " + input);
// 문자열을 int형으로 변환하여 출력
int num = Integer.parseInt(input); // 문자열을 정수로 변환
System.out.println("입력된 값(정수): " + num);
}
}
📌 문자열을 정수로 변환시, 입력된 값이 숫자가 아니라면 NumberFormatException 이 발생할 수 있다.
4. Serialization(직렬화)
- 직렬화(Serialization)는 객체를 연속된 데이터 형식(바이트 스트림)으로 변환하여 파일, 데이터베이스, 네트워크 등으로 저장하거나 전송할 수 있도록 만드는 과정
- 역직렬화(Deserialization)는 입력 스트림으로 읽어들여 바이트 스트림을 다시 객체로 복원하는 과정
- 객체를 직렬화하려면 해당 클래스가 java.io.Serializable 인터페이스를 구현해야한다.
- Serializable은 마커 인터페이스로, 특별한 메서드를 제공하지 않고 단순히 직렬화 가능 여부를 표시한다.
- 직렬화된 객체는 파일 저장, 네트워크 전송, 캐싱 등에 활용된다.
import java.io.*; // Serializable 인터페이스를 구현하기위해 import
class User implements Serializable {
private static final long serialVersionUID = 1L; // 직렬화 버전 관리
String name;
int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
}
public class SerializationExample {
public static void main(String[] args) {
User user = new User("Alice", 25);
// 객체를 직렬화하여 파일로 저장
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("user.ser"))) {
oos.writeObject(user);
System.out.println("객체가 직렬화되었습니다.");
} catch (IOException e) {
e.printStackTrace();
}
// 파일에서 객체를 역직렬화
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("user.ser"))) {
User deserializedUser = (User) ois.readObject();
System.out.println("역직렬화된 객체: " + deserializedUser.name + ", " + deserializedUser.age);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
// 출력
객체가 직렬화되었습니다.
역직렬화된 객체: Alice, 25
직렬화를 사용하면 객체를 파일로 저장하거나 RMI(Remote Method Invocation)에서 객체를 직렬화하여 전달 할 수 있다.
하지만 CPU와 메모리를 많이먹고 클래스 변경시 역직렬화가 어려운점, 보안 문제로인해 JSON 이나 XML을 사용한다.
Java의 기본 직렬화가 아닌 다른 직렬화를 사용하고 싶다면 Protocol Buffers (Protobuf) 이나Kryo 를 사용하면 된다.
JSON 또는 XML
텍스트 기반 형식으로 더 읽기 쉽고, 언어 간 호환이 좋음
Protocol Buffers
(Protobuf)Google이 개발한 바이너리 직렬화 포맷으로, 성능이 뛰어나고 스키마를 사용
Kryo
Java의 빠르고 효율적인 객체 직렬화 라이브러리
입출력 퀴즈를 풀면서 에러가 정말 많이 나서 예외도 같이 정리해본다.
5. 예외
예외(Exception)은 프로그램 실행시 발생하는 에러를 말한다.
이런 예외 상황에 대응하는것을 예외 처리라고 한다.
예외처리를 하지 않으면 프로그램이 비정상적으로 종료 될 수 있다.
예외 처리를 통해서 프로그램의 비정상적인 종료를 막고 사용자에게 적절한 에러 메세지를 제공할 수 있다.
예외 처리 방법
1. try~ catch~ (finally~ )
try/catch 문을 사용하면 예외를 명시적으로 처리할 수 있다.
try {
예외가 발생할 것 같은 처리 기술
} catch (예외클래스명 변수명){
예외 발생시 수행할 처리
} finally {
처리
}
catch (Exception e) {}
예외의 종류를 나타내는 예외 오브젝트를 인수로 받는다.
Exception - 예외 클래스명
e - 변수명
finally {}
{} 안의 처리는 예외가 일어나든 일어나지 않든 항상 실행되며, finally문은 생략 가능하다
2. throws
throws는 메서드에서 발생할 가능성이 있는 체크 예외(Checked Exception)를 호출자에게 알리기 위해 사용된다.
체크 예외의 경우, 호출자가 이를 처리하거나 다시 던지지 않으면 컴파일 에러가 발생한다.
반면에, 런타임 예외는 호출자가 처리하지 않아도 컴파일 에러는 발생하지 않는다.
a(b) 호출 시 예외 발생시, 호출부가 try 블록 안에 있고 해당 예외를 처리할 수 있는 catch 블록이 있다면, 예외는 catch 블록으로 전달되어 처리된다.
Exception은 Java의 예외 계층 구조에서 체크 예외의 최상위 클래스이기 때문에, 이를 사용하면 모든 체크 예외를 처리할 수 있다.
하지만 RuntimeException이나 Error는 포함되지 않으므로, 모든 상황에 대응하려면 Throwable을 사용해야한다.