▸JAVA/기본 문법

외부 데이터 입출력_java.io [2/3]

코데방 2019. 12. 16.
728x90

[ java.io 패키지 ]

  • 아직도 많이 사용되고 있는 구버전의 입출력 패키지
  • java.nio(New IO, NIO)와 함께 병행해서 사용되고 있음
  • 간단한 입출력의 경우 NIO보다 더 효율적일 수 있음

알게 모르게 항상 사용하게 되는 패키지입니다. NIO가 있긴 하지만 간단한 상황에서는 오히려 더 효과적일 수 있기 때문에 정확한 개념을 알아두는 것이 좋습니다. 외부 데이터와의 연동은 프로그램 성능에 큰 영향을 미칠 수 있습니다.

 

 

실제로는 엄청 복잡한 구조를 가지고 있지만 대략적인 구조와 개념을 알면 사용할 수 있음

 


아래 글은 파일 입출력을 예제로 하였습니다. 스트림을 이용한 네트워크 입출력은 아래 링크글을 참조하시면 됩니다.

 

2020/02/04 - [JAVA/기본 문법] - 네트워크_소켓(Socket) 통신_IO 입출력 [1/3]

 

[ File 클래스 ]

  • 파일의 위치 정보를 가지고 있음
  • 파일 내용을 다루는 것이 아닌, 파일 자체를 다루는 클래스
  • 파일 자체에 대한 여러 정보를 가져오고 편집할 수 있도록 하는 메소드를 포함하고 있음

먼저 File 클래스입니다. 파일을 열어서 편집하기 위해서는 파일위치와 파일명을 프로그램이 알고 있어야 합니다. 이 때 String 타입으로 정보를 넣어줄 수도 있지만 File 클래스의 인스턴스에 정보를 입력해두고 메소드를 이용하면 보다 안전하게 작업할 수 있습니다. 네트워크를 통한 입출력의 경우 유사하게 URL 클래스를 사용합니다. 

 

우선 파일 자체를 편집하기 전에 파일의 주소 또는 파일명을 가진 File 클래스의 인스턴스를 생성합니다. 그리고 인스턴스 메소드를 통해 해당 파일이 실제로 존재하는지, 파일의 길이가 얼마나 되는지, 해당 파일이 실제로 읽기/쓰기/실행이 가능한지 여부 등을 확인할 수 있습니다.

 

또한 파일 자체를 삭제하거나 이름을 변경하거나 권한을 부여하는 등의 조작도 File 클래스를 통해 가능합니다. File 클래스가 가지고 있는 메소드는 아래 링크를 참조하시면 됩니다.

 

2019/12/16 - [JAVA/라이브러리(API)] - java.io.File 주요 메소드 [1/1]

package study.first;

import java.io.File;

public class Main {
	public static void main(String[] args) {
		
		// 파일의 주소 및 파일명을 가진 file 객체 생성
		// input.txt : "Hello World..!!"
		File file = new File("C:\\JAVA\\FirstStudy\\FisrtStudy\\input.txt");
	
		// 파일이 실제 존재하는지 확인
		System.out.println(file.exists()); // true (파일이 존재함)
		
		// 파일의 길이 정보 확인 (byte)
		System.out.println(file.length()); // 15 (byte)
	}	
}

 


 

[ 기본 스트림 클래스들  ]

  • 기본 스트림 : 외부 데이터와 직접 연결되는 스트림(통로)를 생성함
  • 바이트 스트림(Btye Stream) : 1byte 단위로 데이터 입출력 수행
  • └  InputStream(입력 스트림) / OutputStream(출력 스트림)
  • 문자 스트림(Character Stream) : 문자 단위(2byte)로 데이터 입출력 수행
  • └ Reader(입력 스트림) / Writer(출력 스트림)

기본 스트림은 말 그대로 기본이 되는 스트림을 생성하는 클래스입니다. 기본이란 외부의 데이터와 직접 연결되는 통로를 생성하는 것을 의미합니다. 처음에는 기본 스트림만 사용하다가 속도 이슈를 해결하기 위해 아래에서 다룰 필터 스트림이라는 개념이 추가로 등장합니다. 그냥 기본 스트림 안에 추가하면 안되나 생각할 수도 있지만, 이미 기존 클래스로 만들어진 프로그램들에 영향을 미치지 않기 위해 개선된 기능들은 새로운 패키지나 클래스로 만들어지는 경우가 많습니다. 이 때문에 날이 갈 수록 클래스의 구조가 복잡해져서 깊이 공부할 수록 객체지향언어가 어려워지는 것 같습니다. 여담이지만 요즘 쉽다고 핫한 파이썬도 깊게 들어가면 오히려 더 어렵다고 합니다. 개인적으로는 외우는 걸 싫어해서 C언어가 제일 편한 것 같습니다..

 

바이트 스트림은 byte 단위로, 문자 스트림은 문자 단위로 데이터 입출력을 진행합니다. 문자 단위의 변환 처리를 해야하니 속도는 바이트 스트림이 빠르지만 한글과 같이 2byte 이상으로 이루어진 문자는 바이트 스트림으로 다루기 어렵기 때문에 문자 스트림을 사용하면 편리합니다.

 

java.io 패키지에서는 스트림(Stream)이라는 단방향 통로를 생성해서 외부 데이터와 통신합니다. 파일을 다룰 때 많이 쓰이는 "FileInputStream" 클래스의 인스턴스를 생성한다는 말은 해당 파일에서 Java 프로그램으로 데이터를 가져올 수 있는 통로를 하나 생성한다는 의미입니다. "FileOutputStream" 클래스는 반대의 개념입니다.

 

통로를 생성한 뒤, 해당 클래스에 포함된 메소드를 통해 데이터를 가져오거나 내보내게 됩니다. 스트림이란 말 그대로 하나의 "흐름"이기 때문에 데이터의 앞뒤로 왔다갔다하면서 필요한 데이터를 계속 뽑아내는 것이 아니라, 데이터의 0번지부터 끝(EOF)까지 순서대로 이동하며 가져옵니다. 즉, 한 번 데이터를 읽고 나면 다시 되돌아갈 수가 없습니다.

 

파일 위치와 파일명에 대한 정보는 직접 찍어줘도 되지만 앞서 설명한 File 클래스를 활용하여 조금 더 안전하게 작업할 수 있습니다. 기본 스트림 클래스들이 가지고 있는 메소드는 아래 링크를 참조하시면 됩니다.

 

2019/12/15 - [JAVA/라이브러리(API)] - java.io.FileInputStream 주요 메소드 [1/1]

2019/12/15 - [JAVA/라이브러리(API)] - java.io.FileOutputStream 주요 메소드 [1/1]

2019/12/15 - [JAVA/라이브러리(API)] - java.io.FileReader 주요 메소드 [1/1]

2019/12/15 - [JAVA/라이브러리(API)] - java.io.FileWriter 주요 메소드 [1/1]

package study.first;

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

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

		// 파일의 주소 및 파일명을 가진 file 객체 생성
		// input.txt : "Hello World..!!"
		File file = new File("C:\\JAVA\\FirstStudy\\FisrtStudy\\input.txt");

		// 파일이 존재할 경우 파일과의 스트림 생성
		if (file.exists()) {

			try (FileInputStream fi = new FileInputStream(file)) {

				System.out.println((char) fi.read()); // "H" 출력

			} catch (Exception e) {
				System.out.println("파일 작업에 실패했습니다.");
			}
		}
	}
}

 


 

[ 필터 스트림의 버퍼 클래스들 ]

  • 기본 스트림에 버퍼(Buffer)개념을 도입해 속도를 높임
  • 기본 스트림의 인스턴스를 생성자로 하며, 단독으로 외부 데이터와 연결될 수 없음
  • 기본 스트림의 메소드와 거의 동일하며, 약간의 추가 기능을 제공함

기본 스트림은 버퍼(Buffer)를 사용하지 않기 때문에 속도가 느립니다. 버퍼란 간단히 인터넷 동영상을 생각하면 됩니다. CPU의 연산 능력은 입출력 속도보다 월등하기 때문에, 입출력 속도에 맞춰서 움직이면 비효율적입니다. 인터넷 동영상을 볼 때 "버퍼링 중입니다"라는 메세지를 종종 볼 수 있듯이, 일단 버퍼라는 임시 공간에 데이터를 한번에 담은 뒤 데이터를 한번에 처리하는 것을 버퍼링이라고 합니다. 터 스트림은 버퍼를 사용하지 않는 기본 스트림에 버퍼 기능을 추가해서 쓸 수 있도록 해줍니다.

 

 

package study.first;

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

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

		// 파일의 주소 및 파일명을 가진 file 객체 생성
		// input.txt : "Hello World..!!"
		File file = new File("C:\\JAVA\\FirstStudy\\FisrtStudy\\input.txt");

		// 파일이 존재할 경우 파일과의 스트림 생성
		if (file.exists()) {

			// 기본 스트림을 생성자로 하는 필터 스트림 생성
			try (BufferedInputStream fi = 
					new BufferedInputStream(new FileInputStream(file))) {

				System.out.println((char) fi.read()); // "H" 출력

			} catch (Exception e) {
				System.out.println("파일 작업에 실패했습니다.");
			}
		}
	}
}

 


 

[ RandomAccessFile 클래스 ]

  • 파일의 데이터를 읽어올 위치(파일 포인터)를 앞 뒤로 옮길 수 있음
  • 파일 데이터의 일부분만 읽을 때 효율적임

위의 스트림을 통한 데이터 입출력은 순차적으로 이루어진다고 했습니다. 즉, 무조건 데이터를 가져와야 다음 데이터를 가져올 수 있습니다. skip() 메소드와 같이 읽어올 데이터를 건너뛸 수 있지만 이는 가져온 데이터는 그냥 없애버려서 우리 눈에 안보이게 하는 것이라 어찌됐건 데이터를 가져오는 작업을 해야합니다.

 

500글자에서 450번째 글자를 가져오고 싶다면 449개의 문자를 가져와서 버린 후(skip)에야 가능합니다. 이러한 문제점을 해결하기 위해 파일을 읽을 위치만 옮겨줄 수 있는 기능을 가진 클래스가 해당 클래스입니다. 파일 포인터를 450번째 위치에 가져다두고 그 위치부터 데이터를 읽어옵니다.

 

하지만 마냥 좋지만도 않은 것이, 일단 읽어올 때 버퍼를 사용하지 않기 때문에 기본 스트림과 같이 속도가 느립니다. 또한 1byte 단위로 읽어오기 때문에 2byte 이상으로 이루어진 한글 같은 문자는 가져와서 다시 조합해주는 번거로움이 필요합니다. 따라서 아주 작은 일부분의 데이터만 조작할 때 사용하고, 또는 다음글에서 다룰 NIO의 채널을 생성해서 같이 사용하는 방식으로 사용할 수 있습니다.

package study.first;

import java.io.RandomAccessFile;

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

	
		// "Hello World..!!"
		try (RandomAccessFile rd = new RandomAccessFile("input.txt", "r")) {
			
			rd.seek(6); // 파일포인터를 인덱스 6으로 옮김 
			System.out.println((char)rd.read()); // "W" 출력
			
			rd.seek(1); // 파일포인터를 인덱스 1로 옮김
			System.out.println((char)rd.read()); // "e" 출력
			
		} catch(Exception e) {
			
			System.out.println("실패");
		}		
	}
}

 


 

[ FileDescriptor 클래스 ]

  • 이미 생성돼 있는 스트림의 연결정보를 가진 클래스
  • 연결정보를 통해 같은 스트림을 가진 여러 개의 스트림 클래스 인스턴스를 생성할 수 있음

정보가 많이 없는 클래스라 정확한 사용 용도는 잘 모르겠습니다. 일단 생성된 스트림 정보를 가지고 있다가 새로운 스트림 클래스의 인스턴스를 생성할 때 인자로 사용할 수 있습니다.

 

아래 예시와 같이 파일의 입력 스트림을 가진 'fi' 변수에서 메소드를 통해 FileDescriptor 타입 인스턴스를 생성해주고, 이 인스턴스를 통해 다시 다른 파일 입력 스트림을 가진 'f2'변수를 만들게 되면 두 변수는 동일한 스트림을 공유하게됩니다. 'fi'스트림을 통해 1byte를 읽고 다시 'f2'스트림을 통해 1byte를 읽어보면 같은 스트림이 이동된 걸 알 수 있습니다.

package study.first;

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

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

		// 파일의 주소 및 파일명을 가진 file 객체 생성
		// input.txt : "Hello World..!!"
		File file = new File("C:\\JAVA\\FirstStudy\\FisrtStudy\\input.txt");

		// 파일이 존재할 경우 파일과의 스트림 생성
		if (file.exists()) {

			// 기본 스트림을 생성자로 하는 필터 스트림 생성
			try (FileInputStream fi = 
					new FileInputStream(file)) {

				// fi와 같은 권한을 가진 스트림 생성
				FileDescriptor fd = fi.getFD();
				FileInputStream f2 = new FileInputStream(fd);
				
				// fi로 읽고 f2로 읽어도 같은 스트림에서 작동
				System.out.println((char)fi.read());  // "H"
				System.out.println((char)f2.read()); // "e"
				
			} catch (Exception e) {
				System.out.println("파일 작업에 실패했습니다.");
			} 
		}
	}
}

 


 

java.io의 기본 구성은 위와 같습니다. 클래스의 종류가 많긴 하지만 개념만 잘 알고 있으면 사용은 어렵지 않아 보입니다. 다음 글에서는 NIO에 대한 내용을 정리해보겠습니다. 

728x90

댓글

💲 추천 글