프로그래밍/JAVA

[ JAVA ] 입출력

리신 2023. 4. 5. 00:57
반응형

입출력이란?

I/O란 INPUT과 OUTPUT의 약자로 입력과 출력, 간단히 줄여서 입출력이라고 한다.

입출력은 컴퓨터 내부 또는 외부의 장치와 프로그램간의 데이터를 주고받는 것을 말한다.

 

1. 스트림 (Stream)

한마디로 정의 하면 스트림이란 데이터를 운반하는데 사용되는 연결통로이다.

 

스트림의 특징

스트림은 단방향통신만

가능하다.

따라서 입력과 출력을 동시에 처리 할 수 없다.

 

입력과 출력을 동시에 수행하려면❕❓

입력을 위한 입력스트림과 출력을 위한 출력스트림,  2개의 스트림이 필요하다.

 

그리고 스트림은 먼저 보낸 데이터를 먼저 받게 되어 있으며 중간에 건너뜀 없이 연속적으로 데이터를 주고받는다.

큐와 같은 FIFO(First In First Out)구조로 되어 있다고 생각하면 이해가 쉽다.

 

2. 바이크기반 스트림 - InputStream, OutputStream

스트림은 바이트단위로 데이터를 전송하며 입출력 대상에 따라 아래의 표와 같이 입출력스트림이 있다.

입력스트림 출력스트림 입출력 대상의 종류
FileInputStream FileOutputStream 파일
ByteArrayInputStream ByteArrayOutputStream 메모리(byte배열)
PipedInputStream PipedOutputStream 프로세스(프로세스간의 통신)
AudioInputStream AudioOutputStream 오디오장치

자바에서는 java.io패키지를 통해서 많은 종류의 입출력관련 클래스들을 제공하고 있으며,

입출력의 대상이 달라져도 동일한 방법으로 입출력이 가능하기 때문에 프로그래밍을 하기에 편리하다.

 

InputStream OutputStream
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의 read()와 OutputStream의 write(int b)는 입출력의 대상에 따라 읽고 쓰는 방법이 다를 것이기 때문에

각 상황에 알맞게 구현하라는 의미에서 추상메서드로 정의되어있다.

 

 

3. 보조 스트림

보조 스트림은 말그대로 스트림을 보조하는 역할을 한다.

보조 스트림은 실제로 데이터를 주고받는 스트림이 아니기 때문에 데이터를 입출력 할 수 있는 기능은 없지만,

스트림의 기능을 향상시키거나 새로운 기능을 추가 할 수 있게 해준다.

 

예) test.txt라는 파일을 읽기 위해  FileInputStream을 사용할 때, 입력 성능을 향상시키기 위해 버퍼를 사용하는

보조스트림인 BuffereadInputStream을 사용한다.

 

입력 출력 설명
FilterInputStream FilterOutputStream 필터를 이용한 입출력 처리
BufferedInputStream BufferedOutputStream 버퍼를 이용한 입출력 성능향상
DataInputStream DataOutputStream int, float와 같은 기본형 단위로 데이터를 처리
SequenceInputStream 없음 두 개의 스트림을 하나로 연결
LineNumberInputStream 없음 읽어 온 데이터의 라인 번호를 카운트
ObjectInputStream ObjectOutputStream 데이터를 객체단위로 읽고 쓰는데 사용
없음 PrintStream 버퍼를 이용하여 추가적인 print관련 기능
PushbackInputStream 없음 버퍼를 이용해서 읽어 온 데이터를 다시 되돌리는 기능

 

4. 문자기반 스트림 - Reader, Writer

Java에서는 한 문자를 의미하는 char형이 1byte가 아니라 2bute이기 때문에 바이트기반의 스트림으로 2byte인 문자를 처리하는 데는 어려움이 있다.

이 점을 보완하기 위해서 문자기반의 스트림이 제공된다.

문자데이터를 입출력할 때는 바이트기반 스트림 대신 문자기반 스트림을 사용해야한다.

 

바이트기반 스트림 문자기반 스트림
FileInputStream
FileOutputStream
FileReader
FileWriter
ByteArrayInputStream
ByteArrayOutputStream
CharArrayReader
CharArrayWriter
PipedInputStream
PipedOutputStream
PipedReader
PipedWriter
StringBufferedInputStream
StringBufferedOutputStream
StringReader
StringWriter

문자기반 스트림의 이름은 바이트기반 스트림의 이름에서 뒤에 부분만 바꿔주면 된다.

 

InputStream  → Reader
OutputStream → Writer

 

5. 바이트기반 스트림

 

InputStream 메서드

 메서드 명 설 명 
 int available()  스트림으로부터 읽어 올 수 있는 데이터의 크기를 반환한다. 
 void close()   스트림을 닫음으로써 사용하고 있던 자원을 반환한다. 
 void mark(int readlimit)    현재 위치를 표시해 놓는다. 후에 reset()에 의해서 표시해 놓은 위치로 다시 돌아갈 수 있다. 
 readlimit: 되돌아갈 수 있는 byte의 수
 boolean markSupported()   mark()와 reset()을 지원하는 지를 알려 준다. 이 메서드로 먼저 확인을 해야한다.
 abstract int read()   1 byte를 읽어온다. 더 이상 읽을 데이터가 없다면 -1을 반환한다. 
 int read(byte[] b)   배열 b의 크기만큼 읽어서 배열을 채우고 읽어 온 데이터의 수를 반환한다.
 int read(byte[] b, int off, int len)  최대 len 개의 byte를 읽어서 배열 b의 위치off부터 저장한다. 
 void reset()   스트림에서 위치를 마지막으로 mark()이 호출되었던 위치로 되돌린다. 
 long skip(long n)   스트림에서 주어진 길이n만큼 건너뛴다. 

 

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개 만큼만을 읽어서 출력소스에 쓴다. 

 

스트림의 종류에 따라서 mark()와 reset()을 사용하여 이미 읽은 데이터를 되돌려서 다시 읽을 수 있다.
이 기능을 지원하는 스트림인지 확인하는  markSupported()를 통해서 알 수 있다.


flush()는 버퍼가 있는 출력스트림의 경우에만 의미가 있으며, OutputSteream에 정의된 flush()는 아무런 일도 하지 않는다.
프로그램이 종료될 때, 사용하고 닫지 않은 스트림을 JVM이 자동적으로 닫아 주기는 하지만,

스트림을 사용해서 모든 작업을 마치고 난 후에는 close()를 호출해서 반드시 닫아 주어야 한다.

그러나 ByteArrayInputStream과 같이 메모리를 사용하는 스트림과
System.in, System.out과 같은 표준 입출력 스트림은 닫아 주지 않아도 된다.

 

6. ByteArrayInputStream과 ByteArrayOutputStream

두 스트림은 메모리, 즉 바이트배열에 데이터를 입출력 하는데 사용되는 스트림이다.

주로 다른 곳에 입출력하기전에 데이터를 임시로 바이트배열에 담아서 변환 등의 작업을 하는데 사용된다.

 

예제를 보면 아래의 코드는 ByteArrayInputStream ByteArrayOutputStream을 이용해서 바이트배열 inSrc의 데이터를 outSrc로 복사하는 예제이다.

public class Main {
    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;
        // read()를 호출한 반환값을 변수 data에 저장한 후 data에 저장된 값이 -1인지 비교
        while((data = input.read()) != -1) {
            output.write(data); 
        }

        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]
*/

바이트배열은 사용하는 자원이 메모리 밖에 없으므로 가비지컬렉터에 의해 자동적으로 자원을 반환하므로 close()를 이용해서 스트림을 닫지 않아도 된다.

그리고 위 코드는 read()와 write( int b)만 사용하기 때문에 한번에 1 byte만 읽고 쓰므로 작업효율이 떨어진다.

 

그러면 효율적이게 작업을 하기 위해서는 어떻게 해야할까?

아래의 예제를 보면 int read(byte[] b, int off, int len)와 void write(byte[] b, int off, int len)을 이용해서 입출력하는 방법을 보여주는 예이다.

 

이전 예제와는 달리 byte배열을 이용해서 한 번에 배열의 크기만큼 읽고 쓸 수 있다.

만약 스트림의 크기를 정확히 모를 때는 일정한 크기의 바이트를 계속해서 받아서 써야한다.

public class Main {
    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.read(temp, 0, temp.length);       // 읽어 온 데이터를 temp에 담는다.
        output.write(temp, 5, 5);           // temp[5]부터 5개의 데이터를 write한다.

        outSrc = output.toByteArray();

        System.out.println("Input Source: " + Arrays.toString(inSrc));
        System.out.println("temp Source: " + Arrays.toString(temp));
        System.out.println("output Source: " + Arrays.toString(outSrc));

    }
}
/*
출력결과
Input Source: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
temp Source: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
output Source: [5, 6, 7, 8, 9]


*/

 

7. FileInputStream과 FileOutputStream

FileInputStream/FileOutputStream은 파일에 입출력을 하기 위한 스트림이다.

생성자 설명
FileInputStream(String filePath) filepath로 지정한 파일에 대한 입력 스트림을 생성한다.
FileInputStream(File fileObj) fileObj로 지정한 파일에 대한 입력 스트림을 생성한다.
FileInputStream(FileDescriptor fdObj) fdObj 로 기존의 접속을 나타내는 파일 시스템의 입력 스트림을 생성한다.
FileOutputStream(String filepath)  filepath로 지정한 파일에 대한 OutputStream을 생성한다.
FileOutputStream(String filepath)  지정한 파일로 OutputStream을 생성한다. append 인자로 출력할 때 append 모드를 설정한다.
FileOutputStream(File fileObj)  fileObj로 지정된 파일에 대한 OutputStream을 생성한다.
FileOutputStream(File fileObj, boolean append) fileObj로 지정된 파일에 대한 OutputStream을 생성한다. append 인자로 출력할 때 append 모드를 설정한다.
FileOutputStream(FileDescriptor fdObj) fdObj 로 기존의 접속을 나타내는 파일 시스템의 OutputStream을 생성한다.

* 단순히 텍스트파일을 다루는 경우에는 문자기반의 스트림인 FileReader/FileWriter를 사용하는 것이 더 좋다.

 

 

*참고 : JAVA의 정석

반응형

'프로그래밍 > JAVA' 카테고리의 다른 글

[ JAVA ] 네트워킹 (Networking)  (0) 2023.04.25
[ JAVA ] 직렬화란  (0) 2023.04.25
[ JAVA ] 쓰레드란?  (0) 2023.02.21
[JAVA] Properties 란?  (0) 2023.01.31
[JAVA] TreeMap 이란  (0) 2023.01.29