[Java 기초] 입출력 I/O
Updated:
이번 포스트에서는 자바에서의 입출력에 대해서 알아보도록 하겠다. 👨🏫
📋 목차
- Stream, Buffer, Channel 기반의 I/O
- Byte와 Character 스트림
- InputStream과 OutputStream
- 표준 스트림 $($System.in, System.out, System.err)
- 파일 읽고 쓰기
Stream, Buffer, Channel 기반의 I/O
입출력이란 내부, 외부의 장치와 프로그램간의 데이터를 주고받는 것을 말한다.
Stream
스트림 : 데이터를 운반하는데 사용되는 연결통로.
- 단반향통신만 가능하다. 입출력을 동시에 처리할 수 없다.
- 입출력 동시 수행을 위해선 입력,출력 스트림이 필요하다.
- 먼저보낸 데이터를 먼저받는 FIFO 구조
- 작업을 마친 스트림은
close()
로 닫아줘야 한다. $($표준 입출력, 메모리 사용 스트림의 경우 제외)
Buffer
버퍼를 사용한다는 것은 1 바이트씩 바로바로 보내는것이 아니라 버퍼공간에 데이터를 모아뒀다가 한번에 보내는 방법이다.
이로인해 입출력 횟수가 줄어들고 이는 곧 OS 레벨에 있는 시스템 콜
의 횟수가 줄어드는 것을 의미하기 때문에 성능이 향상된다.
Channel
NIO$($New Input/Output)
기존 I/O의 단점을 개선시켜서 나온 java.nio
패키지이다.
NIO
는 기존의 IO와 다음과 같은 차이점이 존재한다.
Channel 기반이다.
Channel은 Stream과 달리 양방향 입출력이 가능하다.
즉, 입력과 출력을 위한 각각의 채널을 만들 필요가 없다.
Buffer를 사용한다.
출력시 버퍼
에 여러개의 바이트를 한번에 모았다가 한번에 출력하는 것이 기존 IO에서 1바이트를 쓰면 1바이트를 읽는 방식보다 효율적이다.
IO 또한 보조 스트림인 BufferedInputStream / BufferedOutputStream
을 사용해서 버퍼를 제공하는 것이 가능하다.
NIO는 기본적으로 버퍼를 사용해서 입출력을 수행한다.
Blocking + non-blocking
NIO는 블로킹과 넌블로킹의 특징을 모두 가지고있다.
기존의 IO는 입력 스트림의 read()
혹은 출력 스트림의 write()
메서드를 호출하면 데이터가 입력/출력되기 전까지 쓰레드가 블로킹$($대기상태) 되었다.
스트림을 닫아서 블로킹을 빠져나와야했다.
반면 NIO의 블로킹은 쓰레드를 interrupt해서 블로킹 상태에서 빠져나올 수 있다.
NIO의 넌블로킹은 지금 바로 읽고 쓸수 있는 상태의 채널을 선택해서 작업 쓰레드가 처리하게 하기 때문에 블로킹이 되지 않는다.
이때 채널의 선택은 Selector
가 제공하는 방법을 통해 선택하게 된다.
Byte와 Character 스트림
Byte 기반 스트림 : 입출력 단위가 1 byte.
InputStream - 입력
OutputStream - 출력
Character 기반 스트림 : 이
Reader - 입력
Writer - 출력
Java에서 char
형은 2byte이기 때문에 바이트기반 스트림으로 문자를 처리하는데는 어려움이 있다.
문자데이터를 입출력할 때는 문자기반 스트림을 사용한다.
Byte 스트림 - InputStream과 OutputStream
InputStream의 메서드
메서드명 | 설명 |
---|---|
abstract int read() |
1 byte를 읽어 온다. 읽어올 데이터가 없으면 -1을 반환. 추상메서드 이므로 InputStream의 자손들이 상황에 맞게 구현해야 한다. |
int read(byte[ ] b) |
배열 b의 크기만큼 읽어 배열을 채운다. 읽어온 데이터의 수를 반환. |
int read (byte[ ] b, int off, int len) |
len개 의 byte를 읽어 배열 b의 지정된 off위치 에 저장한다. |
int available() |
읽어 올 수 있는 데이터 크기를 반환. |
void close() |
스트림을 닫고 자원을 반납. |
void mark(int readlimit) |
후에 reset() 에 의해 돌아갈 수 있도록 현재위치를 표시. |
void reset() |
mark() 가 호출되었던 위치로 되돌린다. |
long skip(long n) |
스트림에서 n만큼을 건너뛴다. |
OutputStream의 메서드
메서드명 | 설명 |
---|---|
abstract void write(int b) |
주어진 값을 출력소스에 쓴다. |
void write(byte[ ] b) |
배열 b에 저장된 내용을 출력소스에 쓴다. |
void write(byte[ ] b, int off, int len) |
배열 b에 저장된 내용 중 off번째 부터 len개 만큼 읽어서 출력소스에 쓴다. |
void close() |
입력소스를 닫고 자원 반환. |
void flush() |
스트림 버퍼의 모든 내용을 출력소스에 쓴다. |
예제
바구니배열$($temp)을 이용한 입출력 작업은 효율을 증가시킨다.
바이트배열$($메모리)에 데이터를 입출력하는데 쓰이는 ByteArrayInputStream
을 통해 스트림을 이용한 입출력을 보여준다.
import java.io.*;
import java.util.Arrays;
class Example {
public static void main(String[] args) {
byte[] inSrc = {0,1,2,3,4,5,6,7,8,9};
byte[] outSrc = null;
byte[] temp = new byte[10];
ByteArrayInputStream input = null;
ByteArrayOutputStream output = null;
input = new ByteArrayInputStream(inSrc);
output = new ByteArrayOutputStream();
// input으로 부터 최대 temp.length 바이트를 읽어서 temp배열의 0번째 위치부터 저장
input.read(temp, 0, temp.length);
output.write(temp, 5, 5); // temp[5]부터 5개의 데이터를 쓴다.
outSrc = output.toByteArray();
System.out.println("Input Source :" + Arrays.toString(inSrc));
System.out.println("temp :" + Arrays.toString(temp));
System.out.println("Output Source :" + Arrays.toString(outSrc));
}
}
출력
Input Source :[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
temp :[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Output Source :[5, 6, 7, 8, 9]
FileInput과 FileOutputStream
파일에 입출력을 하기위한 스트림 생성자이다.
FileInput 생성자
생성자 | 설명 |
---|---|
FileInputStream(String name) |
파일이름$($name)을 가진 실제 파일과 연결된 FileInputStream을 생성. |
FileInputStream(File file) |
위의 String name과 같지만 파일 이름을 File 인스턴스로 지정해줘야 한다. |
FileInputStream(FileDescriptor fdObj) |
파일 디스크립터로 FileInputStream 생성. |
FileOutput 생성자
생성자 | 설명 |
---|---|
FileOutputStream(String name) |
파일이름$($name)을 가진 실제 파일과 연결된 FileOutputStream을 생성. |
FileOutputStream(String name, boolean append) |
파일이름$($name)을 가진 실제 파일과 연결된 FileOutputStream을 생성하고 append 가 true 면 기존 파일 내용의 마지막에 덧붙이고 false 면 기존 파일내용을 덮어쓴다. |
FileOutputStream(File file) |
String name과 같지만 파일 이름을 File인스턴스로 지정해줘야 한다. |
FileOutputStream(File file, boolean append) |
$($String name, boolean append)와 같지만 파일 이름을 File인스턴스로 지정해줘야 한다. |
FileOutputStream(FileDescriptor fdObj) |
파일 디스크립터로 FileOutputStream을 생성. |
import java.io.*;
class Example {
public static void main(String[] args) {
try {
FileOutputStream fos = new FileOutputStream("example.txt");
BufferedOutputStream bos = new BufferedOutputStream(fos, 5); // BufferedOutputStream의 버퍼 크기를 5로한다.
// example.txt 파일에 123456789 입력.
for(int i = '1'; i <= '9'; i++) bos.write(i);
bos.close(); // 버퍼에 남아있던 6789 입력됨
// fos.close()인 경우 12345 출력
} catch (IOException e) {
e.printStackTrace();
}
}
}
바이트기반 보조스트림
보조스트림 자체로는 입출력을 처리하지 못하며 스트림을 생성한 다음 이를 사용해서 보조스트림을 생성해야한다.
FilterInputStream과 FilterOutputStream
protected FilterInputStream$($InputStream in)
public FilterOutputStream$($OutputStream out)
-
모든 보조스트림의 조상이다.
-
자체로는 아무런 일도 하지 못한다.
-
상속을 통해 메서드를 오버라이딩하여 원하는 작업을 수행.
BufferedInputStream과 BufferedOutputStream
버퍼를 사용하여 스트림의 입출력 효율을 높이는 보조스트림이다.
버퍼를 사용하면 한번에 여러 바이트를 입출력하기 때문에 효율적이다.
BufferedInputStream의 생성자
생성자 | 설명 |
---|---|
BufferedInputStream(InputStream in, int size) |
InputStream 인스턴스를 입력소스로 하여 byte단위의 버퍼를 갖는 BufferedInputStream인스턴스를 생성. |
BufferedInputStream(InputStream in) |
InputStream인스턴스를 입력소스로 하고 8192byte 크기의 버퍼를 갖는다. |
read 메서드로 데이터를 읽게되면 BufferedInputStream
은 입력소스로 부터 버퍼 크기만큼 데이터를 읽어서 내부 버퍼에 저장한다.
이후 작업시, 내부의 버퍼로 부터 데이터를 읽어오고 버퍼에 저장된 모든 데이터를 다 읽고나서 read 메서드가 호출되면 다시 BufferedInputStream이 데이터를 읽어서 버퍼에 저장하는 과정이 반복적으로 이루어진다.
BufferedOutputStream의 생성자
생성자 | 설명 |
---|---|
BufferedOutputStream(OutputStrream out, int size) |
OutputStream인스턴스를 출력소스로하며 byte단위의 버퍼를 갖는 BufferedOutputStream 인스턴스를 생성. |
BufferedOutputStream(OutputStream out) |
OutputStream인스턴스를 출력소스로하며 8192 byte 크기의 버퍼를 갖는다. |
flush() |
버퍼의 모든 내용을 출력 후, 버퍼를 비운다. |
close() |
flush() 를 호출하고 BufferedOutputStream인스턴스의 자원을 모두 반환. |
write메서드를 이용한 출력이 BufferedOutputStream에 저장되고 버퍼가 가득 차면 모든 내용을 출력소스에 출력한다. 그 다음 버퍼를 비우며 과정을 반복한다.
버퍼가 가득 찰때 출력소스에 출력하기 때문에 마지막 출력부분이 버퍼에 남을수 있다.
때문에 출력작업을 마치고 나면 close()
나 flush()
를 호출해서 남은 부분을 출력되도록 해야한다. $($주로 보조스트림의 경우 close$($)만 호출.)
import java.io.*;
class Example {
public static void main(String[] args) {
try {
FileOutputStream fos = new FileOutputStream("example.txt");
BufferedOutputStream bos = new BufferedOutputStream(fos, 5); // BufferedOutputStream의 버퍼 크기를 5로한다.
for(int i = '1'; i <= '9'; i++) bos.write(i);
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
Character 스트림 - Reader와 Writer
문자기반 스트림은
-
2 byte로 스트림을 처리한다.
-
여러 종류의 인코딩과 유니코드간의 변환을 자동으로 처리해준다.
- Reader가 특정 인코딩을 읽고 유니코드로 변환.
- Writer가 유니코드를 특정 인코딩으로 변환하여 저장.
FileReader와 FileWriter
- 파일로부터 텍스트데이터를 읽고 쓰는데 사용.
- FileInputStream, FileOutputStream과 사용법이 같다.
import java.io.*;
class Example {
public static void main(String[] args) {
try {
String fileName = "test.txt";
FileInputStream fis = new FileInputStream(fileName);
FileReader fr = new FileReader(fileName);
int data = 0;
// FileInputStream을 통한 파일내용 읽어 화면 출력
while((data = fis.read()) != -1) {
System.out.print((char)data); // 문자가 깨져서 출력됨.
}
System.out.println();
fis.close();
// FileReader를 통한 파일내용 읽어 화면 출력
while((data = fr.read()) != -1) {
System.out.print((char)data); // 문자가 정상적으로 출력됨.
}
System.out.println();
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
문자기반의 보조스트림
BufferedReader
readLine()
메서드를 통해 파일을 라인단위로 읽음.
InputStreamReader와 OutputStreamWriter
-
바이트기반 스트림을 문자기반 스트림으로 연결시켜준다.
-
바이트기반 스트림의 데이터를 인코딩 문자 데이터로 변환.
InputStreamReader 생성자와 메서드
생성자 / 메서드 | 설명 |
---|---|
InputStreamReader(InputStream in) |
OS기반 기본 인코딩 문자로 변환하는 InputStreamReader를 생성. |
InputStreamReader(InputStream in, String encoding) |
지정된 인코딩을 사용하는 InputStreamReader를 생성. |
String getEncoding() |
InputStreamReader의 인코딩을 알려준다. |
OutputStreamWriter 생성자와 메서드
생성자 / 메서드 | 설명 |
---|---|
OutputStreamWriter(OutputStream in) |
OS기반 기본 인코딩 문자로 변환하는 OutputStreamWriter를 생성. |
OutputStreamWriter(OutputStream in, String encoding) |
지정된 인코딩을 사용하는 OutputStreamWriter를 생성. |
String getEncoding() |
OutStreamWriter의 인코딩을 알려준다. |
예제
import java.io.*;
class Example {
public static void main(String[] args) {
String line = "";
try {
InputStreamReader isr = new InputStreamReader(System.in);
BufferedReader br = new BufferedReader(isr);
System.out.println("현재 OS의 인코딩 :" + isr.getEncoding());
do {
System.out.print("문장을 입력하세요. q to finish");
line = br.readLine();
System.out.println("입력한 문장 :" + line);
} while(!line.equalsIgnoreCase("q"));
br.close();
System.out.println("프로그램 종료");
} catch (IOException e) { }
}
}
표준 스트림
-
Console을 통해 데이터를 입력하고 Console로 데이터를 출력하는 것
-
Java 어플리케이션 실행시 자동적으로 생성된다. $($별도의 스트림 생성 필요 없음)
System.in 콘솔로부터 데이터를 입력
System.out 콘솔로 데이터를 출력
System.err 콘솔로 데이터를 출력
public final class System {
public final static InputStream in = nullInputStream();
public final static PrintStream out = nullPrintStream();
public final static PrintStream err = nullPrintStream();
}
in, out, err의 타입이 InputStream과 PrintStream이지만 실제로는 BufferedInputStream과 BufferedOutputStream의 인스턴스를 사용한다.
표준입출력 대상 변경
메서드 | 설명 |
---|---|
static void setOut(PrintStream out) |
System.out의 출력을 지정된 PrintStream으로 변경 |
static void setErr(PrintStream err) |
System.err의 출력을 지정된 PrintStream으로 변경 |
static void setIn(InputStream in) |
System.in의 입력을 지정한 InputStream으로 변경 |
import java.io.*;
class Example {
public static void main(String[] args) {
PrintStream ps = null;
FileOutputStream fos = null;
try {
fos = new FileOutputStream("test.txt");
ps = new PrintStream(fos);
System.setOut(ps);
} catch (FileNotFoundException e) {
System.err.println("File Not Found");
}
System.out.println("Hello by System.out");
System.err.println("Hello by System.err");
}
}
파일 읽고 쓰기
File 클래스
를 통해 파일과 디렉토리를 다룰 수 있다.
File
인스턴스 생성만으로 파일/디렉토리가 생성되는건 아니다.
File인스턴스 생성후 출력스트림
생성 혹은 createNefFile()
을 호출해야 파일/디렉토리가 생성된다.
File의 생성자와 경로 관련 메서드
생성자 / 메서드 | 설명 |
---|---|
File(String fileName) |
문자열$($fileName)을 이름으로 갖는 파일/디렉토리를 위한 File인스턴스를 생성. 프로그램이 실행되는 위치가 path가 된다. |
File(String pathName, String fileName) File(File pathName, String fileName) |
파일의 경로와 이름을 지정하여 File인스턴스 생성. |
File(URI uri) |
지정된 uri로 파일을 생성 |
String getName() |
파일 이름을 반환 |
String getPath() |
파일 경로를 반환 |
String getAbsolutePath() File getAbsouluteFile() |
파일의 절대경로를 반환 |
String getParent() String getParentFile() |
파일의 조상 디렉토리를 반환 |
String getCanonicalPath() File getCanonicalFile() |
파일의 정규경로를 반환 |
경로와 관련된 File의 멤버변수
멤버변수 | 설명 |
---|---|
static String pathSeparator |
OS에서 사용하는 경로 구분자. 윈도우 - “,” , 유닉스 - “.” |
static char pathSeparatorChar |
OS에서 사용하는 경로 구분자. 윈도우 - “,” , 유닉스 - “.” |
static String separator |
OS에서 사용하는 이름 구분자. 윈도우 - “₩”, 유닉스 - “/” |
`static char separatorChar | OS에서 사용하는 이름 구분자. 윈도우 - “₩”, 유닉스 - “/” |
구분자가 OS마다 다르기 떄문에 OS독립적으로 프로그램을 작성하기 위해서 위의 멤버변수를 사용한다.
File f = new File("/Users/Test/jdk1.8/work/test.java"); // 해당 파일이름을 갖는 파일 생성.
File f = new File("/Users/Test/jdk1.8/work", "test.java"); // 경로와 파일 이름 지정하여 파일 생성.
File dir = new File("/Users/Test/jdk1.8/work"); // 디렉토리 생성
File f = new File(dir, "test.java");
이미 존재하는 파일을 참조하는 경우
File f = new File("/Users/Test/jdk1.8/work", "test.java");
기존에 없는 파일을 새로 생성하는 경우
File f = new File("/Users/Test/jdk1.8/work", "test.java");
f.createNewFile(); // 새로운 파일을 생성
참조
Java의 정석 $($남궁 성)
whiteship 자바 라이브 스터디
Leave a comment