나루나른 2024. 3. 27. 21:06

파일 입출력

자바에서 파일 입출력(File I/O)은 java.io 패키지를 통해 이루어진다. 이 패키지는 데이터를 읽고 쓰기 위한 다양한 스트림 클래스들을 제공하는데, 여기서 스트림(Stream)이란 데이터의 연속적인 흐름을 의미하며, 자바에서는 이를 이용해 파일과 프로그램 사이의 데이터 입출력을 처리한다.

 

파일 읽기(FileInput)

파일에서 데이터를 읽기 위해서는 주로 'FileInputStream', 'BufferedReader'와 같은 클래스를 사용한다. BufferedReader를 사용할 때는 FileReader를 함께 사용하여 파일로부터 문자 데이터를 효율적으로 읽어들일 수 있다.

ex) FileInput이 있다면 같은 쌍으로 FileOutput이 오는 구조

 

파일 읽기의 예)

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

public class ReadFileExample {
    public static void main(String[] args) {
        try (BufferedReader br = new BufferedReader(new FileReader("example.txt"))) {
            String line;
            while ((line = br.readLine()) != null) {
                System.out.println(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

 

 

파일 쓰기(FileOutput)

파일에 데이터를 쓰기 위해서는 'FileOutputStream', 'BufferedWriter' 등을 사용할 수 있다. BufferedWriter 의 경우 FileWriter와 함께 사용되며, 문자 데이터를 파일에 효율적으로 쓸 수 있게 해준다.

 

파일 쓰기의 예)

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

public class WriteFileExample {
    public static void main(String[] args) {
        try (BufferedWriter bw = new BufferedWriter(new FileWriter("example.txt"))) {
            bw.write("Hello, World!");
            bw.newLine(); // 새로운 줄 추가
            bw.write("Java File I/O example.");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

 

대표적으로 파일 입출력에서 File 클래스는 파일에 관련된 정보를 알 수 있는 클래스다. 파일명, 파일 크기, 확장자 명, 파일 삭제, 파일 타입 등의 정보가 들어가 있다. 단, File 클래스는 파일 내용을 읽고 쓰는 메서드는 제공하지 않기 때문에 파일의 내용을 다루기 위해서는 'FileInputStream', 'FileOutputStream', 'FileReader', 'FileWriter' 등 다른 입출력 스트림 클래스를 사용해야 한다.

 

File 클래스의 예)

try {
	//예외 발생이 예상되는 코드를 안에 넣는다
	//경로명 + 파일명
	String pathname = "C:\\frontend\\randomimage.png";
	//경로명으로 '/' 기호도 가능하다.
			
	//File 클래스에 파일 담기
	File file= new File(pathname);
			
	if(file.exists()) {
		System.out.println("파일을 정상적으로 가져왔습니다");
	}else {
		System.out.println("파일을 찾을 수 없습니다. 경로를 다시 확인해주세요.");
	}
			
	} catch (Exception e) {
	//예외가 발생되면 해당 부분을 처리할코드를 작성
	e.printStackTrace();
	}
		
	System.out.println("END");//정상적으로 프로그램이 실행 후 종료되었다는 메세지를 출력
}

 

또한 자바에서의 파일 입출력은 크게 바이트(Byte) 기반, 문자(Character ) 기반으로 나뉜다. 바이트 기반은 1byte씩 데이터를 읽어오고, 문자 기반은 2byte씩 데이터를 읽어온다.

 

바이트 스트림(InputStream / OutputStream):

  • 이미지, 오디오 파일 등 모든 종류의 데이터를 읽고 쓰는 데 사용된다. 'FileInputStream''FileOutputStream'이 이에 속한다.

문자 스트림(Reader / Writer):

  • 텍스트 데이터를 읽고 쓰는 데 최적화되어 있다. 'FileReader'와 'FileWriter', 'BufferedReader''BufferedWriter'가 이에 해당한다.

그 외에도 파일 입출력 작업은 IOException을 포함한 여러 예외를 발생시킬 수 있으므로, 따라서 파일 입출력 연산은 반드시 '예외 처리 구문(try-catch) 안에서 수행되어야 한다는 주의사항이 존재한다.'

Java 7 이상에서는 try-with-resources 문을 사용하여 자원을 자동으로 관리(자동으로 닫기)할 수 있다. 이 방식은 try-catch구문 안에서 실행된 파일 입출력 작업 이후에  finally를 통해 사용한 자원을 닫아주어야 하는 번거로움을 줄여준다.(closing 작업의 번거로움이 없다.) 또한 'finally를 통한 자원 반납의 경우 반납의 순서를 주의해야한다.' 

 

finally 블록을 통한 자원 반납의 순서가 중요한 이유는 '자원이 서로 의존하는 경우가 많기 때문이다'. 예를 들어, 파일을 읽거나 쓰는 작업에서 FileReader, FileWriter, BufferedReader, BufferedWriter와 같은 스트림들을 사용할 경우 이 스트림들은 특정 순서로 열리고 반대 순서로 닫혀야 한다. 이는 '한 자원이 다른 자원에 의존하여 작동하기 때문에 BufferedReader는 내부적으로 FileReader에 의존하며 따라서, BufferedReader를 먼저 닫고 그 다음에 FileReader를 닫아야 한다.' 이 순서를 지키지 않은 경우 이로 인해 리소스 누수로 이어질 수 있으며, 예상치 못한 오류의 원인이 되기도 한다.

 

자원 반납의 예)

FileReader fr = null;
BufferedReader br = null;
try {
    fr = new FileReader("someFile.txt");
    br = new BufferedReader(fr);
    // 파일 읽기 작업 등...
} catch (IOException e) {
    e.printStackTrace();
} finally {
    if (br != null) {
        try {
            br.close(); // 먼저 의존하는 자원을 닫는다.
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    if (fr != null) {
        try {
            fr.close(); // 그 다음에 의존된 자원을 닫는다.
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

 

 

● 스레드(Thread) 

자바에서 스레드(Thread)는 프로세스 내에서 실제로 작업을 수행하는 단위다. 동시에 실행될 수 있는 프로그램의 흐름이기도 하며 자바는 멀티 스레딩을 지원하여, 하나의 프로세스 내에서 여러 작업을 동시에 수행할 수 있게 한다. 이를 통해 자원을 효율적으로 사용하고, 프로그램의 응답성을 향상시킬 수 있다.

 

자바에서 스레드를 생성하고 실행하는 데는 두 가지 방법이 있는데  

 

1. Thread 클래스를 상속받는 방법 : 이 방법은 Thread 클래스를 상속받아, 'run()' 메서드를 오버라이드하여 스레드가 실행할 코드를 정의한다. 그 다음, 해당 클래스의 인스턴스를 생성하고 'start()' 메서드를 호출하여 스레드를 실행하는 방법이다.

 

Thread 클래스 상속의 예)

//쓰레드 클래스를 상속받아 쓰레드 관리자가 관리해준다.
class MyThread2 extends Thread{
	private int num;
	private String name;
	
	public MyThread2() {}
	
	public MyThread2(int num, String name) {
		this.num = num;
		this.name = name;
	}
	
    //run() 메소드를 오버라이드 하여 구현하고자 하는 비지니스 로직 구현을 구현함
    //여기서 start() 함수는 run()함수를 호출하는 기능만 하므로 override하면 코드의 동작이 꼬인다.
	@Override
	public void run() {
		for(int a=0; a<num; a++) {
			System.out.println(name + ":" + a);
		}
	}
}

public class thread {
	public static void main(String[] args) {
		//쓰레드를 사용하는 경우
		//JVM(자바 가상머신)이 쓰레드 관리자에 등록하고, start()메소드가 run()을 호출한다.
		//주로 실시간으로 동작하는 채팅 및 실시간 예매 시스템에 많이 사용한다.
		
		MyThread2 t2 = new MyThread2(5,"★");
		MyThread2 t2_2 = new MyThread2(5,"★★");
		MyThread2 t2_3 = new MyThread2(5,"★★★");
		
        //쓰레드가 없이 사용할 경우는 먼저 호출한 데이터부터 순서대로 처리해서 출력함.
        //쓰레드가 사용될 경우 호출 순서에 상관없이 먼저 처리된 데이터부터 화면이 출력함.
		t2.start();
		t2_2.start();
		t2_3.start();
	}
}

 

자바 콘솔창에서의 출력결과

 

2. Runnable 인터페이스를 구현하는 방법 :  Runnable 인터페이스를 구현하고, run() 메서드 내에 스레드가 실행할 작업을 정의한다. Runnable 객체를 Thread의 생성자에 전달하고, Thread 객체의 start () 메서드를 호출하여 스레드를 실행한다.

 

Runnable 인터페이스를 통한 Thread 구현 예)

//쓰레드 클래스를 상속받아 쓰레드 관리자가 관리해준다.
class MyThread3 implements Runnable{
		private int num;
		private String name;
		
		public MyThread3() {}
		
		public MyThread3(int num, String name) {
			this.num = num;
			this.name = name;
		}
		
		@Override
			public void run() {
				// TODO Auto-generated method stub
			for(int a=0; a<num; a++) {
				System.out.println(name+":"+a);
			}
		}
}

public class thread {
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		//Runnable 인터페이스를 이용해 쓰레드를 구현한 경우
		/*
		 	interface Runnable{}
		 	class Thread implements Runnable{}
		*/
		
		Thread t1 = new Thread(new MyThread3(5, "★"));
		Thread t2 = new Thread(new MyThread3(5, "★★"));
		Thread t3 = new Thread(new MyThread3(5, "★★★"));
		
		t1.start();
		t2.start();
		t3.start();
	}
}

자바 콘솔창에서의 출력 결과