- 스트림 (Stream) / 버퍼 (Buffer) / 채널 (Channel) 기반의 I/O
- InputStream과 OutputStream
- Byte와 Character 스트림
- 표준 스트림 (System.in, System.out, System.err)
- 파일 읽고 쓰기
- NIO(New Input/Output)
I/O
I/O란 Input 과 Output의 약자로 입력과 출력, 간단히 줄여서 입출력이라고 한다.
입출력은 컴퓨터 내부 또는 외부의 장치와 프로그램간의 데이터를 주고받는 것을 말한다.
1. 스트림 (Stream) / 버퍼 (Buffer) / 채널 (Channel) 기반의 I/O
스트림
프로그램에서는 데이터를 외부에서 읽고 다시 외부로 출력하는 작업이 빈번히 일어난다.
자바에서 데이터는 스트림(Stream)을 통해 입출력되므로 스트림의 특징을 잘 이해해야 한다.
스트림은 단일 방향으로 연속적으로 흘러가는 것을 말하는데, 물이 높은 곳에서 낮은 곳으로 흐르듯이 데이터는 출발지에서 나와 도착지로 들어간다는 개념이다.
- 스트림은 데이터를 읽고 기록하는 중간역할을 한다.
- 스트림은 빨대다.
- 빨대는 음료수를 마시는 중간역할을 한다.
- 빨대는 입에 있는 음료수를 다시 내뱉는 중간역할을 한다.
- 스트림은 단 방향 빨대이다. 음료수를 내뱉고 다시 마시려면 빨대가 2개 필요하다.
입력 스트림과 출력 스트림
프로그램이 출발지냐 또는 도착지냐에 따라서 스트림의 종류가 결정되는데, 프로그램이 데이터를 입력받을 때에는 입력 스트림(InputStream) 이라 부르고, 프로그램이 데이터를 보낼 때에는 출력 스트림(OutputStream) 이라고 부른다.
스트림의 종류
스트림은 크게 문자 단위로 처리하느냐, 바이트 단위로 처리하느냐에 따라서 나눌 수 있습니다.
먼저 문자 스트림의 구성도는 아래와 같이 나타납니다.
입력 문자 스트림은 Reader라는 단어가 붙어있습니다.
출력 문자 스트림은 Writer라는 단어가 붙어있습니다.
스트림은 크게 InputStream , OutputStream이 있습니다.
InputStream 은 입력장치로부터 입력을 받고 프로그램을 도착지로 향하는 Stream입니다.
OutputStream은 프로그램을 시작점으로 출력장치 및 프로그램을 목적지로 하는 Stream입니다.
버퍼(Buffer)
버퍼란 임시 저장 공간을 의미 한다. 임시 저장 공간이라고 해서 쌩뚱맞게 보일 수 있지만 정확히 말하면 A와 B가 서로 입출력을 수행하는데에 있어서 속도차이를 극복하기 위해 사용하는 임시 저장 공간을 의미 한다.
프로그래밍이나 운영체제에서 사용하는 버퍼는 거의 대부분 CPU와 보조기억장치 사이에서 사용되는 임시 저장 공간을 의미한다.
예를 들어 CPU는 1초에 100개의 데이터를 처리할 수 있지만 보조기억장치는 CPU가 처리 할 수 있는 일을 1초에 3개씩 보낸다. 이렇게 되면 CPU의 효율성이 떨어진다.
이 때 버퍼를 사용한다. 버퍼는 CPU 내부에 있는 캐시메모리 보다는 느리지만 보조 기억 장치보다 훨씬 빠른 주기억 장치(RAM)를 이용한다.
보조기억장치는 주 기억장치의 버퍼로 마련해둔 공간에 데이터를 열심히 보내 쌓아 둔다.
CPU는 처리가 빠르므로 밀려 있는 다른 일을 처리한 후 시간이 남을때 가끔 버퍼를 확인하여 데이터가 모두 쌓였는지 확인하고 모두 쌓였다면 가져다 한꺼번에 처리한다.
이 처럼 버퍼 라는 것은 속도차이가 큰 두 대상이 입출력을 수행할 때 효율성을 위해 사용하는 임시 저장공간이라고 할 수 있다.
채널
기존의 Java I/O는 Blocking 하고, Java NIO는 Non-blocking 하다는 사실을 알고 있다.
그렇기에 Java IO에서 스레드에서 Reader와 Writer만 존재해, Reader로 블로킹을 하고, Writer로 블로킹을 풀어주면 된다.
하지만, Java NIO는 Non-blocking 방식이다. 따라서, 데이터를 주고 받기 위해서 스레드와 데이터가 들어간 버퍼사이의 일종의 터널을 만들어주어야 한다. 이것이 채널이 하는 역할이다.
그리고 이 채널은 파일, 소켓, 데이터그램 등과 같은 다양한 I/O 소스로부터 데이터 블록을 버퍼로 쓰거나 읽어올 수 있다.
채널의 특징
- Socket과 연결 되어, 입출력 역할을 수행한다
- 입력과 출력을 동시에 수행한다.
- Selector와 연결되어 있고, 하나에 Selector에는 다수의 채널이 존재할 수 있다.
- Blocking 된 스레드를 깨우거나, 다시 Blocking할 수 있다.
2. InputStream과 OutputStream
스트림은 바이트단위로 데이터를 전송하며 입출력 대상에 따라 다음과 같은 입출력스트림이 있다.
입력스트림출력스트림입출력 대상의 종류
FileInputStream | FileOutputStream | 파일 |
ByteArrayInputStream | ByteArrayOutputStream | 메모리(byte 배열) |
PipedInputStream | PipedOutputStream | 프로세스(프로세스간의 통신) |
AudioInputStream | AudioOutputStream | 오디오 장치 |
자바에서는 java.io 패키지를 통해서 많은 종류의 입출력관련 클래스들을 제공하고 있으며, 입출력을 처리할 수 있는 표준화된 방법을 제공함으로써 입출력의 대상이 달라져도 동일한 방법으로 입출력이 가능하기 때문에 프로그래밍을 하기에 편리하다.
InputStreamOutputStream
abstract int read() | abstract void write(int b) |
int read(byte[] b) | void write(byte[] b) |
int read(byte[] b, int off, int len) | void write(byte[] b, int off, int len) |
InputStream과 OutputStream에 정의된 읽기와 쓰기를 수행하는 메서드 이다.
InputStream의 read()와 OutputStream의 write(int b)는 입출력의 대상에 따라 읽고 쓰는 방법이 다를 것이기 때문에 각 상황에 알맞게 구현하라는 의미에서 추상 메서드로 정의되어 있다.
InputStream
InputStream은 바이트 기반 입력 스트림의 최상위 클래스로 추상 클래스이다. 모든 바이트 기반 입력 스트림은 이 클래스를 상속받아서 만들어 진다.
다음과 같이 FileInputStream, BufferedInputStream, DataInputStream 클래스는 모두 InputStream 클래스를 상속하고 있다.
InputStream 클래스에는 바이트 기반 입력 스트림이 기본적으로 가져야 할 메소드가 정의되어 있다.
리턴타입메소드설명
int | read() | 입력 스트림으로부터 1바이트를 읽고 읽은 바이트를 리턴한다. |
int | read(byte[] b) | 입력 스트림으로부터 읽은 바이트들을 매개값으로 주어진 바이트 배열 b에 저장하고 실제로 읽은 바이트 수를 리턴한다. |
int | read(byte[] b, int off, int len) | 입력 스트림으로부터 len개의 바이트만큼 읽고 매개값으로 주어진 바이트 배열 b[off] 부터 len개 까지 저장한다. 그리고 실제로 읽은 바이트수인 len개를 리턴한다. 만약 len개를 모두읽지 못하면 실제로 읽은 바이트 수를 리턴한다. |
void | close() | 사용한 시스템 자원을 반납하고 입력 스트림을 닫는다. |
int | available() | 스트림으로부터 읽어 올 수 있는 데이터의 크기를 반환한다. |
void | mark(int readlimit) | 현재위치를 표시해 놓는다. 후에 reset()에 의해서 표시해 놓은 위치로 다시 돌아갈 수 있다. readlimit은 되돌아갈 수 있는 byte의 수이다. |
boolean | markSupported() | mark()와 reset()을 지원하는지를 알려준다. mark()와 reset() 기능을 지원하는 것은 선택적이므로, mark()와 reset()을 사용하기 전에 markSupported()를 호출해서 지원여부를 확인해야 한다. |
void | reset() | 스트림에서의 위치를 마지막으로 mark()이 호출되었던 위치로 되돌린다. |
long | skip(long n) | 스트림에서 주어진 길이(n) 만큼을 건너뛴다. |
OutputStream
InputStream과 반대되는 개념으로 OutputStream 추상 클래스는 데이터가 나가는 통로의 역할에 관해 규정하고 있는 추상 클래스이다.
메서드명설명
void close() | 입력소스를 닫음으로써 사용하고 있던 자원을 반환한다. |
void flush() | 스트림의 버퍼에 있는 모든 내용을 출력소스에 쓴다. |
abstract void write(int b) | 주어진 값을 출력소스에 쓴다. |
void write(byte[] b) | 주어진 배열 b에 저장된 모든 내용을 출력소스에 쓴다. |
void write(byte[] b, int off, int len) | 주어진 배열 b에 저장된 내용중에서 off번째 부터 len개 만큼만을 읽어서 출력소스에 쓴다. |
3. Byte와 Character 스트림
바이트기반 스트림에는 inputstream, outputstream과 더불어 ByteArrayInputStream, ByteArrayOutputStream, FileInputStream, FileOutputStream 이 있다.
ByteArrayInputStream/ByteArrayOutputStream은 메모리, 즉 바이트배열에 데이터를 입출력 하는데 사용되는 스트림이다. 주로 다른 곳에 입출력하기 전에 데이터를 임시로 바이트 배열에 담아서 변환 등의 작업을 하는데 사용된다.
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.util.Arrays;
public class IOEx1 {
public static void main(String[] args) {
byte[] inSrc = {0,1,2,3,4,5,6,7,8,9};
byte[] outSrc = null;
ByteArrayInputStream input = null;
ByteArrayOutputStream output = null;
input = new ByteArrayInputStream(inSrc);
output = new ByteArrayOutputStream();
int data = 0;
while((data = input.read())!= -1){
output.write(data); //void write(int b)
}
outSrc = output.toByteArray(); // 스트림의 내용을 byte배열로 반환한다.
System.out.println("Input Source : "+ Arrays.toString(inSrc));
System.out.println("Output Source : "+ Arrays.toString(outSrc));
}
}
// 결과
Input Source : [0,1,2,3,4,5,6,7,8,9]
Output Source : [0,1,2,3,4,5,6,7,8,9]
ByteArrayInputStream/ByteArrayOutputStream을 이용해서 바이트 배열 inSrc의 데이터를 outSrc로 복사하는 예제인데, read()와 write()를 사용하는 가장 기본적인 방법을 보여준다.
while문의 조건식이 조금 복잡한데, 이 조건식은 아래와 같은 순서로 처리된다.
(data = input.read()) ! = -1
1. data = input.read(); // read()를 호출한 반환값을 변수 data에 저장한다. (괄호먼저)
2. data != -1 // data에 저장된 값이 -1인지 아닌지 비교한다.
바이트 배열은 사용하는 자원이 메모리 밖에 없으므로 가비지컬렉터에 의해 자동적으로 자원을 반환하므로 close()를 이용해서 스트림을 닫지 않아도 된다.
FileInputStream/FileOutputStream 은 파일에 입출력을 하기 위한 스트림이다.
생성자설명
FileInputStream(String name) | 지정된 파일이름(name)을 가진 실제 파일과 연결된 FileInputStream을 생성한다. |
FileInputStream(File file) | 파일의 이름이 String이 아닌 File인스턴스로 지정해주어야 하는 점을 제외하고 FileInputStream(String name)와 같다. |
FileInputStream(FileDescriptor fdObj) | 파일 디스크립터(fdObj)로 FileInputStream을 생성한다. |
FileOutputStream(String name) | 지정된 파일이름(name)을 가진 실제 파일과의 연결된 FileOutputStream을 생성한다. |
FileOutputStream(String name, boolean append) | 지정된 파일이름(name)을 가진 실제 파일과 연결된 FileOutputStream을 생성한다. 두 번째 인자인 append를 true로 하면, 출력 시 기존의 파일내용의 마지막에 덧붙인다. false면, 기존의 파일내용을 덮어쓰게 된다. |
FileOutputStream(File file) | 파일의 이름을 String이 아닌 File인스턴스로 지정해주어야 하는 점을 제외하고 FileOutputStream(String name)과 같다. |
FileOutputStream(File file, boolean append) | 파일의 이름을 String이 아닌 File인스턴스로 지정해주어야 하는 점을 제외하고 FileOutputStream(String name, boolean append)과 같다. |
FileOutputStream(FileDescriptor fdObj) | 파일 디스크립터(fdObj)로 FileOutputStream을 생성한다. |
import java.io.FileInputStream;
import java.io.IOException;
public class FileViewer {
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream(args[0]);
int data = 0;
while((data=fis.read()) != -1){
char c = (char)data;
System.out.println(c);
}
}
}
//실행결과
import java.io.FileInputStream;
import java.io.IOException;
public class FileViewer {
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream(args[0]);
int data = 0;
while((data=fis.read()) != -1){
char c = (char)data;
System.out.println(c);
}
}
}
커맨드라인으로부터 입력받은 파일의 내용을 읽어서 그대로 화면에 출력해주는 간단한 예제이다.
read()의 반환값이 int형(4byte)이긴 하지만, 더 이상 입력값이 없음을 알리는 -1을 제외하고는 0~255(1 byte) 범위의 정수값이기 때문에, char형(2 byte)으로 변환한다 해도 손실되는 값은 없다.
read()가 한번에 1 byte씩 파일로부터 데이터를 읽어 들이긴 하지만, 데이터의 범위가 십진수로 0255(16진수로는 0x000xff) 범위의 정수값이고, 또 읽을 수 있는 입력값이 더 이상 없음을 알릴 수 있는 값(-1)도 필요하다. 그래서 다소 크긴 하지만 정수형 중에서는 연산이 가장 효율적이고 빠른 int 형 값을 반환하도록 한 것이다.
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class FileCopy {
public static void main(String[] args) {
try(FileInputStream fis = new FileInputStream(args[0]);
FileOutputStream fos = new FileOutputStream(args[0]))
{
int data = 0;
while((data=fis.read()) != -1){
fos.write(data);
}
}catch (IOException e){
e.printStackTrace();
}
}
}
//실행결과
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class FileCopy {
public static void main(String[] args) {
try(FileInputStream fis = new FileInputStream(args[0]);
FileOutputStream fos = new FileOutputStream(args[0]))
{
int data = 0;
while((data=fis.read()) != -1){
fos.write(data);
}
}catch (IOException e){
e.printStackTrace();
}
}
}
FileInputStream과 FileOutputStream을 사용해서 FileCopy.java파일의 내용을 그대로 FileCopy.bak로 복사하는 일을 한다.
단순히 FileCopy.java의 내용을 read()로 읽어서 , write(int b) 로 FileCopy.bak에 출력한다.
이처럼 텍스트파일을 다루는 경우에는 FileInputStream/FileOutputStream보다 문자 기반의 스트림인 FileReader/FileWriter를 사용하는 것이 더 좋다.
Character 스트림
문자 데이터를 다루는데 사용된 다는 것을 제외하고는 바이트기반 스트림과 문자기반 스트림의 사용방법은 거의 같기 때문에 앞서 설명한 바이트기반 스트림에 대한 내용만으로도 별도의 설명은 필요없을 것이라 생각한다.
바이기반 스트림의 조상이 InputStream/OutputStream 인 것과 같이 문자 기반의 스트림에서는 Reader/Writer가 그와 같은 역할을 한다.
Reader의 메서드
abstract void close() | 입력스트림을 닫음으로써 사용하고 있던 자원을 반환한다. |
void mark(int readlimit) | 현재위치를 표시해놓는다. 후에 reset()에 의해서 표시해 놓은 위치로 다시 돌아갈 수 있다. |
boolean markSupported() | mark()와 reset()을 지원하는지를 알려준다. |
int read() | 입력소스로부터 하나의 문자를 읽어온다. char의 범위인 0~65535범위의 정수를 반환하며, 입력스트림의 마지막 데이터에 도달하면, -1을 반환한다. |
int read(char[] c) | 입력소스로부터 매개변수로 주어진 배열 c의 크기만큼 읽어서 배열 c에 저장한다. 읽어 온 데이터의 개수 또는 -1을 반환한다. |
abstract int read(char[] c, int off, int len) | 입력소스로부터 최대 len개의 문자를 읽어서, 배열 c의 지정된 위치(off)부터 읽은 만큼 저장한다. 읽어 온 데이터의 개수 또는 -1을 반환한다. |
int read(CharBuffer target) | 입력소스로부터 읽어서 문자버퍼(target)에 저장한다. |
boolean ready() | 입력소스로부터 데이터를 읽을 준비가 되어있는지 알려준다. |
void reset() | 입력소스에서의 위치를 마지막으로 mark()가 호출되었던 위치로 되돌린다. |
long skip(long n) | 현재 위치에서 주어진 문자 수 (n) 만큼을 건너뛴다. |
Writer의 메서드
Writer append(char c) | 지정된 문자를 출력소스에 출력한다. |
Writer append(CharSequence c) | 지정된 문자열(CharSequence)을 출력소스에 출력한다. |
Writer append(CharSequence c, int start, int end) | 지정된 문자열(CharSequence)의 일부를 출력소스에 출력(CharBuffer, String, StringBuffer 가 CharSequence를 구현) |
abstract void close() | 출력스트림을 닫음으로써 사용하고 있던 자원을 반환한다. |
abstract void flush() | 스트림의 버퍼에 있는 모든 내용을 출력소스에 쓴다.(버퍼가 있는 스트림에만 해당됨) |
void write(int b) | 주어진 값을 출력소스에 쓴다. |
void write(char[] c) | 주어진 배열 c에 저장된 모든 내용을 출력소스에 쓴다. |
abstract void write(char[] c, int off, int len) | 주어진 배열c에 저장된 내용 중에서 off번째부터 len길이만큼만 출력소스에 쓴다. |
void write(String str) | 주어진 문자열(str)을 출력소스에 쓴다. |
void write(String str, int off, int len) | 주어진 문자열(str)의 일부를 출력소스에 쓴다.(off번째 문자부터 len개 만큼의 문자열) |
FileReader와 FileWriter
FileReader/FileWriter는 파일로부터 텍스트파일을 읽고, 파일에 쓰는데 사용된다.
사용방법은 FileInputStream/FileOutputStream과 다르지 않다.
PipedReader와 PipedWriter
PipedReader/PipedWriter는 쓰레드 간에 데이터를 주고받을때 사용된다. 다른 스트림과는 달리 입력과 출력스트림을 하나의 스트림으로 연결(connect)해서 데이터를 주고받는다는 특징이 있다.
스트림을 생성한 다음에 어느 한쪽 쓰레드에서 coonect()를 호출해서 입력스트림과 출력스트림을 연결한다. 입출력을 마친 후에는 어느 한쪽 스트림만 닫아도 나머지 스트림은 자동으로 닫힌다.
StringReader와 StringWriter
StringReader/StringWriter는 CharArrayReader/CharArrayWriter와 같이 입출력대상이 메모리인 스트림이다.
StringWriter에 출력되는 데이터는 내부의 StringBuffer에 저장되며 StringWriter의 다음과 같은 메서드를 이용해서 저장된 데이터를 얻을 수 있다.
StringBuffer getBuffer() //StringWriter에 출력한 데이터가 저장된 StringBuffer를 반환한다.
String toString() //StringWriter에 출력된 (StringBuffer에 저장된) 문자열을 반환한다.
4. 표준 스트림 (System.in, System.out, System.err)
표준입출력은 콘솔(console, 도스창)을 통한 데이터 입력과 콘솔로의 데이터 출력을 의미한다. 자바에서는 표준 입출력(standard I/O)을 위해 3가지 입출력 스트림, System.in, System.out, System.err을 제공하는데, 이 들은 자바 어플리케이션의 실행과 동시에 사용할 수 있게 자동적으로 생성되기 때문에 개발자가 별도로 스트림을 생성하는 코드를 작성하지 않고도 사용이 가능하다.
지금까지 줄 곧 사용해온 System.out을 스트림의 생성없이 사용할 수 있었던 것이 바로 이러한 이유 때문이다.
System.in //콘솔로부터 데이터를 입력받는데 사용
System.out //콘솔로 데이터를 출력하는데 사용
System.err //콘솔로 데이터를 출력하는데 사용
System클래스의 소스에서 알 수 있듯이 in , out , err 은 System클래스에 선언된 클래스변수(static변수) 이다. 선언부분만 봐서는 out, err, in 의 타입은 InputStream과 PrintStream 이지만 실제로는 버퍼를 이용하는 BufferdInputStream과 BufferdOutputStream의 인스턴스를 사용한다.
public final class System{
public final static InputStream in = nullInputStream();
public final static PrintStream out = nullPrintStream();
public final static PrintStream err = nullPrintStream();
...
}
import java.io.*;
class StandardIOEx1 {
public static void main(String[] args){
try{
int input = 0;
while((input = System.in.read()) != -1){
System.out.println("input : " + input + ", (char)input :" + (char)input);
}
}catch(IOException e){
e.printStackTrace();
}
} // main
}
//결과
C:\jdk1.8\work\ch15>java StandardIOEx1
hello
input : 104, (char)input :h
input : 101, (char)input :e
input : 108, (char)input :l
input : 108, (char)input :l
input : 111, (char)input :o
input : 13, (char)input : //특수문자라서 화면에 보이지 않는다.
input : 10, (char)input :
// 개행문자가 출력되어 줄바꿈 되었다.
^Z // Ctrl + Z를 눌러서 입력의 끝(EOF)를 알린다.
C:\jdk1.8\work\ch15>
화면에 커서가 입력을 기다리고 있을 것이다. hello라고 입력하고 '^Z'를 누르거나 Enter를 누르면, 입력한 문자들이 출력되고 프로그램이 종료된다.
예제를 실행하여 System.in.read()가 호출되면, 코드의 진행을 멈추고 콘솔에 커서가 깜빡이며 사용자의 입력을 기다린다.
while((input = System.in.read()) != -1) { }
콘솔입력을 버퍼를 가지고 있기 때문에 Backspace키를 이용해서 편집이 가능하며 한 번에 버퍼의 크기만큼 입력이 가능하다. 그래서 Enter키나 입력의 끝을 알리는 '^z' 를 누르기 전까지는 아직 데이터가 입력 중인 것으로 간주되어 커서가 입력을 계속 기다리는 상태(블락킹 상태)에 머무르게 된다.
콘솔에 데이터를 입력하고 Enter키를 누르면 입력대기상태에서 벗어나 입력된 데이터를 읽기 시작하고 입력된 데이터를 모두 읽으면 다시 입력대기 상태가 된다.
이러한 과정이 반복되다가 사용자가 '^z'를 입력하면 , read()는 입력이 종료되었음을 인식하고 -1을 반환하여 while문을 벗어나 프로그램이 종료된다.
setOut(), setErr(), setIn()
초기에는 System.in, System.out, System.err의 입출력대상이 콘솔화면이지만, setIn(), setOut(), setErr()를 사용하면 입출력을 콘솔 이외에 다른 입출력 대상으로 변경하는 것이 가능하다.
static void setOut(PrintStream out) | System.out의 출력을 지정된 PrintStream으로 변경 |
static void setErr(PrintStream err) | System.err의 출력을 지정한 PrintStream으로 변경 |
static void setIn(InputStream in) | System.in의 입력을 지정한 InputStream으로 변경 |
그러나 JDK1.5 부터 Scanner 클래스가 제공되면서 System.in으로부터 데이터를 입력받아 작업하는 것이 편리해졌다.
5. 파일 읽고 쓰기
자바 내장 클래스인 FileWriter, BufferedWriter(파일읽기) , FileReader,BufferedReader(파일쓰기)를 사용하도록 하겠습니다.
FileWriter
FileWriter 객체와 BufferedWrite 객체를 생성해줍니다.
여기서 FileWriter객체를 생성할때 생정자에 true를 주면 파일 이어서 쓰기가 가능합니다.
default는 false입니다. (false로 하면 파일을 새로 쓰게 됩니다.)
public class FileMake {
public static void main(String[] args) {
try(
FileWriter fw = new FileWriter( "soccer.txt" ,true);
BufferedWriter bw = new BufferedWriter( fw );
)
{
bw.write("손흥민"); //버퍼에 데이터 입력
bw.newLine(); //버퍼에 개행 삽입
bw.write("이강인");
bw.newLine();
bw.flush(); //버퍼의 내용을 파일에 쓰기
}catch (IOException e ) {
System.out.println(e);
}
File f = new File("soccer.txt");
// 파일 존재 여부 판단
if (f.isFile()) {
System.out.println("soccer.txt 파일이 있습니다.");
}
}
}
실행을 하면 다음과 같이 실행이 되는 것을 확인할 수 있다.
File Read
파일을 읽을때는 아래 객체를 사용한다.
FileReader, BufferedReader
FileReader의 생성자에는 파일경로를 작성한다. 지금은 자바프로젝트 경로에 있으므로 파일명만 작성해도 읽어낼 수 있으나, 다른 경로에 있을 경우 상대경로 또는 절대경로를 입력하면 된다.
//위에 코드와 밑의 코드를 합쳐보자
try(
FileReader rw = new FileReader("soccer.txt");
BufferedReader br = new BufferedReader( rw );
){
//읽을 라인이 없을 경우 br은 null을 리턴한다.
String readLine = null ;
while( ( readLine = br.readLine()) != null ){
System.out.println(readLine);
}
}catch ( IOException e ) {
System.out.println(e);
}
코드를 붙인 후 코드를 실행하게 되면 아래와 같이 출력이 나오게 됩니다.
6. NIO(new Input/Output)
NIO는 무엇인가?
자바 4부터 새로운 입출력(New Input/Output) 이라는 뜻에서 java.nio 패키지가 포함되었는데, 자바 7로 버전업하면서 자바 IO 와 NIO 사이의 일관성 없는 클래스 설계를 바로 잡고 비동기 채널 등의 네트워크 지원을 대폭 강화한 NIO.2 API가 추가되었음.
그렇다면 IO 와 NIO의 차이점은 무엇인가?
구분 | IO | NIO | |
입출력 방식 | 스트림방식 | 채널방식 | |
버퍼방식 | 넌버퍼 | 버퍼 | |
비동기 방식 | 지원 안함 | 지원 | |
블로킹/넌 블로킹방식 | 블로킹 방식만 지원 | 블로킹/ 넌블로킹 모두 지원 |
스트림 VS 채널
IO는 스트림 기반입니다. 스트림은 입력 스트림과 출력 스트림으로 구분되어 있기 때문에 데이터를 읽기 위해서는 입력 스트림을 생성해야 하고, 데이터를 출력하기 위해서는 출력 스트림을 생성해야 합니다. 예를 들어 하나의 파일에서 데이터를 읽고 저장하는 작업을 모두 해야 한다면 FileInputStream 과 FileOutputStream을 별도로 생성해야 합니다.
NIO는 채널 기반입니다. 채널은 스트림과 달리 양방향으로 입력과 출력이 가능합니다. 그렇기 때문에 입력과 출력을 위한 별도의 채널을 만들 필요가 없습니다. 예를 들어 하나의 파일에서 데이터를 읽고 저장하는 작업을 모두 해야 한다면 FileChannel 하나만 생성하면 됩니다.
넌버퍼 VS 버퍼
IO에서는 출력 스트림이 1바이트를 쓰면 입력 스트림이 1바이트를 읽습니다. 이런 시스템은 대체로 느립니다. 이것보다는 버퍼를 사용해서 복수 개의 바이트를 한꺼번에 입력받고 출력하는 것이 빠른 성능을 냅니다. 그래서 IO는 버퍼를 제공해 주는 보조 스트림인 BufferedInputStream, BufferedOutputStream을 연결해서 사용하기도 합니다.
NIO는 기본적으로 버퍼를 사용해서 입출력을 하기 때문에 IO 보다는 성능이 좋습니다. 채널은 버퍼에 저장된 데이터를 출력하고, 입력된 데이터를 버퍼에 저장합니다.
IO는 스트림에서 읽은 데이터를 즉시 처리합니다. 그렇기 때문에 스트림으로부터 입력된 전체 데이터를 별도로 저장하지 않으면, 입력된 데이터의 위치를 이동해 가면서 자유롭게 이용할 수 없습니다
.
NIO는 읽은 데이터를 무조건 버퍼에 저장하기 때문에 버퍼 내에서 데이터의 위치를 이동해 가면서 필요한 부분만 읽고 쓸 수 있습니다.
블로킹 VS 넌블로킹
IO는 블로킹(blocking)이 됩니다. 입력 스트림의 read() 메소드를 호출하면 데이터가 입력되기 전까지 스레드는 블로킹(대기 상태)됩니다.
마찬가지로 출력 스트림의 write() 메소드를 호출하면 데이터가 출력되기 전까지 스레드는 블로킹됩니다.
IO 스레드가 블로킹되면 다른 일을 할 수 없고 블로킹을 빠져나오기 위해 인터럽트도 할 수 없습니다. 블로킹을 빠져나오는 유일한 방법은 스트림을 닫는 것입니다.
NIO는 블로킹과 넌블로킹 특징을 모두 가지고 있습니다.
IO 블로킹과의 차이점은 NIO 블로킹은 스레드를 인터럽트함으로써 빠져나올 수가 있다는 것입니다.
블로킹의 반대 개념인 넌블로킹인데, 입출력 작업 시 스레드가 블로킹되지 않는 것을 말합니다.
NIO의 넌블로킹은 입출력 작업 준비가 완료된 채널만 선택해서 작업 스레드가 처리하기 때문에 작업 스레드가 블로킹되지 않습니다.
여기서 작업 준비가 완료되었다는 뜻은 지금 바로 읽고 쓸 수 있는 상태를 말합니다.
NIO 넌블로킹의 핵심 객체는 멀티플렉서(multiplexor) 인 셀렉터(Selector)입니다. 셀렉터는 복수 개의 채널 중에서 준비 완료된 채널을 선택하는 방법을 제공해줍니다.
참고문헌
JAVA의 정석
출처: https://elena90.tistory.com/entry/Java-파일-입출력스트림InputStreamOutputStreamReaderWriter [오니님의짱꺤뽀]
출처: https://dololak.tistory.com/84 [코끼리를 냉장고에 넣는 방법]
출처: https://adrian0220.tistory.com/150
출처: https://vmpo.tistory.com/63
출처: https://palpit.tistory.com/640 [Palpit's Techlog]
'JAVA' 카테고리의 다른 글
15주차 : 람다식(Lambda) (0) | 2021.03.14 |
---|---|
14주차 : 제네릭 (0) | 2021.03.14 |
12주차 : Annotation (애노테이션) (0) | 2021.02.07 |
11주차 : enum (0) | 2021.02.07 |
10주차 : Thread (0) | 2021.01.31 |